15.2 The Defender3D code


The overrides for cGameDefender3D are fairly routine. In the constructor, we have _border.set(19, 19.0, 41.0) to give the world a z-thickness. We think of the z direction as running down into the screen. We give the game a cCritterDefender3DPlayer, and the seedCritters calls a simple loop to populate the world.

for (int i=0; i < _seedcount; i++) 
    new cCritterDefender3DProp(this); 

The adjustGameParameters code is also quite simple; if there are less than _seedcount props, it replenishes the count.

In order to have our games behave smoothly as we use the menu to switch on and off the various View menu options, we need to have two separate view and viewer initialization methods. If we were only writing one game with one kind of view this wouldn't be necessary; the complexity is a result of the code being usable as a flexible framework to build a variety of changeable games.

The initializeView(CPopView *pview) call sets the viewer (that is, the pview->pviewpointcritter()) to use a cListenerViewerRide listener. This means that the viewer is by default attached to the player.

The initializeViewpoint(cCritterViewer *pviewer) tweaks the viewer (that is, the pviewer) in various ways, depending on whether the viewer is using the default rider listener or whether you have possibly switched the viewer to use a Scooter type control that's not locked to the player. In the case where we are riding the player, this call positions the viewer directly behind the player so that we can look along the player's shooting direction.

Here you can see why the Pop Framework has separate view and viewer initialization methods? It's so the games will behave smoothly as you use the menu to switch on and off the various View menu options. If you turn off the Ride Player, then the code calls initializeViewpoint to reset the viewer to some reasonable position, and if you turn Ride Player back on, initializeViewpoint gets called again. We set the viewer to use the Ride Player option in initializeView, as this is the call that gets made when we start up a view.

Now let's discuss the overrides of the critters, which accounts for the bulk of the new defender game code.


Let's talk about the cGameDefender3DPlayer constructor first. The constructor begins by giving the player a new kind of listener.

Since this is something like a Space Invaders game, we want to use some kind of Arrow key control. But rather than using a cListenerArrow, we use a cListenerArrowAttitude. The difference is that where the cListenerArrow uses Left/ Right, Up/Down, and PgUp/PgDn to move the critter along the x-, y-, and z-axes respectively, the cListenerArrowAttitude uses Left/Right, Up/Down, and PgUp/PgDn to move the critter along its normal, binormal, and tangent directions, respectively. The latter is more flexible for a wide range of orientations.

We want our player critter's sprite to maintain a fixed attitude, basically acting as a gun sight. Here's the code to do this from the cGameDefender3DPlayer constructor.

setAttitudeToMotionLock(FALSE); /* don't turn your sprite with the 
    motions. */ 
setAimToAttitudeLock(FALSE); //Don't turn your attitude with the gun. 
setAttitudeTangent(-cVector::ZAXIS); //point down into the world. 
setSprite(new cSpriteCircle()); 
psprite()->setFilled(FALSE); /* So I can see through the center of the 
    disk. */ 
psprite()->setSpriteAttitude( cMatrix::yRotation(PI/2.0)); 
    //To face the user. 

The purpose of the code above is to carry out these steps.

  • Turn off any coupling between the attitude and the motion or the aim direction.

  • Point the critter and its gun down the negative z-axis.

  • Remove its filling so we can see through it.

  • Rotate the sprite up out of the plane of the critter's tangent and normal.

The rotation step isn't quite obvious, and in fact the author got it by a little trial and error. In retrospect it seems right because the critter's intrinsic x-axis is its tangent, which now points towards the negative z-axis (into the screen) and the critter's intrinsic y-axis is its normal which points towards the negative x-axis (to the left of the screen), so the plane of these two is edge-on to the viewer. A 90° rotation about the y-axis is just what's needed!

It turns out to be pretty hard to effectively aim at a moving object in three-dimensional space. So we smarten-up our player's shooting skills by (a) having cGameDefender3DPlayer::shoot pick out the critter closest to the gun's current aiming line to be the target critter and (b) shoot at this chosen target critter by attaching to the bullet a cForceObjectSeek force that seeks this particular target.

cCritterBullet* cCritterDefender3DPlayer::shoot() 
    cCritterBullet *pbullet = cCritterArmed::shoot(); 
    cCritter* paimtarget = 
        aimvector()), this); 
        // Find critter closest to your aiming line but ahead 
        // of you ("this"). 
    pbullet->addForce(new cForceObjectSeek(paimtarget, 20.0)); 
    return pbullet; 

Another significant override of a player method is the collide. We want the player to view the coin-shaped cCritterDefender3DPropFrag as health food.

BOOL cCritterDefender3DPlayer::collide(cCritter *pcritter) 
    BOOL collideflag = cCritter::collide(pcritter); 
    if (collideflag && pcritter-> 
        setHealth(health() + 1); 
    return collideflag; 

In order to make sure that the player collides with these frags as the caller, but to avoid having to compute a prohibitively n-squared-type number of frag collisions, we override the cCritterDefender3DPropFrag::collidesWith so as to have frags only bother colliding with the player.

int cCritterDefender3DPropFrag::collidesWith(cCritter *pcritterother) 
    if (pcritterother == pplayer()) 
        return cCollider::COLLIDEASARG; //so Player can eat them. 
    else //to keep the speed up, don't do other collisions. 
        return cCollider::DONTCOLLIDE; 


We want our props to be thick, tumbling polygonal prisms that fall towards the viewer. So that they can tumble, we unlock the attitude from the motion and set a spin. So that they move towards the viewer, we give them a gravitational force in the z direction. Here are some relevant lines from the cGameDefender3DProp constructor.

setAttitudeToMotionLock(FALSE); /* don't turn your sprite with the 
    motions, instead let it tumble. */ 
randomizeSpin(1.0, 5.0); 
addForce(new cForceGravity(30.0, cVector::ZAXIS)); 

Note that cForceGravity lets you specify the direction of the gravitational pull in the second argument.

Another issue with the cGameDefender3DProp is to start them out at the far end of the world, that is, at a location near the 'low corner' with the minimum z value. The constructor uses this line to pick a position in the far 20% of the _movebox. Note that since we fed the owner game in as an argument to the constructor, the critter's _movebox has already been set to match the game's _border.

    _movebox.hicorner() ? (1.0 ? 0.2)* 

Whenever a cGameDefender3DProp hits the closest wall of the world, we want to (a) penalize the player, and (b) kill off this prop critter. To accomplish this, we override the cGameDefender3DProp::update to check the condition (_outcode & BOX_HIZ), using a bitwise AND to check if the flag is set. Alternately, we could directly look at the position().z() value. If the condition holds, we have the prop critter penalize the player and disappear (see also Exercise 3.10.5).

void cCritterDefender3DProp::update(CPopView *pactiveview) 
    cCritter::update(pactiveview); //Always call this first 
    if (_outcode & BOX_HIZ) /* use bitwise AND to check if a flag is 
        set. */ 
        pplayer()->damage(1); //punish the player 
        delete_me(); //tell the game to remove yourself 

Finally there is the matter of having the prop burst into a shower of coins when you shoot it. We do this by overriding its die method, which carries out some exciting frills instead of immediately calling delete_me.

void cCritterDefender3DProp::die() 
//Make some new tumbling fragment critters. 
    for (int i=0; i< cCritterDefender3DProp::FRAGCOUNT; i++) 
        new cCritterDefender3DPropFrag(this); //custom constructor 
//Change the prop sprite 
    _age = 0.0; //Use age for a time of the "dying act" 
    setShield(TRUE); //so you don't die again. 
    setAttitudeToMotionLock(FALSE); /* In case this happened to be 
        on, turn it off here so you're free to set the attitude as 
        you like. */ 
    setSpin(0.0); //Stop tumbling 
    setVelocity(cVector::ZEROVECTOR); //Stop moving 
    clearForcearray(); //Stop falling 
    psprite()->setEdged(TRUE); //show as skeleton 

If you look at Exercise 15.1, you'll find that we have an alternate method of showing the prop damage by using a cSpriteLoop animation of an explosion.

To finish off, let's print the cCritterDefender3DPropFrag constructor.

    if (pcritterprop->pgame()) 
        pcritterprop->pgame()->add(this); //Sets moveBox. 
    setSprite(new cSpriteCircle()); 
    setMaxspeed(10.0); //Very fast 
    randomizeVelocity(); //3D velocity 
        //don't turn your sprite with the motions. 
    randomizeSpin(1.0, 5.0); //Tumble 
    setShield(TRUE); // So you don't get shot. 
    setUseFixedLifetime(TRUE); //So you die off pretty quickly 
        //3 seconds 
    addForce(new cForceGravity(100.0, 
        //Fall visually "down" but also towards the viewer. 
    addForce(new cForceDrag(0.1)); 

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