This tutorial will cover a few techniques you can use to make objects interact with each other.
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.
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:
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.
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.
qaf::Environment::registerCollisionHandler is a template method. It accepts two type parameters.<MissileObj, SpaceMineObj> are the type parameters. Collision detection is class-based, so we actually tell Qaf the class, or type, of object that we want to handle.handleCollision_Missile_SpaceMine is a pointer to the function that should be called when a collision between a MissileObj and a SpaceMineObj is detected.That's it!
If you run the executable, you'll see a couple of extra touches:
qaf::CollisionStruct::render() in the objects' render() method.qaf::Environment::loadRoom() in the frame function.See the full source code for this tutorial in the file: tutorials/tutorial05.cpp
1.5.1-p1