7.3 Force and acceleration

We handle the question of the forces acting on our critters by using the Strategy pattern. That is, we let our critters be composed with cForce *_pforce objects, and we let a critter feel forces by calling _pforce->force(this).

If we hadn't used the Strategy pattern, we might instead have given the cCritter class a virtual cVector force() method which would give the force acting on the critter at any location and time. But, we want to able to change the kinds of forces that act on a critter without having to derive a whole new child class for each combination of forces. For to derive off critter child classes for the different kinds of forces would lead to a combinatorial explosion of more and more kinds of critter child classes.

We want to allow for force fields such as gravity, friction, a whirlpool, etc. Rather than specifically defining gravity-influenced critters, whirlpool-influenced critters, friction-influenced critters, and so on, we take the notion of a force, and split it off into a separate class called cForce.

The main method of cForce is a cVector force(cCritter *pcritter). The cForce::force method returns a vector that we think of as the force acting on the pcritter. Instead calling a cCritter::force() method, we'll have the critter call _pforce->force(this).

Let's review from our discussions of composition and of the Strategy pattern the question of why we need the this argument. When we use a Strategy pattern, we usually want the delegated strategy function to have access to the calling critter. That is, in order to figure out the force acting on a critter, we may need to know where the critter is located, what its velocity is, etc. And it may also be that we want to have an 'impulsive' force that directly changes the critter's velocity. By passing this into the method call, we give the cForce object the ability to access the members of the calling critter by using the cCritter accessors and then mutate the calling critter with its mutators.

As it turns out, each of our cCritter objects has a CTypedPtrArray<CObArray, cForce*> _forcearray. This is an variant of the Strategy pattern; rather than strategizing out a single force() method we strategize out an arbitrarily sized array of such methods.

The basic cCritter update method feels the forces affecting the critter, and changes the acceleration of the critter accordingly.

void cCritter::update() 

The default feelforce method applies Newton's Law




That is, feelforce (a) sums up the vector forces acting on the critter, (b) divides the vector sum by the critter's mass, and (c) sets the critter's acceleration to this value. In code, these steps look as follows.

void cCritter::feelforce() 
    cVector forcesum; /* Default cVector constructor sets this 
        to (0,0) */ 
    for (int i=0; i<_forcearray.GetSize(); i++) 
        forcesum += _forcearray.GetAt(i)->force(this); 
    _acceleration = forcesum/mass(); /* From Newton's Law: 
        Force = Mass * Acceleration. */ 

We make cCritter::feelforce virtual because in some situations you might not want to simply sum up the forces. This could happen if some of the forces were what the computer scientist Craig Reynolds calls 'steering forces' [Steering behaviors for antonomous characters', (www.red3d.com/cwr/steer/gdc99]. Suppose, for instance, that you had a steering force f1 that avoids bumping into obstacles and a steering force f2 that runs away from bullets. If you simply add the forces f1 and f2 it might sometimes happen that they cancel each other out and you end up hitting an obstacle and being hit by a bullet. A more sophisticated feelforce might prioritize your steering forces. Another possibility that might be used, if you are using several computationally expensive forces, is to 'dither' between them by doing first one force and then the other on alternating updates.

There's one other way that we change our critter's velocities and accelerations: via mouse and keyboard controls. Making another use of the Strategy pattern, we give each critter a cListener* _plistener object. cListener has a listen(cCritter *pcritter) method, and the cCritter feellistener() method calls _plistener->listen.

The Pop Framework provides several different kinds of built-in listener options, and some of them, such as the cListenerCar and cListenerSpaceship, act by adding in a vector value to the _acceleration of the calling pcritter. Other listeners, such as the cListenerScooter, act by directly changing the critter's velocity. You'll find more about listeners in Chapter 12: Listeners.

    Part I: Software Engineering and Computer Games
    Part II: Software Engineering and Computer Games Reference