Tutorial 5 - Iterators and Collision Handlers

In Tutorial 4 - Spawn Points and Factories, we created lots of mines floating around the ship, but we didn't do anything to make them collide.

This tutorial will cover a few techniques you can use to make objects interact with each other.

Homing Missiles

What if the missiles were smart enough to always target the closest mine? To do that, a MissileObj will need to examine all the SpaceMineObjs and pick the closest one to itself. This can be achieved by using an object iterator in MissileObj::update():

#define MISSILE_ANGSPEED     3.5f
        void update ( int objLayer, float dt ) {
            // Search the Environment for the closest space mine:
            SpaceMineObj * pTargetMine = NULL;
            float fTargetDist;
            
            ObjIterator<SpaceMineObj> it = Environment::makeObjIterator<SpaceMineObj>();
            while ( it.hasNext() ) {
                SpaceMineObj * pMine = it.next();
                
                // Calculate distance between the space mine and this missile:
                float fDist = (pMine->getPos() - m_pos).length();
                
                // Have we found a shorter distance?
                if ( pTargetMine == NULL || fDist < fTargetDist ) {
                    pTargetMine = pMine;
                    fTargetDist = fDist;
                }
            }
            
            // We've found our target mine!
            // ...Or maybe not. If there are no mines left in the playfield,
            // then there's nothing to target.
            if ( pTargetMine != NULL ) {
                // OK, we've got a target.
                Vector2D vMissileToMine = pTargetMine->getPos() - m_pos;
                
                // Rotate the missile's velocity so it will arc towards the mine.
                if ( m_vel.angle( vMissileToMine ) < 0 )
                    // Counter-clockwise:
                    m_vel = m_vel.rotate( -MISSILE_ANGSPEED * dt );
                else
                    // Clockwise:
                    m_vel = m_vel.rotate( MISSILE_ANGSPEED * dt );
            }
            
            ...

With this code, missiles will always fly towards the mine that's closest to them. However, we still need to make them actually explode and destroy each other on contact.

Collision Structures

In order to detect object-to-object collisions, each object will be assigned an instance of qaf::CollisionStruct. This is an abstract class; check out its documentation for a list of collision structure subclasses you can use. For this tutorial, we'll use qaf::CollisionStruct::Circle and qaf::CollisionStruct::Polygon.

Next, we need to associate collision structures with our objects. This is done with another method inherited from qaf::GameObj:

qaf::CollisionStruct * qaf::GameObj::getCollisionStruct ();

By default, GameObj's implementation of this method returns NULL (which means "do not test this object for collisions"). To detect object-to-object collision, we're going to override this behavior in our classes and return a collision structure for each object.

SpaceMineObj:
This one is simple enough: Just initialize a circle in the constructor, and return it in getCollisionStruct().

class SpaceMineObj : public GameObj {
    private:
        ...
        
        CollisionStruct::Circle m_circle; // Collision boundaries
        
    public:
        SpaceMineObj ( Vector2D pos, float size ) {
            ...
            
            // Initialize collision structure:
            m_circle.setXCenter( m_pos.x );
            m_circle.setYCenter( m_pos.y );
            m_circle.setRadius( m_size * m_sprite->GetWidth() / 2 );
        }
        
        CollisionStruct * getCollisionStruct () {
            return &m_circle;
        }
        
        ...
    };

MissileObj:
The missiles are constantly moving and rotating, so we can't simply initialize a bounding box in the constructor! Since the collision structure needs to be updated all the time, we'll add a bit of code to update().

class MissileObj : public GameObj {
    private:
        ...
        
        CollisionStruct::Polygon m_poly; // Collision boundaries
    
    public:
        // Constructor:
        MissileObj ( Vector2D pos, Vector2D vel ) {
            ...
            
            // Create a polygonal collision structure we can rotate along with
            // the sprite:
            m_poly = CollisionStruct::Polygon( m_sprite );
            
            // Initialize the polygon's position and rotation:
            Vector2D vUp = Vector2D(0, -1);
            m_poly.setRotation( vUp.angle( m_vel ) );
            m_poly.setPosition( m_pos.x, m_pos.y );
        }
        
        CollisionStruct * getCollisionStruct () {
            return &m_poly;
        }
        
        ...
        
        void update ( int objLayer, float dt ) {
            ...
            
            // Update collision bounds:
            Vector2D vUp = Vector2D(0, -1);
            m_poly.setRotation( vUp.angle( m_vel ) );
            m_poly.setPosition( m_pos.x, m_pos.y );
            
            ...
        }
        
        ...
};

Almost there! Now that we've taken care of collision detection, it's time to implement collision response.

The Collision Handler

This is just a callback function. It will be invoked when a collision occurs, and the two colliding objects will be passed as arguments to the function. We're interested in collisions between MissileObjs and SpaceMineObjs, so the declaration looks like this:

// The collision handler:
void handleCollision_Missile_SpaceMine ( MissileObj * pMissile, SpaceMineObj * pMine ) {

I'm going to use a predefined class from the library (qaf::AnimParticleObj) and an animation object (declared in tut05sprites.txt) to create an explosion at the space mine's position.

    // Create an explosion, by using one of Qaf's helper classes:
    Environment::addGameObj(
        new AnimParticleObj( 
            pResManager->GetAnimation( "aniExplosion" ), // anim
            pMine->getPos().x, pMine->getPos().y, 0,     // x, y, angle
            0, 0, 0,                                     // vx, vy, vr
            0, 0,                                        // ax, ay
            pMine->getSize(), pMine->getSize() ) );      // sizeX, sizeY

The mine and the missile will obliterate each other, so let's remove them from the playfield.

    // Remove both objects from the playfield and delete them:
    Environment::removeGameObj( pMine, true );
    Environment::removeGameObj( pMissile, true );
}

Finally, we register our handler. In WinMain:

    // "Hey, Qaf, this is what you should use to handle collisions between
    // Missiles and SpaceMines":
    Environment::registerCollisionHandler<MissileObj, SpaceMineObj>( handleCollision_Missile_SpaceMine );   

The syntax is a bit heavy there, so let's look at that line little by little.

That's it!

If you run the executable, you'll see a couple of extra touches:

See the full source code for this tutorial in the file: tutorials/tutorial05.cpp


Generated on Sun Mar 25 12:32:13 2007 for Qaf Framework by  doxygen 1.5.1-p1