Yes! You can definitely MAKE THIS YOURSELF! Step by step instructions included!
(Want to save some time? Download the above ZIP file and you will get all 10 of the individual fla files from the succeeding web pages. Just for your convenience. Download this now, and you're done!).
This tutorial will show you how to make your own Flash AS3 Left-Right Shooter game! Oh, you gathered that from the title? Okay, so I get redundant sometimes (I looked up redundant in the dictionary, and what do you know? It didn't have any definition, but it was listed twice)!
Note: the files included here for download are in Flash CS4 format. They also include the Cooper Black font. If your system doesn't have Cooper Black, you will get a different font when you compile (test movie). That's just the way it is, but don't be alarmed if your version doesn't look exactly like mine. In fact, go ahead and embed a font from your computer. I won't mind. You do this using the embed button in the properties panel when a certain textfield is selected.
Note: Static textboxes are always embedded. But they must be embedded from a source, and that's important. They ultimately come from the computer of the person doing the compiling. So you will want to change out the static textboxes too, but you won't have to embed any fonts for those. Confusing? You betcha! But this tutorial won't be, I promise.
Download the starter file. Some of the setup has already been done for you. The screen has been sized to 550 x 600. There are already a few MovieClip symbols in the library. There is a Player symbol, a Spider symbol (the enemy), and a Bullet symbol (the projectile our player will shoot). There is an instance of the Player symbol on the stage, and we've given it an instance name of "player" (is the player a boy or a girl? That's up to you, I'm going to stubbornly say it rather than "he or she").
Our goal is to make this player instance move side to side when we press the right and left arrow keys. The first thing to know is that our stage and main movie have a coordinate system that you must get used to. The good news is that it's really easy and intuitive. Every location on the screen has an x and y number associated with it. The upper left corner can be described as x = 0 and y = 0. The x number tells you how far to the right something is from the left edge of the screen. They y number tells you how far down something is from the top of the screen. And that's all you need to know. When an object (like our player, for example) moves left and right on the screen, it is increasing or decreasing its x property. And likewise for the y direction, which is the up and down direction. As things get further down the screen, their y value increases:
You can move an object on the screen just by issuing commands that change its x and/or y properties. For example, issuing the command player.x = 500 will cause it to move that x location. However, if I were to put a command like that in my code, the player would move to the x location immediately when the program runs, and I would never get to see it happen. The real magic happens when you issue commands like that in conjunction with Flash's event system, as we will soon see.
The above command was what you might call an absolute command. That is, I told the player to change its x property to 500. When I did, it moved there. This command basically told it "I don't care what your x location is right now. Change it to 500." There is another way to get the player to move, and it does take into consideration the current value of x. It's called a relative command, and it looks something like this: player.x = player.x + 10; This is saying "Take the current value of your x property and add 10 to it." This kind of command can also be written like this: player.x += 10;. This shorter form is a little bit less easy to read until you get used to it. But it does exactly the same thing. We say the x value is incremented (don't worry, it just means "increased" or "added to").
Once again, though, if I issue the command player.x += 10; at the beginning of my program, it will happen too fast to see. So let's begin combining commands like these with some events.
First, just for an experimental drill, we'll make the player move to whatever screen location we click on with the mouse. So enter this small block of code into your actions panel on frame 1:
import flash.events.MouseEvent;
stage.addEventListener(MouseEvent.CLICK, stage_onClick);
function stage_onClick(event:MouseEvent):void {
player.x = mouseX;
player.y = mouseY;
}Test the movie. Click the stage with the mouse. Wherever you click, the player will go to the mouse pointer, because the mouseX and mouseY properties of the main timeline keep track of the x and y locations of the mouse pointer. Pretty cool, eh? This is using that absolute type of command I told you about.
The above is a working swf. This is fairly cool, but ultimately not what we want to do. I only showed you this to give you an idea of the coordinate system, and to demonstrate how commands to move the player can be combined with an event. Moving the player is just as easy as setting its x and y properties to some new value! But it needs to be combined with some kind of event in order for the action to be seen! On the next page, we'll begin learning how to move the player with the keyboard instead.
(Note: there does exist a drill 2 file with the wrong way illustrated. I decided I need not upload it (it's in the ZIP file though). You won't need it. So now my numbering is all off by one: drill_3 on page 2, etc.... Doh! Oh, well...)
There is a general principle to using flash events that I want you to keep in mind: to get continuous action, you need to use a continuous event. In Flash's event system, there are only a couple of these continuous events available, and the most important one is the ENTER_FRAME event, and that's the one we'll use. I'll have lots more to say about it soon.
But first, there is a little mistake that practically everyone makes when they first begin programming keyboard input for a game. That mistake is to combine commands to move your player with the events that get keyboard input. It's a natural mistake, because you would think there would be nothing wrong with this:
import flash.events.KeyboardEvent;
stage.addEventListener(KeyboardEvent.KEY_DOWN, stage_onKeyDown);
function stage_onKeyDown(event:KeyboardEvent):void {
if(event.keyCode == 39) {
player.x = player.x + 10;
}
if(event.keyCode == 37) {
player.x = player.x - 10;
}
}
Note that the above does NOT make use of ENTER_FRAME, which is a big part of its erroneous ways! It's correct that the event listener for the keyboard should be added to the stage, so that part is fine. The other correct parts are the if statements that sort out the keys being pressed. 39 is the code for the right arrow key, and 37 the left arrow key. The part that isn't correct is the idea of directly moving the player in response to those keypress events. The only reason this system works at all is because of something called the keyboard repeat rate.
In normal typing on your keyboard (in text editors, word processors, or just about any program on your computer), if you hold down a key, the keystroke will register once, then after an initial slight lag, the key will begin to repeat itself on and on. So what the above code is actually doing is using this keyboard repeating feature to provide the continuous action needed to move the player. The result is that the player moves once right away, then after an initial lag, the player starts moving. Probably this lag is the most undesirable part, but there is also the issue of not being able to control the speed of the continuous action in any way. It's not one of flash's events. The keyboard repeat rate is actually built in to the operating system! Anyway, here's how this wrong way looks in action:
Click on the above swf one time, then use the left and right arrow keys to move the player. Notice the lag? Isn't it horrible? By tying the keyboard events directly to the movement of the player, there's no way to take out this lag. Quick, let's figure out how to fix it!
Since you can't eliminate the keyboard repeat feature, you just need to find a way to work with it and around it. One answer I have found is to create a Boolean variable for each key you want to keep track of. Boolean variables, as you may know, are variables that can have a value of either true or false, and when you first declare them, they already have an initial (default) value of false. Then, when the user presses an arrow key, you assign the associated Boolean variable a value of true. When the user releases that key, you set the variable to false again.
Because the action of the keypress is only turning a variable to true, a person can hold down that key for as long as they want, because the only effect it's having now is it's setting the variable to true. Repeatedly setting the variable to true doesn't change it after the first time (you can't make it any MORE true, eh?), so we effectively absorb the keyboard repeat rate and tame it! So pressing and releasing a key will do nothing but turn a Boolean variable on and off, like a light switch. Meanwhile, we also add an event listener for ENTER_FRAME that tests to see if the value of this variable is true or not. If it is, move the player. If it isn't, don't:
import flash.events.KeyboardEvent;
import flash.events.Event;
var rightArrow:Boolean;
var leftArrow:Boolean;
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;
}
}
function stage_onKeyUp(event:KeyboardEvent):void {
if(event.keyCode == 39) {
rightArrow = false;
}
if(event.keyCode == 37) {
leftArrow = false;
}
}
function stage_onEnterFrame(event:Event):void {
if(rightArrow == true) {
player.x += 10;
}
if(leftArrow == true) {
player.x -= 10;
}
}And here's how this better version looks in action:
(Note: with keyboard stuff, you always have to click on the swf to give it focus. So you'll need to click on the above just once to get it working with the arrow keys). Now you can move the player back and forth with the right and left arrow keys without that ugly lag. Ahhh, much better!
The code is a bit more verbose, but shouldn't be too hard to understand. The keypresses and releases are just turning variables to true or false, and at the same time, the ENTER_FRAME event is moving the player based on whether those same variables are true or not.
I told you I'd have more to say about the ENTER_FRAME event. Well, here it is. I've always felt that this event was misnamed, but they had to call it something, and it had to be short and concise. But the name makes it sound like an event that fires if Flash's playhead enters a new frame, and that's not at all what it is. What it actually is, is an event that fires at the framerate of the movie, whether the playhead is moving or not. It fires even if there is only one frame in your movie. It's constant, like the ticking of a stopwatch. As soon as you add an event listener for it, your event listener will immediately start being called until you remove it again. In the above example, the ENTER_FRAME handler function is executing constantly. You don't see the effect unless you press one of the keys. But the function is still getting called repeatedly. It's just that unless a key is pressed, the two if statements continually fail.
ENTER_FRAME is something you should consider to be the heartbeat of your program. Sometimes it's called your "game loop." Everything that needs to happen continually needs to be controlled by this event somehow. Moving the player with the keys, moving the enemies down from the top of the screen, moving the bullets in the up direction, checking for collisions between the bullets and the enemies, and checking for collisions between the enemies and the player--all these things need continuous action, so they all need this continuous event to control them somehow. Don't worry if it sounds complicated. We'll keep taking it a step at a time!
The next things to do are to make the player stop at the edges of the screen, and to make it fire bullets! On to the next page!
Next, let's examine how to set boundaries for the Player so it stops at the edges of the stage. We'll create a new variable called playerHalfWidth. The reason for this is that the player is centered on its registration point (the registration point is the little cross-hair, or plus-sign that you see when you double click the player to edit the insides of it. It's also the point where all of its x and y values are reckoned from. The crosshair is x = 0 and y = 0). We need to divide its width by two, and it's handy (and more efficient performance-wise) to already have this division calculation done ahead of time. So we declare the variable and give it a value all in one shot:
var playerHalfWidth:Number = player.width / 2;
I hope you are starting to realize that when we are programming, we can create variables whenever we feel the need for them, and we can give them whatever name that makes sense to us. It's a bit like creative writing, but after you do it for awhile, you will start to just know when you need to create a new variable! Also, by using descriptive names, you make the code self documenting and easier to read by others (and by yourself when you return to it in a month or two).
Now we can use this value to set limits for the player's travel. The very left edge of the screen is x location 0 (we covered that earlier). The left limit then, is going to be 0 plus the player's half width, which when added together equals just the player's half width. If the player's x value gets to be any less than this limit, then we'll set it equal to the limit:
if(player.x < playerHalfWidth) {
player.x = playerHalfWidth;
}
The right limit is going to be the right hand edge of the stage minus this playerHalfWidth value. The right edge of the stage is contained in the stage's stageWidth property. So the code for this part goes like this:
if(player.x > stage.stageWidth - playerHalfWidth) {
player.x = stage.stageWidth - playerHalfWidth;
}
By the way, you will see this same basic pattern in a lot of code: If something exceeds a certain value, make it equal that same value. If something is less than a certain value, make it equal that same value. In this case, it will make the player stop from going off the edge of the screen in either direction. Can you guess where it goes in the code? It will go into the ENTER_FRAME handler function. Find the place in that function where the user is pressing the right arrow key, and test for the right side of the screen. Likewise, insert the code testing for the left side at the place where the user is pressing the left arrow key:
import flash.events.KeyboardEvent;
import flash.events.Event;
var rightArrow:Boolean;
var leftArrow:Boolean;
var playerHalfWidth:Number = player.width / 2;
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;
}
}
function stage_onKeyUp(event:KeyboardEvent):void {
if(event.keyCode == 39) {
rightArrow = false;
}
if(event.keyCode == 37) {
leftArrow = false;
}
}
function stage_onEnterFrame(event:Event):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;
}
}
}
So if the code finds that the x value of the player exceeds a certain value, it makes it equal to that value. And it performs this check continually. I'll say it again: Anything that needs to happen continually needs an event that fires continually, and that's the ENTER_FRAME event. Anyway, here's how our new code works:
(Click once on the swf, then use the arrow keys). Now the player stops at the edges. Cool! On the next page, we'll see how we can make the player fire a bullet when the spacebar is pressed!
The next thing to do is to get the player firing bullets. We'll program this to the spacebar. The spacebar is just like any other keyboard key, of course. And since we want to program another key, let's make another Boolean variable to keep track if it's pressed or not:
var spaceBar:Boolean;
Naturally, this new variable declaration can just go right underneath the other ones. The key code for the spacebar is 32. So, like we did before, we add in the code that tests for this key and turns this Boolean variable on and off. This new code goes in the KEY_DOWN and KEY_UP handlers:
function stage_onKeyDown(event:KeyboardEvent):void {
if(event.keyCode == 39) {
rightArrow = true;
}
if(event.keyCode == 37) {
leftArrow = true;
}
if(event.keyCode == 32) {
spaceBar = true;
}
}
function stage_onKeyUp(event:KeyboardEvent):void {
if(event.keyCode == 39) {
rightArrow = false;
}
if(event.keyCode == 37) {
leftArrow = false;
}
if(event.keyCode == 32) {
spacebar = false;
}
}
When the user presses the spacebar, we want to "fire" a bullet. But first we have to create some bullets to fire. To tell you the truth, I've made the next part a little bit easier than the way you might normally do it. Across the bottom of the screen I've dragged out 6 bullet instances, and aligned them with the bottom of the stage, and I moved the player up a bit. Now, normally you might make all of these bullet instances dynamically using the new keyword, and add them to the stage with addChild(), and do it all using a for loop. But in this case I want you to see exactly what's going on. I've also given them each an instance name in the properties panel. They are named bullet0 through bullet5, and so there are six of them altogether. This naming convention is just to get you used to the zero-based system that arrays use.
These instance names are available to our code, just like any other instance name we assign in the properties panel. So let's store all these instances in an array of their own (we add this code to the top of the code window in the variables list, of course):
var bulletArray:Array = new Array(bullet0, bullet1, bullet2, bullet3, bullet4, bullet5);
The above line declares the array, instantiates it, and adds our bullet instances to it. After this line runs, now we can say the following:
Saying bulletArray[0] is the same as bullet0.
Saying bulletArray[1] is the same as bullet1.
Saying bulletArray[2] is the same as bullet2.
Saying bulletArray[3] is the same as bullet3.
Saying bulletArray[4] is the same as bullet4.
Saying bulletArray[5] is the same as bullet5.
It's as though we've gained a whole 'nother set of instance names, because, for example, anywhere in your code where you might say bullet0 do something, you can also say bulletArray[0] do something! And both "names" refer to the exact same bullet object! So what's the advantage of this new set of names? Well, instead of putting a "hard number" in the brackets (like 0 for example), you can also put a variable in the brackets that will evaluate to a number. This system is easier to process with for loops, which we'll get to shortly. While it's possible to make this game without using loops or arrays, these tools just make it so much easier to process groups of things with just a few lines of code. So I'll try to keep it simple and explain it all as we go along.
Well, without further ado, let's get our bullets firing! The first thing to understand is that we are going to recycle these same six bullets continually, instead of creating and destroying new ones all the time. To keep track of which bullet of the six is currently being fired, we'll make an integer variable to keep track of it, and give it the descriptive name bulletIndex:
var bulletIndex:int = -1;
Put this line right underneath the previous one that created the array. Now, the reason why we give this variable an initial value of -1 is that when the user presses the spacebar to fire a bullet, we are going to increment this variable by 1:
bulletIndex++;
When we do that for the first time ever in the game, it will cause the value of the bulletIndex variable to go to 0, which is the very index we need to start with. In order to cycle through just the index values we want, we next have to test the value of the bulletIndex variable. If it is greater than 5, we set it to 0 again, so that the whole system cycles (that is, wraps around to the beginning again):
if(bulletIndex > 5) bulletIndex = 0;
If the bulletIndex > 5, it must have gone to 6, and if so, we just set it to 0 again. All of this is going to happen when the user presses the spacebar, and so the code needs to be in the KEY_DOWN handler, at the point where it is detected that the user has pressed the spacebar. What we need to do is set the bullet's x and y equal to the player's x and y. After the bullet is placed there, it needs to begin moving to the top of the screen. However, this part requires continuous action, so guess what? The code that moves the bullets needs to be placed into the ENTER_FRAME event handler.
We also need some kind of gimmick to only move those bullets upward that have been fired by the player. This can be done by testing to see if a particular bullet's y value is less than the player's. As things move up on the screen, their y values get smaller. So when a bullet is at the player's feet, it's y value is greater than the player's y value.
So the strategy will be to cycle through all the bullets, and make this test. Just to make sure it works right, initially when the spacebar is pressed, each bullet will be placed at the same x location as the player, but we'll give it a y value that is one pixel less than the player's y, in anticipation of that test I just told you about:
var bullet:Bullet = bulletArray[bulletIndex];
bullet.x = player.x;
bullet.y = player.y - 1;
In the first line of the above, what we are doing is just making a temporary variable to store a reference to the more complicated reference bulletArray[bulletIndex]. We do this a lot in programming, and this technique might be called "gaining a reference." It's just so much easier to type the word "bullet" in the succeeding lines than it is to type bulletArray[bulletIndex], and it winds up looking neater, too.
Next, the part that goes into the ENTER_FRAME handler will cycle through all six bullets. For each one, it will determine if it has a y value that's less than the player's. If it does, its y value will be decremented by 10. Now, this code is going to use the for loop structure I told you about. Just think of it as a block of code that runs six times. Each time through the loop, the value of i will be increased by 1. This corresponds to the index of our array, so that each time through the loop, the code deals with the next bullet.
In case the whole idea of the for loop is new to you, I have prepared the following demo program, so that you can step through it interactively. If you go through it a time or two, it should become clear to you just how FOR loops work! The loop in the demo is a little bit simpler version of the one we use here. It starts off using only one line of code in the body of its loop. The idea, though, is to understand the concept of repeating the same section of code a certain number of times, each time with a new value for the loop counter. If you understand that much, increasing the number of lines of code between the braces probably shouldn't throw you (for a loop? sorry, couldn't resist that one)! 
Another thing to get through your mind (which was also brought out by the demo at the very end) is that this loop will run through its six cycles and do it every frame! In other words, ENTER_FRAME runs at the framerate of the movie, which is 40 fps, like I told you earlier. So this loop is running through its six cycles and also doing it 40 times every second! Pretty impressive!
for(var i:int = 0; i < 6; i++) {
if(bulletArray[i].y < player.y) {
bulletArray[i].y -= 10;
}
}
There is one other thing to test for though. When the bullets get to the top of the screen (y location 0), we want them to appear under the player's feet again. So we need to throw in another test:
for(var i:int = 0; i < 6; i++) {
if(bulletArray[i].y < player.y) {
bulletArray[i].y -= 10;
if(bulletArray[i].y < 0) {
bulletArray[i].x = i * 70 + 100;
bulletArray[i].y = 595;
}
}
}
If the bullet goes off the screen at the top (its y location is less than 0), we place it back at the bottom again. The reason that this if statement is nested inside the other one is because if the first if statement fails, the bullet in question is not in play, and need not be considered futher. But if a bullet is in play, and if it has gone off the screen, the next line sets the x location to where it started, using the loop counter variable i to do the math (using a horizontal spacing of 70 and a left margin of 100). The next line sets the y location to 595, which is the y location that all the bullets started out at. When you run the file (or try the live swf, below), you will see that as a particular bullet goes off the top of the screen, the very next moment it's right back where it started! The bullets are being recycled!
Only pressing the spacebar can put a particular bullet back into play again so that the first (outer) if statement passes.
Now there just remains one more pesky little detail to take care of. Once again, the keyboard repeat rate becomes a factor. If we go with the code we've got so far, we'll find that if the user holds down the spacebar, they will fire a bullet, then after an initial lag, the other five bullets will all fire at once! To prevent this and to make sure that only one bullet gets dispensed per keypress, we can make use of the Boolean variable we set up earlier. Find the line in the KEY_DOWN handler that says:
if(event.keyCode == 32) {
and change it to:
if(event.keyCode == 32 && spaceBar == false) {
The && that you see in that line there is the AND operator. In order for the whole expression in the parentheses to evaluate as true, both expressions on either side of this && operator have to individually evaluate as true, too. However, pressing the spacebar sets the spaceBar Boolean variable to true, and it won't become false again until that key is released. Therefore, this whole expression will only evaluate to true once per keypress! We're disallowing rapid-fire and forcing the user to keep hammering on the spacebar to keep firing! Aren't we mean? Anyway, here's the whole code so far:
import flash.events.KeyboardEvent;
import flash.events.Event;
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 bulletIndex:int = -1;
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 {
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;
}
}
//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;
}
}
}
}And here's how the compiled swf behaves:
(Click once to activate. Now you can fire bullets with the spacebar!) There is one other thing to do to make the code better. The ENTER_FRAME handler is already getting long, and right now it does two different things: First it checks to see if the keys are being pressed that move the player, and then it goes through a loop that moves the bullets. To improve it, let's move these two operations into their own separate functions, and then change the ENTER_FRAME handler so that it just "calls" those other functions. We'll name the function that controls the player controlPlayer(), and the one that moves the bullets moveBullets(). The code does exactly the same thing, it's just a little bit better organized. The ENTER_FRAME handler becomes a list of functions to call, and we can work on those other functions separately without feeling like we might mess something up. Also, if we choose to comment out a section of code for testing purposes, using this system we only need to comment out one line (the one that calls the function, naturally).
Anyway, here's the new, reorganized code:
import flash.events.KeyboardEvent;
import flash.events.Event;
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 bulletIndex:int = -1;
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();
}
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;
}
}
}
}
Check out the stage_onEnterFrame() function now! Now it consists of just two lines, which are the calls to the other two functions that have been created below it!
Now that our player is shooting bullets, on the next page, let's give it some enemies to shoot!
Right now, our hero character is having too much fun just shooting bullets aimlessly. The game will also be boring without some elements of challenge, and danger. So let's introduce some enemies!
There is a Spider MovieClip in the library that we will use to make enemy instances. Similar to the bullets, let's just drag a few instances of these spiders to the stage. Place them anywhere you like, because the code we'll write will give them new locations anyway. Give them instance names (using the properties panel) of spider0, spider1, and spider2. Of course, you may have already guessed that our code is going to associate them as a group using an array. Therefore, add this line to the code:
var spiderArray:Array = new Array[spider0, spider1, spider2];
Having these spiders in an array is going to allow us to easily process them using a for loop. Where do you place this new line of code? Insert a line right after the line that declared the bulletArray and place it there. In my tutorials I will often give you new lines of code to insert, but when the code gets long, I must wait till the end of the page to show the whole thing so far. At that point, your code should at least somewhat resemble mine. If you get a thing or two in a slightly different order (the order functions appear usually isn't critical), as long as it works, that's okay.
What's important at this point is to have a general idea of how these enemies are going to move, and to have a vision of how we're going to accomplish it. For example, I envision that the spiders are going to come down from above the top of the screen, and their y value is going to increase by a few pixels each frame, and when they finally get past the bottom of the screen, they should start at the top again. So I am picturing in my mind the area just above the screen. Suppose I consider an area the same width as the screen, and perhaps 500 pixels or so high? Wouldn't it be cool to have a function that could place a given spider in a random x and y location within this area, just before it begins its descent?
That's exactly what we'll do. We'll call the function recycleEnemy. We'll make the function accept one parameter, which will be a reference to whatever spider we want to act upon. We'll give this parameter a type of MovieClip. In other words, we will create a general purpose function that recycles a spider to the top of the screen! All we have to do is call this function, sending it a reference to whichever spider we want it to act upon.
The fact that we will give each spider a random location in this unseen area above the screen means that the spiders will appear fairly unpredictably, even though they will all fall at the same rate of speed. I am going to show you how to do what I have envisioned, but I want to also add that this is an area where you can really get creative if you want to. For example, if you'd like your enemies to fall at random speeds, you can do that. If you'd like your enemies to zig and zag as they fall (by manipulating their x location somehow), you can do that too! I encourage you to experiment with this later on, because that will make an even more interesting and challenging game. But for now, let's just continue on with my own simpler, more modest vision.
Now let's consider how we'll place the enemy at a random location in this field above the screen. First let's consider the x direction (random horizontal placement). I've prepared a series of illustrations to help you picture this:
It should be clear from the above illustrations how the random x location is determined. By providing a left and right margin like this, and considering that the symbol for the enemies is also centered on its registration point, we can thus make sure that enemies can never appear in an awkward position along the left or right hand edge that would make them impossible for the player to shoot.
Next, let's consider the y direction (random vertical placement). I've likewise prepared a series of illustrations for this also:
By allowing for a margin of half the enemy's height (they're centered on their registration point in that direction, too), we make sure that enemies always appear at least slightly above the top of the screen, out of view, before they begin their descent into the visible area. Note that the use of a 500 pixel range for the negative y values was purely arbitrary, and has no special significance whatsoever. It just seemed like a good amount of variance to me, but feel free to change it if you want to.
Now that we've worked out the formulas for both the x and y directions, we can put these into a function that we can call at will:
function recycleEnemy(enemy:MovieClip):void {
enemy.x = 25 + Math.random() * (stage.stageWidth - 50);
enemy.y = -500 * Math.random() - 25;
}
This function will come in mighty handy, as there are several situations where we'll want to recycle the enemies. First, we'll want to start off the program with all the enemies at random locations above the screen, so we'll use this function on all of them right away (using a for loop, of course!). Secondly, if an enemy makes it past the bottom of the screen without the player shooting it, we'll want to recycle it. Thirdly, if an enemy gets shot by one of the player's bullets, we'll want to recycle it. And finally, if an enemy collides with the player, we'll want to recycle it (and the player as well, but we'll get to that later).
The for loop that does this at the beginning of the program goes like this:
for(var i:int = 0; i < 3; i++){
recycleEnemy(spiderArray[i]);
}
You can place this code near the top of all the other code, but right after the variable declarations. Since it's not in a function, it will run right away. From our study of for loops on the previous page, you should now have a clear idea of what this little piece of code does, but I'll explain it anyway. The loop runs three times. The first time through the loop, when i = 0, spiderArray[0] gets recycled. The next time through the loop, when i = 1, spiderArray[1] gets recycled. The third time through the loop, when i = 2, spiderArray[2] gets recycled. Finally, the loop counter goes to 3, the comparison test fails, and the loop ends with all three spiders having been recycled to the top of the screen at random locations.
Having placed the enemies at random locations at the top of the screen, now let's write a function that will continually move them down the screen. Remember, continuous action needs a continuous event, so this function will be the next one in the list that gets called by the enter frame handler, and we'll call it moveEnemies. This function will cycle through the enemies (using yet another for loop, of course!), and increment their y value by three pixels. It will test each one to see if it has gone off the bottom of the screen, and if so, will recycle it back to the top of the screen:
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]);
}
}
}
Next, add a call to this new moveEnemies function to our enter frame handler:
function stage_onEnterFrame(event:Event):void {
controlPlayer();
moveBullets();
moveEnemies();
}You can now press CTRL-ENTER to test the movie. Just like the list in the enter frame handler indicates, you can now control the player, the bullets you shoot will move, and the enemies will come down from the top of the screen and get recycled again when they reach the bottom. Here's the final code:
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.display.MovieClip;
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;
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();
}
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 recycleEnemy(enemy:MovieClip):void {
enemy.x = 25 + Math.random() * (stage.stageWidth - 50);
enemy.y = -500 * Math.random() - 25;
}
And here's how the swf now behaves:
(Once again, you must click on the swf to activate the keyboard controls). Notice that we can move left and right, shoot bullets, and the enemies come after us. But our bullets don't "harm" the enemies, nor do the enemies "harm" us. Well, of course not--we haven't programmed that part yet! Taking it one step at a time, on the next page we will see how to use collision detection to handle interactions between the enemies and the bullets.
Next, we'll add another function that will get called by the enter frame handler. Inside this function, we need to cycle through all the enemies. Then for each enemy we consider, we have to also cycle through all the bullets. This is going to involve using a beast we haven't covered yet: a nested for loop. This might sound scary at first, but it's really not too bad. If you have a really good grasp of the for loop itself, you will be just fine.
In an ordinary for loop, there is a block of code that gets executed repeatedly, a certain number of times. In a nested for loop, the block of code that gets executed repeatedly includes another for loop!
I realize this is a huge head trip if you never encountered it before. But having an array of enemies and an array of bullets to use as an example is definitely going to help you understand it better. We have previously seen how arrays and for loops work well together, and that we can use a for loop to process an array. The fact that the array is zero-based, and the fact that we always use a loop counter that initializes at 0, and a condition that the loop counter be less than a certain number (that number being the number of times we wish to loop, and usually that's also the length of some array), we can be assured that our loop processes every element of the array exactly once each.
What we want to do is make the outer loop run through the array of enemies, and the inner loop run through the array of bullets. We will need to use a different letter for the inner loop's counter variable. Following convention, we will use the letter j. But don't let the loop counters confuse you, i and j are just convenient variable names. Here's the thing to really grasp: the inner loop runs once for each time the outer loop runs. This means that the inner loop will be run three times through altogether, once for each element of the outer loop.
So here's how this works: The outer loop begins processing. The first time through the outer loop, i = 0. Since this is so, we are dealing with spiderArray[0] this whole iteration. Next, the inner loop begins processing the bullets. The fact is that the inner loop will process all six bullets before it gives back control to the outer loop. We can thus test all six bullets for a hit test against the spiderArray[0]. Control is given back to the outer loop, and it begins its second iteration. Now we are processing spiderArray[1]. Once again, the inner loop begins and goes through all six of its iterations (again, with hit testing) before control is again given back to the outer loop. Next, the outer loop begins its third iteration, and now we are dealing wth spiderArray[2]. Once again, the inner loop processes all six bullets.
Hopefully, this is clear to you. The main thing to "get" is that we process the three enemy spiders, and that for each one of those, all six bullets are cycled through using the inner loop. If it helps, imagine a lineup of three of your friends. You walk up to the first one. You tap him or her on the shoulder six times. Then you move on to the next friend and do the same thing. Finally, you move to the third friend and do it one last time. The three friends are the outer loop, and the shoulder taps are the inner loop. And that's the same way we are processing our enemies and bullets.
The loop counters, i and j, are significant because while we are running the inner loop, we still have a reference to the enemy we are processing with the outer loop. Even inside the inner loop, the current enemy can be referenced with spiderArray[i]. Meanwhile, the six bullets are referenced with bulletArray[j]. We can thus process both arrays using this nested loop structure, and when we are done, we can be confident that every spider in the spiderArray has been hit tested against every bullet in the bulletArray:
function enemiesHitTest():void {
//for each of the three spiders
for(var i:int = 0; i < 3; i++) {
//the each of the six bullets
for(var j:int = 0; j < 6; j++) {
if(spiderArray[i].hitTestObject(bulletArray[j])) {
recycleEnemy(spiderArray[i]);
}
}
}
}
We need to also make sure we place a call to this new function inside the enter frame handler:
function stage_onEnterFrame(event:Event):void {
controlPlayer();
moveBullets();
moveEnemies();
enemiesHitTest();
}Using the hitTestObject function is pretty easy. It's a function that's built in to the MovieClip objects we are using. It takes this basic form:
mc1.hitTestObject(mc2);
Where mc1 and mc2 are a couple of movie clips. However, it works just as well if you turn it around the other way:
mc2.hitTestObject(mc1);
You can use either object's method to do a hitTest against the other object, in other words. It doesn't make any difference. Of course, we are using array references like spiderArray[i] and bulletArray[j] instead of mc1 and mc2. But the array references are really just pointers, and they point to the actual movie clip instances themselves. So when we use array references like this we are actually hitTesting the very objects that they are pointing to. An array reference is every bit as good as an instance name, and I am always emphasizing that in my tutorials!
If you try this new code, you will find that it works great! When a spider gets hit with a bullet, they get recycled to a random location above the top of the screen. The fact that they disappear when they do so makes it look like they really got shot! Now you see it, now you don't! Hey, we're mostly just illusionists, anyway! But one thing we forgot! When a spider is hit, the bullet doesn't disappear, but keeps right on going, and may even wipe out yet another spider! This probably isn't what we want, though. To fix it, let's borrow the two lines of code from the moveBullets function that placed a bullet at the player's feet, and copy them to the new function:
function enemiesHitTest():void {
//for each of the three spiders
for(var i:int = 0; i < 3; i++) {
//the each of the six bullets
for(var j:int = 0; j < 6; j++) {
if(spiderArray[i].hitTestObject(bulletArray[j])) {
recycleEnemy(spiderArray[i]);
bulletArray[j].x = j * 70 + 100;
bulletArray[j].y = 595;
}
}
}
}Notice that in the copied lines, we must change the i's to j's, because even though we are looping through the same array of bullets, we are using a "j" for a loop counter in this new location. Otherwise, it works exactly the same way, and places a bullet back at the player's feet after it hits a spider.
You may have guessed that we could have also written a special recycleBullet function and just called it from these two other locations, like we did with recycleEnemy, and that would be just fine, and a good thing to do. We will leave it as it is for now, but anytime you can refactor code that is essentially the same, but appearing in more than one place, you should do so. That way if you ever have to change it you can do it all in one place. Right now it's not too bad, because we can be pretty much certain that these will be the only two places where we'll need to recycle bullets.
But there is as yet one more obvious thing wrong with the above: there is no need to hit test a bullet that's not in play.
For one thing, it's not as efficient. But also, as it is, if the spiders get down to the bottom of the screen and hit the bullets below the player's feet, they get recycled immediately instead of making it past the bottom of the screen. So let's throw in an if condition that causes us to only hit test bullets that are in play. We can do this by making sure their y value is not greater than the player's, like we did before when moving the bullets:
function enemiesHitTest():void {
//for each of the three spiders
for(var i:int = 0; i < 3; i++) {
//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;
}
}
}
}The new line is this one:
if(bulletArray[j].y > player.y) continue;
The continue keyword is a special one that's used with loops. It means "cancel this iteration of the loop, and continue to the next one." This only applies to the inner loop, and makes the inner loop continue on to its next iteration, skipping the remaining lines that would have been executed. This is exactly what we want--any bullets not in play will therefore not be hit tested!
Here is all of the code so far:
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.display.MovieClip;
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;
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++) {
//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;
}
And here is how the swf behaves with these new changes:
(Once again, you must click it once to activate the keyboard keys) We're getting there! This version is fairly cool, but the player is still impervious to contact with the spiders. We need to fix that with some more hit testing. Then we'll add some kind of scoring, and lives, and maybe even some other little touches!
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!)...
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.
Next, we'll tackle making the game progressively more difficult. There are many, many ways to do this--limited only by your imagination! I'm going to use the strategy of just progressively increasing the downward speed of the enemies. This is really easy to do. We'll introduce a variable called spiderSpeed. It needs to be a Number type rather than an integer, so that it can handle fractions. We'll set it to an initial value of 3:
var spiderSpeed:Number = 3;
Next, we'll substitute this variable in the moveEnemies function in place of the previous hard-coded number 3:
spiderArray[i].y += spiderSpeed;
Now we just need a way of increasing this spiderSpeed variable by some factor. A good place to do this is in the enemiesHitTest function, at the point where an enemy has been hit, and the 10 points have been added to the score. So insert this line right after the updateScore(10) line:
spiderSpeed = spiderSpeed * 1.01;
This will make the speed of the enemies increase by 1% each time an enemy is hit, the idea being that shooting them only makes them increasingly madder! This line causes the spiders to get faster and faster, and fairly quickly, too! But not so quickly as to be ridiculous. This makes the game a bit more fun and challenging. There are still many ways that you could improve on this, though. Here is where you can let your imagination soar, and here are some more ideas:
All of the above ideas are possible, but I'm only going to take you so far in this tutorial. The rest is up to you. I wanted to give you some basics for a foundation, and hopefully I've accomplished that. But you should really understand that I have definitely not showed you the only way to make a game, or even the right way. It's just a beginning. You might also want to get it off the main timeline and use my Better Flash Navigation using AS3 Classes system instead, which will also make it much easier to create new levels.
I made a couple of small improvements to the game, based on my observations while playing it:
I moved the player to the bottom of the screen, and also moved all the bullets down, so that they are out of sight below the bottom of the screen. This was begging to be fixed. The hard number 595 in the moveBullets function was changed to 617.
I dragged the player layer upward in the timeline's layer stack, so that it is above the spiders layer. It looked a bit funny to see spiders emerging from the top, but the ones that came down from the upper right were layered in front of the "remaining lives" instances.
Next, I added a few sounds to make the game more interesting. I had so much fun doing this! I had a collection of sounds already on hand, and I chose a few of them for certain game events.
To get a sound into your game, the easiest way is to import it to the library. Then, right click it, choose "Properties," then check off "Export for Actionscript." You'll need to supply a class name. Don't accept the default one suggested by Flash. It will likely be based on the file name, complete with a .wav extension or whatever the extension is. Instead, choose a name that makes sense to you in the context of the game, and that's easy to remember. For example, I used "PlayerHit" for the class name of the sound I wanted to associate with that. When you click OK on this dialog box, just click OK again when Flash tells you that the class couldn't be found in the classpath. It's all right. We want Flash to create the class for us.
Next, find the place in the code where you would like to play a certain sound. For example, the player is (possibly) hit in the first half of the enemiesHitTest function. Insert a new line right after the one that does the hit testing. It's an if condition, and if the body of this statement executes, the player has been hit by a spider. Whether the game is over or not, we want the sound to play. Insert this line:
new PlayerHit().play();
The new keyword creates a new object. You've seen it so many times being assigned to a new variable, that you might have thought it must always be like that. However, we don't really have any reason to assign this new object to a variable, as once it plays we are finished and done with it. And you may use this format in all the other places around your code, after you strategically figure out where playing a sound is called for. Don't worry, I think you know your way around the code by now, if you made it this far.
Anyway, here are the sounds I added. Their class names should tell you exactly what they are for:
I'll paste the code one more time so that you can see where these sounds are played:
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.media.Sound;
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;
var spiderSpeed:Number = 3;
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;
//sound effect:
new StartGame().play();
function stage_onKeyDown(event:KeyboardEvent):void {
if(event.keyCode == 39) {
rightArrow = true;
}
if(event.keyCode == 37) {
leftArrow = true;
}
if(event.keyCode == 32 && spaceBar == false) {
//sound effect:
new FireBullet().play();
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 = 617;
}
}
}
}
function moveEnemies():void {
for(var i:int = 0; i < 3; i++) {
spiderArray[i].y += spiderSpeed;
if(spiderArray[i].y > (stage.stageHeight + 25)) {
//spider got away... penalty sound effect:
if(betweenLives == false) {
updateScore(-5);
new EnemyGotAway().play();
}
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)) {
//sound effect:
new PlayerHit().play();
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])) {
//sound effect:
new EnemyHit().play();
updateScore(10);
//make the spiders get faster:
spiderSpeed = spiderSpeed * 1.01;
recycleEnemy(spiderArray[i]);
bulletArray[j].x = j * 70 + 100;
bulletArray[j].y = 617;
}
}
}
}
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 {
//sound effect:
new NewLife().play();
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 {
//sound effect:
new GameOver().play();
rightArrow = false;
leftArrow = false;
spaceBar = false;
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);
}(Note, there is some code on frames 1 and 3 also, but I haven't chosen to paste it here. It's very basic, just a couple of CLICK listeners. Download the fla file to see it. You won't have any trouble with it).
Here is how the swf behaves now. You must admit that the sounds add a completely new dimension to it:
Alas, the "software" is never finished (imgagine ME writing "software")! But I feel like this tutorial is, at least for now. What I would like to add in the future, though, is a checkbox for turning the sounds on and off, and make them off by default, just for Internet politeness. But right now I must leave that for another day, and a follow up page.
Thank you for hanging in there with me, and I hope you enjoyed this! Thanks in advance for comments!
Jody Hall