Next, we'll add a visual indication of how many lives the player has left. We could just put a number in a textbox, but we'll do it in a little bit more classy way. Let's turn our attention to the timeline, on frame 2 where the gameplay takes place. Right click the player layer and choose "lock others." Make sure that frame 2 is selected. Drag out another instance of the Baby (player) symbol from the library, and place it in the upper right hand corner. Placing it perfectly at this point is not necessary. Make sure it's selected. In the properties panel, give it a width of 28, and a height of 27.1.
Next, duplicate this instance by doing the following: Hold down the Alt key. Click and hold down the left mouse button while dragging out and away from the symbol instance. Holding down the Alt key causes you to drag out a copy. The copy will have the same resizing already applied to it as the original. Do this two more times, so that you wind up with 4 shrunken Baby instances. These will make up our "remaining lives" indicator. Give them instance names in the properties panel of "one", "two", "three", and "four". Their actual instance names aren't really important, we just need to name them something so that we can place them in an array.
Arrange them on the screen so that they are in a line along the top right corner. Ideally, they should be arranged so that their instance names should be in reverse order (four, three, two, one) from left to right. Add this line of code to the actions panel on frame 2. Place this line right after the other lines that declare arrays for the spiders and bullets:
var livesArray:Array = new Array(one, two, three, four);
Next, we need to find the place in the code where we lose a life. This happens in the enemiesHitTest function, where the player is hit, but the game is not over. It's where we call the changePlayer function. We'll make one of these "lives" disappear at this point, based on the value of our lives variable:
livesArray[lives - 1].visible = false;
So place this line right after the line that calls changePlayer(). Why lives - 1? Well, arrays are zero based. Just to make sure, though, let's think this through. The first time we lose one life, the lives variable is decremented from 5 to 4. The length of our livesArray, meanwhile, is 4. The highest index of the livesArray, therefore, is 3 (livesArray will have indices of 0, 1, 2, and 3 for a total of 4 elements). So subtracting 1 from the lives variable will make this work properly. Four minus one is three, so we will be telling livesArray[3] to turn invisible. The next time this code runs, the next lowest element will turn invisible, and so on.
So now we have our lives indicator working great! The next thing to take on is the scorekeeping.
We'll do this work on the player layer also. Create a static textfield and place the text "Score:" in it. Next, create a dynamic textfield and place the text "0" in it. Make sure this textbox is not selectable, and that the text is left aligned. Arrange these two textfields in the upper left hand corner of the screen. Give the dynamic one an instance name of score_txt in the properties panel. Also, in the properties panel, click the Embed button and embed numerals for this textfield.
In the code, let's make a new variable for the score:
var score:int = 0;
We'll also write a general purpose function for updating and displaying the score. We'll call it updateScore. This function will accept a parameter that's an integer, which will be the amount that we add or subtract from the score:
function updateScore(amount:int):void {
score += amount;
if(score < 0) score = 0;
score_txt.text = String(score);
}
You can just place this function at the very end of all the code. It really doesn't matter in what order it appears, though. We can call this function with a number, and it will add that number to the score. If we call this function with a negative number, it will also "add" it to the score. Adding a negative number has the effect of subtracting, naturally, so here we have a function that we can use to both add a bonus or subtract a penalty, depending on how we call it. Since we can exact penalties, the second line makes sure that the score is never a negative number. It's probably not good to have the possibility of the player "owing" points. The last line makes sure that every time the score variable is manipulated, the textbox also has its value updated, and the last part of this line makes sure the score variable gets converted to a string for display in the textfield.
Now we just need to find those places in the code where we would like to award or take away points. This is pretty easy now. When a bullet hits a spider, we'll award 10 points. This happens in the enemiesHitTest function, in the inner loop where the spiders are hit tested with the bullets. If a bullet hits a spider, it gets reclycled, remember? Just before the recycleEnemy line, then, add this line:
updateScore(10);
Now, let's suppose that we want to apply a 5 point penalty for spiders that get past the bottom of the screen. Find the place in the code where that happens. It's in the moveEnemies function. Once again, place the line of code just before the recycleEnemy line:
updateScore(-5);
There is one other thing to consider with this penalty, though. What about spiders that may be passing the bottom of the screen in that two second interval in which a life has been lost and we are changing players? It wouldn't be fair to exact a penalty in that case. So let's fix it. Create another boolean variable called betweenLives:
var betweenLives:Boolean;
Next, let's set the betweenLives variable to true as a new first line of the changePlayer function (insert this new line at the beginning of the function):
betweenLives = true;
Next, let's set this same variable to false again as a new first line of the afterDelay function (once again, insert it as a new line at the beginning of the function:
betweenLives = false;
Therefore, this variable will only be true during the two seconds that our game is between lives. Next, let's change the line that exacts the 5 point penalty (this is found in the moveEnemies function):
if(betweenLives == false) updateScore(-5);
Problem solved! See how you can create and use boolean variables any time you want to keep track of something?
I ran across one other problem in my testing. When the user clicks the "playAgain" button, it seems the focus necessary for the keyboard controls goes away. To fix it, I added this one line of code to frame 2:
stage.focus = this;
This line of code makes sure that the stage's focus is set to the game itself, and not one of its parts. Without this, you have to click on the application again to get the keyboard working again. With this line in place, though, it works like it ought to without soliciting another click from the user. Yay!
Anyway, here's a listing of all the code so far:
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 betweenLives: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 livesArray:Array = new Array(one, two, three, four);
var bulletIndex:int = -1;
var lives:int = 5;
var score:int = 0;
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);
stage.focus = this;
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)) {
if(betweenLives == false) updateScore(-5);
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();
livesArray[lives - 1].visible = false;
} 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])) {
updateScore(10);
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 {
betweenLives = true;
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 {
betweenLives = false;
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);
}
function updateScore(amount:int):void {
score += amount;
if(score < 0) score = 0;
score_txt.text = String(score);
}
The code listing is getting kind of long, but the recent changes in the above are these:
And of course, there is the new updateScore() function at the very end. Although the code listing is long, it's fairly well organized, so that it's easy to find your way around in it and remember what does what, so that you can easily make changes and refinements.
And here's the swf, with the lives and scoring:
On the next page, we'll consider how we might make the game progressively more difficult, and make some more refinements and improvements.