Invader Game with Repeat Fire
For those of you pursuing the repeat or automatic-fire option in the Invader game, here are the promised notes describing one possible solution -- not necessarily the best. In the interest of independent discovery, I'll leave a few details sketchy. By now, you should be able to figure them out for yourself.
I. Architecture
The overall structure of this project remains the same as the starter version: the main movie file lab02_Multi-Shot.fla contains three MovieClips, bullet_mc, shooter_mc, and invader_mc, linked to class files called bulletClass, shooterClass, and invaderClass.
There is, additionally, a document class linked to the main movie, called invaderDocClass. As you'll see, this change is essential to the repeat-fire effect.
There are no changes to the Bullet class. There are modifications to shooterClass.as and invaderClass.as. All the code in the document class is new, of course. We'll start with this material.
II. Document class
In addition to instructions needed to instantiate the invaders and set up a score variable and score display (which we won't cover here), there are two important new custom methods in the document class:
public function fireBullet():void
{
theBullet = new bulletClass();
theBullet.x = MovieClip(root).shooter.x;
theBullet.y = MovieClip(root).shooter.y;
theBullet.name = "b";
addChild(theBullet);
}
public function removeBullet(which):void
{
bulletToRemove.removeEventListener(Event.ENTER_FRAME, which.bulletFly);
removeChild(which);
}
The first of these methods handles firing a bullet, while the second takes care of the other end of the business, removing the bullet once it has either hit an Invader, or gone offscreen.
Note that we make a new intance of bulletClass (and thus bullet_mc) every time a firing event is detected. This is the only way to handle repeat or automatic fire, since it's the only way to work with an unlimited number of projectiles.
Also notice that we give each bullet an instance name (b). It may seem confusing that each of our bullets has the same name, but as you'll see, we don't use this name to tell one instance from another. However, we do use the common instance name to discriminate bullets from other objects in the system. If this seems mysterious right now, read on.
Concerning the addChild() method, you'll remember that we've developed a certain caution, since adding an unlimited number of children to the Display List can cause memory drains. However, we've arranged things so that every bullet created is ultimately destroyed, so even if we have a very large number of bullets in action, their population remains limited.
The limiting factor becomes evident in the second method, removeBullet(). This method in turn calls two standard methods of MovieClip, removeChild() and removeEventListener(). Note that the latter method is called not on the main movie, but on the bullet instance that is being removed. That's because the listener we're erasing has been defined in the class code for the bullet.
It's essential that we remove this listener. If we don't -- and prepare to be amazed -- the listener continues to function even after its associated object is removed!
To understand how this works, remember: removing something from the Display List simply makes it no longer visible on the screen. It does not remove it from the FlashPlayer's memory. Fortunately, we won't create enough Bullet instances to cause trouble.
As for removeChild(), this is more straightforward. We call it from the root (the document class) because that's where all our bullets are instantiated, in the fireBullet() method.
With these two new methods in place, we only need to discover how they are connected to the more familiar architecture of the project. Let's start with the Shooter class.
III. shooterClass
There is only one addition to the standard code: when a firing key event is detected, we execute the following instruction:
MovieClip(root).fireBullet();
As you can see, this statement calls the fireBullet() method of the document class (i.e., the root). You may remember that in the original, simpler version of this project the firing action on Shooter communicated with the Bullet class directly. Now, of course, we're making bullets up as we go.
IV. invaderClass
This new method of making and removing bullets has consequences for the Invader object, which (in our scheme) needs to hit test against the bullet. We can no longer use the original strategy, in which every Invader constantly tested for collision with a single Bullet. That won't work when we have multiple bullets.
The new strategy involves the Display List, which is a handy inventory of all visible objects in our project at any given time. (ActionScript 2 lacked such a feature, requiring us to build equivalents and work-arounds. The Display List is one of the few clear and obvious benefits of the new ActionScript.)
Here's the new hit-test component:
if(!beenHit)
{
var child:DisplayObject;
for (var i:uint=0; i < MovieClip(root).numChildren; i++)
{
child = MovieClip(root).getChildAt(i);
if(this.hitTestObject(child) && child.name.charAt(0) == "b")
{
gotoAndPlay(10);
MovieClip(root).removeBullet(child);
MovieClip(root).theScore++;
beenHit = true;
}
}
Everything that goes on here is prefaced by a check on a Boolean flag variable called beenHit, which is set to false when it is declared, and is re-set to false in the reSet() method (not discussed here).
If we don't add this flag test, it is possible for one or more Bullets to collide with an Invader before it resets. This will increase the player's score by more than one point for every hit. If that's what you prefer, then remove the flag and its test.
Assuming the flag is false, we start a for loop that reviews the contents of the Display List, whose length is registered in the system variable numChildren. We have to call this information from the root, of course, since that's where our bullets are instantiated.
On each pass, we assign the object in the list to an internal variable called child (declared and typed at the top of this class). Then, using this alias variable essentially to save some typing, we hit test the current object (one of our Invaders) against the object assigned to child, provided that object has the instance name b, which is the name assigned to all our Bullets.
You may ask why we add this second condition, involving the instance name. Doing so prevents us from deriving a positive hit test against any non-Bullet object in our system, specifically the Shooter, which is also in the Display List. Remember that Bullets originally appear on the stage at a point that overlaps the Shooter. Hence they would hit test positively if we didn't limit the test to things named b. Also, remember that the Invader itself is in the Display List, but fortunately, neither it nor the Shooter are named b.
If we have a positive result from the hit test -- again, versus any of the Bullets currently in play -- we execute a frame transition to begin the Invader's blow-up animation. Then we call back to the root's removeBullet() method, passing child as a parameter. This tells the document class (or root) to remove the specific Bullet with which we have collided. We must do this at the root level, because an object can only be removed from the Display List by its parent, the object that created it.
We also increment the score, and set our beenHit flag to true, which prevents any further hit testing until we've reset the Invader offscreen.
V. Source files
Source files for this version of the Invader project are available in the lab02 directory, in a subdirectory called autofire.
|
|