This tutorial will cover a few techniques you can use to make objects interact with each other.
MissileObj
will need to examine all the SpaceMineObj
s 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.
MissileObj
s and SpaceMineObj
s, 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