Game Development Community

Stop anticipating my collisions TGB

by Kevin James · in Torque Game Builder · 07/25/2006 (5:16 pm) · 14 replies

Ok, so I have a tank, and it fires shells. I used to have the shell speed at 200 (forward only). I dragged it all the way down to 60 because the shell would explode 5ft. before it hit whatever it was going to hit. Even at 60 if the shell comes to close to certain corners, it will explode. So far all the collisions have been between my shell and a tile layer. I'm fine at having the speed at sixty, but I want the collision detection to be bug-free which it currently is not. Help, suggestions, advice, and whatever else is welcome! ;D

About the author

Computer security, digital forensics, and platform jumper enthusiast. shells.myw3b.net/~syreal/


#1
07/26/2006 (11:11 am)
Just to clarify, are your tiles squares? Or are there different shapes likes slopes, but the shell explodes when it goes near the slopes? If the latter, then you'll probably need to edit the collision points. I wrote a quick explanation on how to do this, here. If the shell speed is really slow, like 15, is the collision perfect or does it still explode even though there's no contact?
#2
07/26/2006 (6:42 pm)
Ah yes, I should have included those details, Apurva. If I set the speed at 20, then the collision is perfect. All my tiles are the default polygons, in which case they are all squares. I was going to say something else, but a friend called and I have thus forgotten. Let me know any other info that you may need to know.
#3
07/26/2006 (7:03 pm)
Hmm, quite tricky to assess what the problem could be. I tried colliding two sprites at a linear velocity of 200. I also, tried colliding a sprite with the tilemap, and they both collided at the right time. How are you coding the collision detection? Try doing this, and see if you notice anything funny:
when you start your game, open the console. Then type in:
scenewindow2d.getScenegraph().setdebugon(5)

You should see green boxes surrounding your tiles and sprites, which represent collision boxes. With this mode on, shoot a missile (at 200) and see if the collision boxes overlap when there's an explosion, or whether it explodes even though there's been no overlap.
#4
07/26/2006 (7:39 pm)
Observe:
www.metalbuildingreplicas.com/clan/TGB/screen1.jpg
When I fire directly (at 200) into this building, the shell explodes between the two blocks closest to the tank. If I fire close and perpendicular to the wall, the shell does not explode. However, at 60, the shell will explode unexpectly only around the lower corner. The shell uses a collision callback that calls a shellExplode function, that plays the effect and deletes the shell. Perhaps it is because I'm using forward motion only on my shell, it seemed simpler at the time, but if it has caused this hassel, I'm am not going to be a happy camper. . .but better a discontented camper with a good, bug-free game, than a happy one with a buggy game. Im hitting the hay, so don't expect a response short of 10 hrs. lol!

Thanks for your time.
#5
07/26/2006 (7:51 pm)
Did any of the collision boxes overlap when the explosion took place, or did they just explode randomly? This is quite a strange problem. Try enabling callbacks on all collidable objects. I just spent half-hour today trying to get some rotation things to work. I wanted a sprite to rotate and once it rotated 180 degrees, it should echo "rotated". However, it kept doing it instantly, instead of after it finished rotating. Turned out, I forgot to explicitly turn on callback. Since this thing was being echoed anyway, I assumed it was on by default, but it wasn't. So basically, try enabling callbacks for all the objects, just not the shells. That may help, but not sure.

edit: I'm using firefox and the picture didn't come up until after I posted the above. Is your tilemap made of just the one layer, or are there more layers? I'll see if I can think of anything tomorrow. As for me, I too am off to sleep :D.
#6
07/27/2006 (7:18 am)
Lets just say I had some issues with the picture, lol! :)

I adapted some code from my tank to incorporate it into the movement of the shell. This way, the shell now uses setLinearVelocity instead of setForwardSpeed. For some strange reason the collision detection is now perfect down to the pixel, though the shell will explode just a little bit before it hits a barrier. You may be onto something with enabling other callbacks. If I make the tilemap receive collision, maybe this will elminate the problem of exploding a little before the shell hits something. This problem is a lot better than shells randomly exploding though, ;)

btw---The tile map is all one layer.
#7
07/27/2006 (7:28 am)
Glad you got it working a bit more that before! Do the shells have a world limit, that could be stopping them from going any further? Also, if your turret moves around and you can shoot at different angles, try using the setLinearVelocityPolar command. It lets you choose the angle at which to shoot, so you don't have to split the X and Y components. Is the barrier, in the picture, the wall? Or is it something outside the picture? When you shoot the shell, is the collision box around it the same size as the shell? When you add a sprite to TGB, the collision box is, by default, the same size as the bounding box, which is slightly bigger than the actual sprite, if it's not a perfect square. Therefore, unless you have made the collision box smaller for the shell (assuming the shell is a smaller sprite), then that could be the reason why it's colliding slightly before the wall. If it still doesn't work, and you want me to take a look at it, then feel free to send me the project file and I'll see if I can fix it here.
#8
07/27/2006 (8:06 am)
The shells don't have any world limit, the barrier is actually the wall with the collision set to on in the tile editor. I'll try the setLinearVelocityPolar cmd since maybe for the same strange reason as the SetLinearVelocity working better than setForwardSpeed, mayber Polar will work better than just setLinearVelocity. Thanks for the offer to look at it yourself, I really appreciate it Apurva, but its not really that big of a deal, if I can't fix, then I'll just move on. I haven't yet tried enabling the callback for the tilemap, so I'll let you know how it goes as soon as I try it.
#9
07/27/2006 (8:09 am)
Ok, that's cool. Hope you get it working!
#10
07/27/2006 (8:55 am)
I guess defaultly the collision callback is called only for the sender object, so the callback for the tilemap didn't work, and setLinearVelocityPolar didn't affect it, but it did simplyfy my code, which is nice! Thanks for the advice, I'll be sure to post my game in the Show Off! section in a week or so. Keep you eyes peeled! O_O
#11
08/02/2006 (11:35 am)
I've had a look at this and I have discovered the problem. Essentially, when an object does a broad-phase collision (first-collision-pass), it takes its position/area and expands that area using its current velocity over the current integration period as an extrapolation of the potential area to be covered during that period. That area is then used as the broad-phase detection of objects. Unfortunately, it's here that there's a problem.

I expanded this code to take into account the different areas that the object can assume that covers both polygon and circle (sub/superscribed) modes. Unfortunately, I didn't take into account that when in the "forward-only" mode, the velocity isn't necessarily added using the axis basis, it's added according to the current orientation.

So to be clear; if you were using the "forward-only" movement mode, your collisions won't be correct.

I'll have to look at this issue but I think the change will be limited to a single function, namely "t2dSceneContainer::findPotentialCollisions" so I may be able to post the fix here.

I have posted this bug into our system (#1924).

Sorry for the problem.

- Melv.
#12
08/02/2006 (11:51 am)
Okay, so I had a quick look and the fix seems simple but I really don't have the time to test it adequately this evening. Here's the change to "t2dSceneContainer::findPotentialCollisions" in "t2dSceneContainer.cc". Please bear in mind that I can only guarantee that this builds, not works as I've not tested it!

Anyway, here goes...
#13
08/02/2006 (11:51 am)
//-----------------------------------------------------------------------------------
// Find Potential Collision(s) for specified SceneObject within Container Bin System.
//-----------------------------------------------------------------------------------
U32 t2dSceneContainer::findPotentialCollisions( t2dSceneObject* pSceneObject2D, const F32 elapsedTime, t2dFindCallback pFnCallback, void* storage, RectF& srcRectangleSearched )
{
    // Check everything is fine!
    AssertFatal( pSceneObject2D != NULL, "t2dSceneContainer::findPotentialCollisions() - Invalid Object" );

    // Finished if either send-collision/object are disabled.
    if ( !pSceneObject2D->getCollisionActiveSend() || !pSceneObject2D->getEnabled() )
        return 0;

// T2D Debug Profiling.
#ifdef TORQUE_ENABLE_PROFILER
    PROFILE_START(t2dSceneContainer_findPotentialCollisions);
#endif

    // Fetch World Collision Clip Rectangle.
    srcRectangleSearched = pSceneObject2D->getWorldCollisionClipRectangle();

    // Expand Search Rectangle to include area covered by current velocity over elapsed time plus the object size.
    t2dVector objVelocity;

    // Are we using forward-only movement?
    if ( pSceneObject2D->getForwardMovementOnly() )
    {
        // Yes, so calculate forward vector.
        const t2dVector forwardVec(0.0f, -pSceneObject2D->getLinearVelocity().len() * elapsedTime);
        // Orientate the velocity to the new vector.
        objVelocity = forwardVec * pSceneObject2D->getRotationMatrix();
    }
    else
    {
        // No, so get integrated velocity.
        objVelocity = pSceneObject2D->getLinearVelocity() * elapsedTime;
    }

    // Remove Trivial Velocities.
    objVelocity.clampZero();

    // Are we moving over time?
    if ( objVelocity.isLenNotZero() )
    {
        // Deal with X-Axii.
        if ( mLessThanZero(objVelocity.mX) )
        {
            // Extend Left.
            srcRectangleSearched.point.x += objVelocity.mX;
            srcRectangleSearched.extent.x -= objVelocity.mX;
        }
        else
        {
            // Extend Right.
            srcRectangleSearched.extent.x += objVelocity.mX;
        }

        // Deal with Y-Axii.
        if ( mLessThanZero(objVelocity.mY) )
        {
            // Extend Up.
            srcRectangleSearched.point.y += objVelocity.mY;
            srcRectangleSearched.extent.y -= objVelocity.mY;
        }
        else
        {
            // Extend Down.
            srcRectangleSearched.extent.y += objVelocity.mY;
        }
    }

    // Find which bins we need to search.
    U32 minX, minY, maxX, maxY;
    getBinRectangle( srcRectangleSearched, minX, minY, maxX, maxY );

    // Fetch Source Collision Group/Layer Masks.
    U32 srcCollisionGroupMask = pSceneObject2D->getCollisionGroupMask();
    U32 srcCollisionLayerMask = pSceneObject2D->getCollisionLayerMask();

    // Change the Current Sequence Key.
    nextSequenceKey();

    // Reset Object Found Counter.
    U32 objectsFound = 0;

    // Step through Bin ranges.
    for ( U32 y = minY; y <= maxY; y++ )
    {
        // Calculate Bin Position.
        U32 offsetY = (y % mBinCount) * mBinCount;

        for ( U32 x = minX; x <= maxX; x++ )
        {
            // Calculate Bin Position.
            U32 arrayIndex = offsetY + (x % mBinCount);

            // Fetch Bin Chain.
            t2dSceneBinReference* pBinChain = mSceneBinArray[arrayIndex].mpNextBinReference;

            // Increase Bin Collisions.
            mpParentSceneGraph2D->getDebugStats().objectBinCollisions++;

            // Step through Chain.
            while ( pBinChain )
            {
                // Fetch Scene Object Reference.
                t2dSceneObject* pSceneObjectRef = pBinChain->mpSceneObject2D;

                // Make sure we're not colliding with ourself!
                if ( pSceneObjectRef != pSceneObject2D )
                {
                    // Is the Object Enabled?
                    if ( pSceneObjectRef->getEnabled() && !pSceneObjectRef->isBeingDeleted() )
                    {
                        // Yes, so have we dealt with this object already?
                        if ( pSceneObjectRef->getContainerSequenceKey() != getCurrentSequenceKey() )
                        {
                            // No, so set the container sequence key to indicate so.
                            pSceneObjectRef->setContainerSequenceKey( getCurrentSequenceKey() );

                            // Check if there's a potential collision.
                            if (    pSceneObjectRef->getGraphGroupMask() & srcCollisionGroupMask &&
                                pSceneObjectRef->getLayerMask() & srcCollisionLayerMask )
                            {
                                // Fetch Clip Rectangle.
                                RectF clipRectangle = srcRectangleSearched;

                                // Do the collision clip rectangles intersect?
                                if ( clipRectangle.intersect( pSceneObjectRef->getWorldCollisionClipRectangle() ) )
                                {
                                    // Yes, so perform callback.
                                    (*pFnCallback)(pSceneObjectRef, storage);

                                    // Increase Objects Found Counter.
                                    objectsFound++;
                                }
                            }
                        }
                    }
                }

                // Move to next bin reference.
                pBinChain = pBinChain->mpNextBinReference;
            }
        }
    }

    // Fetch Overflow Bin Chain.
    t2dSceneBinReference* pBinChain = mSceneOverflowBin.mpNextBinReference;

    // Step through Chain.
    while ( pBinChain )
    {
        // Fetch Scene Object Reference.
        t2dSceneObject* pSceneObjectRef = pBinChain->mpSceneObject2D;

        // Increase Bin Collisions.
        mpParentSceneGraph2D->getDebugStats().objectBinCollisions++;

        // Make sure we're not colliding with ourself!
        if ( pSceneObjectRef != pSceneObject2D )
        {
            // Is the Object Enabled?
            if ( pSceneObjectRef->getEnabled() && !pSceneObjectRef->isBeingDeleted() )
            {
                // Yes, so have we dealt with this object already?
                if ( pSceneObjectRef->getContainerSequenceKey() != getCurrentSequenceKey() )
                {
                    // No, so set the container sequence key to indicate so.
                    pSceneObjectRef->setContainerSequenceKey( getCurrentSequenceKey() );

                    // Check if there's a potential collision.
                    if (    pSceneObjectRef->getGraphGroupMask() & srcCollisionGroupMask &&
                        pSceneObjectRef->getLayerMask() & srcCollisionLayerMask )
                    {
                        // Fetch Clip Rectangle.
                        RectF clipRectangle = srcRectangleSearched;

                        // Do the collision clip rectangles intersect?
                        if ( clipRectangle.intersect( pSceneObjectRef->getWorldCollisionClipRectangle() ) )
                        {
                            // Yes, so perform callback.
                            (*pFnCallback)(pSceneObjectRef, storage);

                            // Increase Objects Found Counter.
                            objectsFound++;
                        }
                    }
                }
            }
        }

        // Move to next bin reference.
        pBinChain = pBinChain->mpNextBinReference;
    }

// T2D Debug Profiling.
#ifdef TORQUE_ENABLE_PROFILER
    PROFILE_END();   // t2dSceneContainer_findPotentialCollisions
#endif

    // Return Objects Found Count.
    return objectsFound;
}
#14
08/02/2006 (1:10 pm)
:O

I haven't set up VC++ yet, so I'm not able to test it either and most of the source code is way over my head anyway. Thanks for looking into the problem though, Melv. Hopefully the fix will appear in the next version!

P.S. I had already resolved the problem, using setLinearVelocityPolar, though I'm considering fixing that with an all-out setLinearVelocity method, becuse collision callbacks are still fired early, though not nearly as drastic.