13.3 The cCritterBulletAs mentioned in the last section, when a cCritterArmed fires a shot, its shoot method is called, and a new bullet gets initialized in two stages. First the bullet's no-argument constructor cBullet() is called. The constructor does the following.
In the second stage of a bullet's initialization, the cCritterBullet:: initialize(cCritterArmed *pshooter) is called with the shooting critter as the pshooter argument. The reference to pshooter allows the bullet to set its target according to the preferences of the pshooter . The default cCritterBullet constructor gives the bullet a yellow isosceles triangle for its sprite. The default cBullet::initialize method does the following.
The cBullet::update method is written to call the base class cCritter::update and to kill off the bullet if it has touched an edge of the world and its _dieatedges flag is on. The way that update can tell if a critter has touched the edges of the world (wrapped or bounced from the edge on its last move) is to look in its _outcode , which is set by the cCritter::move method to be non-zero if the critter touches the border. The cBullet::update code looks like this. void cCritterBullet::update(CPopView *pactiveview) { cCritter::update(pactiveview); /* Feels force, also checks _age against _lifetime. */ if (_outcode && _dieatedges)/* _outcode nonzero means near an edge. This keeps bullets from bouncing or wrapping, but it also makes the critters unable to fire when they are really near an edge. */ { delete_me(); return; } } The _dieatedges flag is TRUE by default for the cCritterBullet and the cCritterBulletSilver . The reason for having it normally be TRUE is that the game tends to look confusing if too many bullets wrap or bounce, so we normally kill them whenever they hit the edge. But you can optionally turn this behavior off by setting the _dieatedges flag to FALSE . The cCritterBulletRubber constructor and the cCritterBulletSilverMissile constructors both set _dieatedges to FALSE , in the first case because it's fun to watch the rubber bullets bounce around, and in the second case because we want to make the silver missiles particularly lethal and hard to escape. Also remember that since a bullet has its _usefixedlifetime flag on, the base cCritter::update call will kill off the bullet if it's older than the age _fixedlifetime , which is normally going to be three seconds. The most characteristic part of a bullet's behavior is to damage things, and this code is in the cCritterBullet override of the cCritter::collide(cCritter *pcritter) method. We don't put the damage-other-critters-when-you-touch-them code into the cCritter::update because bullets hitting things is about the interactions between pairs of critters. It makes more sense “ and is more time-efficient “ to handle this inside the collide method which is already in place to look at each pair of critters that you think you might be interested in. The bullet collide code goes like this. BOOL cCritterBullet::collide(cCritter *pcritter) { if (isTarget(pcritter)) //If you hit a target, damage it and die. { if (!touch(pcritter)) return FALSE; int hitscore = pcritter->damage(_hitstrength); delete_me(); /* Make a service request, but you won't go away yet. */ if (_pshooter) //Possible that _pshooter has died, is NULL. _pshooter->addScore(hitscore); return TRUE; } else //Bounce off other critters in a normal fashion. return cCritter::collide(pcritter); /* Bounce off non-target critters */ } Given that the bullet's collision behavior is more complicated than a standard critter's collision behavior, the Pop Framework gives a bullet priority in calling the collide method. As mentioned above, we do this by setting the bullet's _collidepriority to a higher value than ordinary critters have. In addition, we override the int cCritterBullet::collidesWith(cCritter *pcritter) method to return cCollider::DONTCOLLIDE if pcritter is (a) the bullet's shooter or (b) another bullet from the same shooter. Condition (a) is fairly obvious. We need (b) because sometimes if you are moving in the direction that you're shooting, your bullets may be overlapping each other, and you don't want them to destroy or to bounce off of each other. You can check Chapter 11: Collisions to review the details about how collidesWith is used for adding collision pairs to the game's cCollider list of collision pairs. The BOOL cBullet::isTarget(cCritter *pcritter) tells whether a given pcritter is something that the bullet wants to damage. The default isTarget method returns TRUE for every pcritter except for critters of the type cCritterWall . The cCritterBulletSilver overrides the isTarget method to only target one particular critter. What makes these bullets 'silver' is that they are targeted for one thing and one thing only, in analogy to the silver bullets of legend with which one is supposed to be able to shoot a werewolf. For these guys, the isTarget method is simplicity itself. BOOL cCritterBulletSilver::isTarget(cCritter* pcritter) { return pcritter == _ptarget; } An example of a use of cCritterBulletSilver occurs in the Spacewar game, in which the enemy UFO critters are shooting silver bullets at the player. We want the player to be able to fend off these bullets by shooting at them. You might think the player's bullets would blow the silver bullets up in any case, as the default cCritterBullet::isTarget would return TRUE for these bullets. But remember that when we have a pair (pa, pb) we only call pa->collide(pb) or pb->collide(pa) , but not both. If pa and pb are both bullets; we might be unsure about which one gets to control the collide; indeed this could simply depend on where they happen to be listed in the _pbiota array, which in turn depends on exactly when you pressed the spacebar to shoot your bullet relative to when the UFO shot its silver bullet. Seemingly it could happen that when your bullet hits a silver bullet, it is the silver bullet's collide method which is in control. In order to avoid this bad state of affairs, we have the cCritterBulletSilver constructor set the silver bullet's _collidepriority to a value cCollider::CP_ SILVERBULLET which is slightly less than cCollider::CP_BULLET . This means that only correctly ordered bullet-to-silver-bullet collision pairs will be added to the game's collider list. |