Next, we are going to hit test the enemy spiders with the player. You might think that we would add yet another function to the ones being called by the enter frame handler. However, instead of doing that, we can just make use of the existing enemiesHitTest function, especially since that function is already looping through the spiderArray in its outer loop. We can just throw a hit test in there to see if any of the enemies is hitting the player:
function enemiesHitTest():void {
//for each of the three spiders
for(var i:int = 0; i < 3; i++) {
if(spiderArray[i].hitTestObject(player)) {
//do something
}
//the each of the six bullets
for(var j:int = 0; j < 6; j++) {
//don't consider bullets that aren't in play:
if(bulletArray[j].y > player.y) continue;
if(spiderArray[i].hitTestObject(bulletArray[j])) {
recycleEnemy(spiderArray[i]);
bulletArray[j].x = j * 70 + 100;
bulletArray[j].y = 595;
}
}
}
}
For now, I've just written a comment "//do something" in this spot. Because we really need to think about just what we're going to do if the player gets hit.
When the player gets hit, we want to remove it from the screen somehow, at least temporarily. We need to also introduce the concept of "lives" and keep track of how many the player has left. The first part is easy, let's introduce this new variable near the top of the code in the variables list:
var lives:int = 5;
So the first thing that should happen when the player gets hit is that this lives variable gets decreased by 1:
lives--;
Place this line right underneath the "//do something" comment in the enemiesHitTest function. In the following lines, we need to do a comparison using this new value of lives:
lives--;
if(lives > 0) {
changePlayer();
} else {
gameOver();
}
The logic here is sound, even though we haven't yet written the changePlayer or gameOver functions. We'll do that next. The idea though, should be obvious. If the player has lives remaining, the player needs to be changed out, and if not, the game is over. We'll take on each of these functions in turn.
If the player has been hit, it needs to be removed from the screen somehow to prevent it from registering another hit. This requires some thought as to a strategy for just how to do this. The solution I came up with, in the same spirit of recycling, is to temporarily move the player off the screen. This will prevent it from getting hit by any more enemies, even though they keep on coming down uninterruped. Then, what we need is a delay of about two seconds before the player comes back. We can create such a delay using the Timer class:
function changePlayer():void {
player.x = stage.stageWidth + 100;
stage.removeEventListener(KeyboardEvent.KEY_DOWN, stage_onKeyDown);
stage.removeEventListener(KeyboardEvent.KEY_UP, stage_onKeyUp);
rightArrow = false;
leftArrow = false;
//create a two second delay:
var timer:Timer = new Timer(2000, 1);
timer.addEventListener(TimerEvent.TIMER, afterDelay);
timer.start();
}
The first line of this function moves the player to the right, and off the screen. The next couple of lines remove the event listeners for the Keyboard, so that the user (temporarily) will be unable to just move the player back again. The next two lines make absolutely certain of this by setting rightArrow and leftArrow both to false, in case there might have been a key down already when the player was hit.
Next, a Timer is instantiated to create a two second delay (2000 milliseconds). The "1" there is feeding the Timer class's "repeatCount" parameter. So the "1" means to only let this timer fire one time. The next line adds an event listener for the TimerEvent.TIMER event, and names the function that should run when this happens. Again, we haven't written the "afterDelay" function yet, but we will. The final line starts the timer.
Now let's write the afterDelay function:
function afterDelay(event:TimerEvent):void {
player.x = stage.stageWidth / 2;
for(var i:int = 0; i < 3; i++) {
if(spiderArray[i].y > 400) {
recycleEnemy(spiderArray[i]);
}
}
stage.addEventListener(KeyboardEvent.KEY_DOWN, stage_onKeyDown);
stage.addEventListener(keyboardEvent.KEY_UP, stage_onKeyUp);
}
Remember, the above function will fire after a two second delay. The first line places the player back at the center of the screen. Next, a for loop is run on the spiderArray. If any of these enemies have a y value greater than 400, then they are recycled. This prevents any possible immediate crashes that might not be fair to the player, because the enemies have not ceased coming down from the top. The final two lines return the keyboard controls back to the user. Note that in all of this it was not necessary to remove the event listener for ENTER_FRAME. If the keyboard listeners alone are removed, it is enough the assure that the rightArrow, leftArrow, and spaceBar variables cannot possibly get a value of true during this time.
Now it only remains to write the gameOver function. This, however, takes us into some further important decision making. Specifically, we need to decide what actually happens if the game is over, and also probably provide some mechanism for a "play again" option. And if the user decides to play again, we need to somehow make sure the game starts over afresh. At this point, the easiest way to do this is going to be to introduce a three-frame navigation system. All of the stuff we have been doing on frame 1 will be moved to frame 2. The newly created frame 1 will display a title, and ask the user to "click anywhere to begin" (this also solves the issue of getting the user to click on the swf once initially so that it can be given keyboard focus on a web page). Then we'll add a frame 3 that will ask the player if they want to play again, and provide a "play again" button that they can click. Clicking this button will transport the user back to frame 2 again. Revisiting a frame on the timeline causes the whole thing to reset itself from the beginning (player, enemies, lives, bullets, everything!), which is exactly what we need.
I've gone ahead and imposed this three frame navigation system on this page's download file. Arranging your timeline this way is not too very difficult, you just need to have had some experience creating frames and keyframes. What would be difficult (and probably unnecessary) would be explaining every detail of this process. If you've worked with Flash very much, you probably already know how to do it. If not, just study the download file and observe how its timeline is set up.
Returning to the game code which is now on frame 2, we have yet to add the gameOver function. If the game is truly over, we will issue the command to gotoAndStop(3). But when we go to frame 3, we have to make sure that all the listeners we added to the stage are turned off. Otherwise, those listeners remain in effect. The ENTER_FRAME event, especially, is calling a bunch of other functions that refer to objects on the screen. However, on frame 3, those other objects aren't going to be there anymore, so we'll get all kinds of errors unless we remove this event listener:
function gameOver():void {
stage.removeEventListener(Event.ENTER_FRAME, stage_onEnterFrame);
stage.removeEventListener(KeyboardEvent.KEY_DOWN, stage_onKeyDown);
stage.removeEventListener(KeyboardEvent.KEY_UP, stage_onKeyUp);
gotoAndStop(3);
}
There remains yet one other small detail, which I discovered while testing this game (in other words, I got a runtime error). At the point in the function enemiesHitTest, where the gameOver() function is called, the processing is in the middle of a loop (the outer one). If we don't break out of this loop, the inner loop will be processed. When we are on the same frame and just simply changing out the player with the two second delay, this poses no problem. But when the game is over, the gameOver function tells the timeline to gotoAndStop(3), which it does. But the function we are in also continues to process, and it refers to bullets that are no longer there, and errors occur. The solution is to just place the break; command immediately following the call to gameOver() in that function.
Here's all of our code so far (now on frame 2, code on frames 1 and 3 not shown):
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
var rightArrow:Boolean;
var leftArrow:Boolean;
var spaceBar:Boolean;
var playerHalfWidth:Number = player.width / 2;
var bulletArray:Array = new Array(bullet0, bullet1, bullet2, bullet3, bullet4, bullet5);
var spiderArray:Array = new Array(spider0, spider1, spider2);
var bulletIndex:int = -1;
var lives:int = 5;
for(var i:int = 0; i < 3; i++){
recycleEnemy(spiderArray[i]);
}
stage.addEventListener(KeyboardEvent.KEY_DOWN, stage_onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, stage_onKeyUp);
stage.addEventListener(Event.ENTER_FRAME, stage_onEnterFrame);
function stage_onKeyDown(event:KeyboardEvent):void {
if(event.keyCode == 39) {
rightArrow = true;
}
if(event.keyCode == 37) {
leftArrow = true;
}
if(event.keyCode == 32 && spaceBar == false) {
spaceBar = true;
bulletIndex++;
if(bulletIndex > 5) bulletIndex = 0;
var bullet:Bullet = bulletArray[bulletIndex];
bullet.x = player.x;
bullet.y = player.y - 1;
}
}
function stage_onKeyUp(event:KeyboardEvent):void {
if(event.keyCode == 39) {
rightArrow = false;
}
if(event.keyCode == 37) {
leftArrow = false;
}
if(event.keyCode == 32) {
spaceBar = false;
}
}
function stage_onEnterFrame(event:Event):void {
controlPlayer();
moveBullets();
moveEnemies();
enemiesHitTest();
}
function controlPlayer():void {
if(rightArrow == true) {
player.x += 10;
if(player.x > stage.stageWidth - playerHalfWidth) {
player.x = stage.stageWidth - playerHalfWidth;
}
}
if(leftArrow == true) {
player.x -= 10;
if(player.x < playerHalfWidth) {
player.x = playerHalfWidth;
}
}
}
function moveBullets():void {
//cycle through all six bullets in the bullet array
//using a FOR LOOP:
for(var i:int = 0; i < 6; i++) {
//if the bullet is above the player, move it up 10 pixels every frame:
if(bulletArray[i].y < player.y) {
bulletArray[i].y -= 10;
//if the bullet has gone off the top of the screen,
//put it back at the player's feet again:
if(bulletArray[i].y < 0) {
bulletArray[i].x = i * 70 + 100;
bulletArray[i].y = 595;
}
}
}
}
function moveEnemies():void {
for(var i:int = 0; i < 3; i++) {
spiderArray[i].y += 3;
if(spiderArray[i].y > (stage.stageHeight + 25)) {
recycleEnemy(spiderArray[i]);
}
}
}
function enemiesHitTest():void {
//for each of the three spiders
for(var i:int = 0; i < 3; i++) {
if(spiderArray[i].hitTestObject(player)) {
//do something
lives--;
if(lives > 0) {
changePlayer();
} else {
gameOver();
break;
}
}
//the each of the six bullets
for(var j:int = 0; j < 6; j++) {
//don't consider bullets that aren't in play:
if(bulletArray[j].y > player.y) continue;
if(spiderArray[i].hitTestObject(bulletArray[j])) {
recycleEnemy(spiderArray[i]);
bulletArray[j].x = j * 70 + 100;
bulletArray[j].y = 595;
}
}
}
}
function recycleEnemy(enemy:MovieClip):void {
enemy.x = 25 + Math.random() * (stage.stageWidth - 50);
enemy.y = -500 * Math.random() - 25;
}
function changePlayer():void {
player.x = stage.stageWidth + 100;
stage.removeEventListener(KeyboardEvent.KEY_DOWN, stage_onKeyDown);
stage.removeEventListener(KeyboardEvent.KEY_UP, stage_onKeyUp);
rightArrow = false;
leftArrow = false;
//create a two second delay:
var timer:Timer = new Timer(2000, 1);
timer.addEventListener(TimerEvent.TIMER, afterDelay);
timer.start();
}
function afterDelay(event:TimerEvent):void {
player.x = stage.stageWidth / 2;
for(var i:int = 0; i < 3; i++){
if(spiderArray[i].y > 400) {
recycleEnemy(spiderArray[i]);
}
}
stage.addEventListener(KeyboardEvent.KEY_DOWN, stage_onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, stage_onKeyUp);
}
function gameOver():void {
stage.removeEventListener(Event.ENTER_FRAME, stage_onEnterFrame);
stage.removeEventListener(KeyboardEvent.KEY_DOWN, stage_onKeyDown);
stage.removeEventListener(KeyboardEvent.KEY_UP, stage_onKeyUp);
gotoAndStop(3);
}
And here's how the swf behaves:
Many improvements have been made in this version. But even though we've got 5 lives for the player, there is no visual indication of that. Also we need to add some kind of scoring system, and also some kind of indication as to when a "round" is up. It would be better, too, and more fun if the game got progressively more difficult somehow. We'll tackle these issues on the next page (and the page after that!)...