AS3 101: Loops - Basix
Welcome back to AS3 101, the fifth edition! At this point, you should be comfortable with the concepts of variables, functions, conditionals (branching), and Arrays. Not only should you be comfortable with the concepts, but you should also be fluent in how ActionScript 3 implements these concepts.
For this fifth episode, we'll be adding to that catalog of skills the various looping structures found in AS3. You'll find some dependencies on the information learned in AS3 101 Part 4, on Arrays, so if you've skipped that one or need a refresher, please review that tutorial before continuing with this one.
For our final project, we'll combine loops and arrays, to make a simple shoot-em-up game.
Step 1: What's a Loop?
If you've ever engaged the "repeat play" function on your CD player, MP3 player, or music management software, you've experienced a loop. If you've ever had to write lines on the chalk board after school, you've experienced a loop. If you've ever watched "Groundhog Day," you've witnessed a loop.
A loop in programming is a control block, similar in essence to an if statement or a function, in that there's a bit of code that resides between curly braces, and that block is controlled by some form of control structure. In this case, the control allows the block to be executed over and over, most likely with slight variations, until it is told to stop.
There are three kinds of loops in ActionScript, the for loop, the while loop, and the do loop. There are also two other kinds that are loops used specifically for traversing a collection such as an Array, and can't be used in any other way. These are the for...in loop and the for...each loop.
One thing to understand about the three "standard" loops is that they will run forever if you let them. An improperly coded loop will result in what is known as an infinite loop, owing to which the computer will dutifully do what you told it to do, and that is to process that same block over and over again until the end of time. Fortunately, the for loop is hard to mess up (though it certainly happens often enough), and on top of that, ActionScript 3 has built-in infinite loop protection. If a script runs for more than 15 seconds without coming up for air, then you'll see a warning to the effect of "A script is causing this movie to run slowly. Would you like to abort?" If you see that while working through this tutorial, click "Yes," because chances are you did something wrong and you may as well take the escape hatch that is being offered to you. Without this exit, the loop would just run, and run, and run, effectively locking you out of touching Flash again until you manually terminate it (Force Quit on the Mac, End Task on Windows), and restart Flash.
With that said, loops are extremely useful. In fact, they're indispensable programming tools. They can take some of the tedium out of setting up a bunch of similar buttons. They are necessary for working with dynamic data, for example when an arbitrary number of items will be coming in via XML. You can process particle systems with loops. The vast majority of games require the use of loops. And you can achieve such logic as "give me any random number except for the last one you gave me" or "build a string with the specified number of spaces in it."
Step 2: The For Loop
The most commonly used kind of loop, in any language, is probably the for loop. Three of the five loop constructs actually use the word for, but one is referred to simply as the "for loop" because there are no other keywords involved in making one. Let's write a simple for loop:
1 |
for (var i:int = 0; i < 10; i++) { |
2 |
trace("Hello."); |
3 |
}
|
And let's break it apart. First up is the for keyword (like so many of our programming constructs, this one starts with a keyword). Immediately after this is a set of parentheses. Inside of these parentheses are actually three whole ActionScript expressions. We'll come back to these in a second, but note that there are two semi-colons. These separate the three expressions. Next is an opening curly brace. Then we have some code, and a closing curly brace. The code between the curly braces is what gets executed as the loop iterates.
Now, those three expressions. They are:
- The initializer
- The test
- The counter



The initializer is really just a statement that runs as the very first thing in the loop. It gets run once, and it gets run first. So in this example the first thing we do is create a variable called "i" and set it to 0.
The test is an expression that should evaluate to true or false. Think of it as something that would be at home as the condition of an if statement. The for loop uses this condition before every iteration of the loop to see if it should continue executing. As long as the condition is true, the loop will continue to execute. As soon as it's false, it will break out of the loop. In our example, this means that as long as the variable i has a value that is less than 10, we will continue the loop. Should it ever equal 10 or be greater than 10, the loop will stop.
Finally, the counter is another statement that will execute once for every iteration of the loop, after all of the code between the curly braces has executed. Looking at our example, this is the final piece of the puzzle, and we can see that after we trace "Hello," we take that i variable and increment it by 1.
It's helpful to step through the life of a loop at a slow pace. Here's what happens:
- Create a variable called "i" and set it to 0.
- Check to see if the variable i is less than 10. It's currently 0, so that's true, so we...
- Trace out "Hello."
- Increment i. i is now 1.
- Check to see if the variable i is less than 10. It's now 1, so that's still true, so we...
- Trace out "Hello."
- Increment i. i is now 2.
- Check to see if the variable i is less than 10. It's now 2, so that's still true, so we...
- Trace out "Hello."
- Increment i. i is now 3.
- ... and this goes on... let's skip ahead to when i is 9...
- Increment i. i is now 9.
- Check to see if the variable i is less than 10. It's now 9, so that's still true, so we...
- Trace out "Hello."
- Increment i. i is now 10.
- Check to see if the variable i is less than 10. It's now 10...aha! 10 is not less than 10. The condition is false, so now we exit the loop, and resume executing code immediately after the closing curly brace.
If you've been keeping score, you'll have noticed that we should have traced "Hello." 10 times. Keep in mind that while we stop when i is equal to 10, we start i at 0, so i increments from 0 to 9, allowing for 10 iterations. Notice how this echoes the fact that Arrays have indices that start at 0. While you could easily write for loops that start at 42 and run while i is under 52 – thus achieving 10 iterations – there are very good reasons to making sure you try to stick with starting at 0. I'm going to have to pull a "trust me" on this one, but basically as long as you're consistent, you can avoid a lot of unexpected results in the long run.
Go ahead and run this snippet. You should see ten "Hellos" in the Output panel.

That by itself isn't terribly useful. If I wanted to save some time when I'm writing lines after school ("I will not leave variables untyped. I will not leave variables untyped. I will not...") then this is great. But almost always you're going to want to vary what happens in each iteration by a little. Surely, there must be a way to accomplish this?
There sure is! You know that variable "i" we created to help keep track of the loop? Would you believe that that's a variable exactly like any other variable in ActionScript? Remember, even though those three expressions within the parentheses look a little different, they're still just ActionScript expressions. We can technically put any valid ActionScript there. I don't recommend doing that, though, at least not for beginners. My point, instead, is that that variable "i" is available to us. Let's alter the loop body so that instead of tracing "Hello." we trace i:
1 |
for (var i:int = 0; i < 10; i++) { |
2 |
trace(i); |
3 |
}
|
And run it. You'll see we've got a list of integers from 0 to 9. Having this available opens up a world of possibility, as we'll start to explore a little later.

I should point out that the name "i" for this kind of loop is a strong convention not only in ActionScript, but virtually all programming languages. You're free to use any variable name you like, but if you use simply "i," then everyone will know exactly what you're up to, which is a good thing.
And as a parting thought on for loops, if you want to see an infinite loop in action, try this:
1 |
for (var i:int = 0; i < 10; i--) { |
2 |
trace(i); |
3 |
}
|
Can you spot why this loop will never end?
Step 3: While and Do
There are two other "standard" loops that are really just variations on the same idea. We'll look at them briefly for completeness sake.
First, let's look at the while loop. This is actually the core loop construct. A for loop gets compiled into a while loop. Every now and then though, a while loop can do something that a for loop is just too cumbersome for. The while loop looks like this:
1 |
var i:int = 0; |
2 |
while (i < 10) { |
3 |
trace(i); |
4 |
i++; |
5 |
}
|
This is actually the exact same as our for loop from the last step, just written with a while. The while loop itself starts with the keyword while and includes the curly braces. The first line is just our initializer, which is necessary in order to get ready for the loop, but isn't taken care of automatically like it is with a for loop. The part between the parentheses after the keyword is the test. As long as that is true, the loop will execute. Our loop body consists of not only the thing we want to happen (trace i), but also the incrementing of the i variable. Again, it's necessary to devote a line to this, although the for loop provides a convenience for this very common idiom.



In other words, this code is exactly the same as the for loop from earlier, only with more lines involved. It's technically not more code, just more lines.
So why use a while loop? Usually, it's because we don't have a clear idea of exactly how many times something needs to happen. Imagine a game character moving in a certain direction, and it needs to stop when a wall blocks its path. We could conceivably write something like:
1 |
while (!character.hitTest(wall)) { |
2 |
character.forward(); |
3 |
}
|
The do loop is the oft-overlooked cousin of the while loop. They are virtually identical, with two small exceptions. In a do loop, the keyword is do (duh!). And also, the while comes after the closing curly brace. It looks like this:
1 |
var i:int = 0; |
2 |
do { |
3 |
trace(i); |
4 |
i++; |
5 |
} while(i < 10); |
The functional difference between the two is that in a do loop, you are guaranteed that the loop body will execute at least once. In a while loop, it's possible that the condition might not be met at the outset, in which case the body never executes.
Imagine a specialized random number generator. You want to choose a random number from 1 to 10 each time a button is clicked, but you want to make sure that the number isn't ever the same as the last number that was chosen. With a do loop, you can pick a random number, compare it to the last number, and then repeat the process as necessary. In this case, you need to pick at least one new random number. It might be all you need, but it has to happen at least once. Imagine the var "randomNumber" holds the current random number, and that this code is inside of, say, a function that is hooked up to the button click.
1 |
var lastNumber:int = randomNumber; |
2 |
do { |
3 |
randomNumber = Math.ceil(Math.random() * 10); |
4 |
} while (randomNumber == lastNumber); |
This code creates a temporary variable called "lastNumber" that stores the current "randomNumber" value. Then we enter the do loop. Since it's a do, we execute the body, then worry about the condition, so we give "randomNumber" a new random value (by taking some value generated through Math.random(), which is 0 to 1, multiplying by 10, and then rounding up with Math.ceil()). Then we check the condition: if the current value of "randomNumber" (which we just changed) is the same as the value stored in "lastNumber" (which was the last real value of "randomNumber"), then keep looping. Try again, in other words. So if "randomNumber" was 7, and the first time through the loop also generated a 7, then we'd step through the loop again, until something other than 7 is obtained. The result is a random number that is never the same as the last one we had.
Step 4: Continue
Sometimes it is necessary to alter the execution of a loop while we are in the midst of running the loop. We can actually do two things from inside a loop that alters the flow of that loop.
continue is a keyword that causes the loop to immediately start iterating again. It might leave code unexecuted in the body, and that's actually the point of continue-ing a loop. You use it like this:
1 |
for (var i:int = 0; i < 10; i++) { |
2 |
trace(i); |
3 |
if (i > 5 && i < 8) { |
4 |
continue; |
5 |
}
|
6 |
trace("The value of i is: " + i); |
7 |
}
|
This isn't a terribly useful example but it illustrates the usage and effects of continue. This is a typical for loop, with "i" going from 0 to 9. The first thing in the block is to trace out "i." No big deal so far. Then, we check to see if "i" is within a certain range: above 5 and below 8. If it is, then we continue. The last line is another trace. So, while "i" is 0, 1, 2, 3, 4, or 5, we see two traces for each value of "i". But then "i" turns 6, which is greater than 5 and less than 8, so we continue. Therefore, this iteration of the loop stops immediately and we start the next iteration, with "i" incremented and set to 7. 7 also meets the criteria and we pull a similar trick. In both cases, because we've continued before the last line of the block, we only see one trace for those two values of "i."

Step 5: Break
break is a sort of panic button for loops. Using it will cause the loop to simply end its execution, no matter how true the condition is. break can be useful for building in fail-safes in while and do loops, since those can be prone to infinite loops. Consider:
1 |
var num:Number; |
2 |
do { |
3 |
num == Math.random(); |
4 |
} while (num != 0.42); |
This do loop, while not technically an infinite loop, will most likely trigger the script timeout limit before ever generating exactly 0.42. We can build a safety mechanism like so:
1 |
var num:Number; |
2 |
var loopCounter:int = 0; |
3 |
do { |
4 |
num == Math.random(); |
5 |
loopCounter++; |
6 |
if (loopCounter > 500) { |
7 |
break; |
8 |
}
|
9 |
} while (num != 0.42); |
It's a bit more work, but it ensures that our loop will never execute more than 500 times.
break is more useful than just that, though. You might want to loop through an Array, and look for a particular value. Once you find a match, there's no need to keep scanning the rest of the Array, so you may as well break out of the loop. We'll see this in action shortly.
Step 6: For Each & For In
There are two types of looping structures that are specifically designed to work with collections like Arrays. They differ in subtle ways, but good programmers know the differences and know when to use which one.
First, let's suppose we had an Array:
1 |
var ary:Array = ["one", "two", "three", "four", "five"]; |
Now we can loop over this Array. That is, we know that Arrays implicitly are collections of values. We want to take each individual value, and do something with each in turn. This is a very common task, so it's convenient to have two types of loop dedicated to this action.
We can write a for...in loop like this:
1 |
for (var index:String in ary) { |
2 |
trace(index + ": " + ary[index]); |
3 |
}
|
for...in starts with the for keyword again, but use of the in keyword makes this different than the regular for loop. The thing to the right of in is the thing we want to loop over (the Array). The stuff to the left of in is a variable declaration. This variable is available to the loop (and elsewhere, incidentally) and will contain, for each iteration through the loop, the index/key/name of each item contained in the collection. In the case of an Array, we get the numeric indices. Note, though, that these are converted to Strings.

So, the variable "index" has the key stored in it. This means we can access the actual values by using that key in square brackets on the collection (ary[index]).
for...each loops are very similar, with one key difference: instead of creating a variable to hold the key, you create a variable to hold the actual value of the item in the collection.
1 |
for each (var item:String in ary) { |
2 |
trace(item); |
3 |
}
|

Notice that the datatype is String for the variable "item." This is coincidentally the same as what we had in the for...in loop. However, in this case the datatype is driven by what values are actually stored in the Array. If we had this Array:
1 |
var ary:Array = [1, 2, 3, 4, 5]; |
Our loop might look like this:
1 |
for each (var item:int in ary) {... |
In the body of the loop, the variable we declared will hold the value from the collection in each iteration. We had to go through a small hoop to get this value in the for...in loop. The for...each loop tends to be a little cleaner. However, you don't have access to the key in this loop, so if that's important, you'll need to use another type of loop.
for...each loops are not only faster to type, they are faster to execute. So if that type of loop fits your requirements (that is, you don't need to know the key), you might want to prefer it.
Step 7: Nested Loops
It's certainly possible to nest loops, just like it's possible to create multi-dimensional Arrays. It's a simple matter to do so, just be sure to honor the indentation or things get dreadfully hard to read:
1 |
for (var i:int = 0; i < 5; i++) { |
2 |
for (var j:int = 0; j < 5; j++) { |
3 |
trace(i + ", " + j); |
4 |
}
|
5 |
}
|
If you run this snippet, you'll see something like this:
1 |
0, 0 |
2 |
0, 1 |
3 |
0, 2 |
4 |
0, 3 |
5 |
0, 4 |
6 |
1, 0 |
7 |
1, 1 |
8 |
1, 2 |
9 |
1, 3 |
10 |
1, 4 |
11 |
2, 0 |
12 |
... etc. |
This gives a matrix of values. In this case, we get all combinations from 0, 0 to 4, 4.
Note, however, that it is extremely important to use a different variable name for the nested loop. We used "i" for the first loop (known as the outer loop) and "j" for the second loops (the inner loop). If we had used "i" like we habitually do, we'd up with just this:
1 |
0, 0 |
2 |
1, 1 |
3 |
2, 2 |
4 |
3, 3 |
5 |
4, 4 |
There can only be one variable "i" at a time, and so "i" reaches 5 on the inner loop and therefore meets the condition in the outer loop as well, terminating the entire thing earlier than we intended. Fortunately, ActionScript 3 has some built in protections that help prevent this error.
It's possible to build up multi-dimensional Arrays like this:
1 |
var grid:Array = []; |
2 |
for (var i:int = 0; i < 5; i++) { |
3 |
grid[i] = []; |
4 |
for (var j:int = 0; j < 5; j++) { |
5 |
grid[i][j] = i + ", " + j; |
6 |
}
|
7 |
}
|
It's also worth noting that we can break out of (or continue) nested loops by name. If you use break in an inner loop, it only breaks the current (inner) loop, not the outer loop. If you need to break the outer loop from within the inner loop, you can label your outer loop like this:
1 |
outerLoop: for (var i:int = 0; i < 5; i++) { |
2 |
for (var j:int = 0; j < 5; j++) { |
3 |
trace(i + ", " + j); |
4 |
if (i == 3 && j == 2) { |
5 |
break outerLoop; |
6 |
}
|
7 |
}
|
8 |
}
|
Note the very first bit: the label, a colon, and then the for statement. This gives the outer loop a label we can use with break and continue. Then in the inner loop, upon a certain arbitrary condition, we break out of the entire loop by specifying out of which loop to break.
Step 8: Looping Over Arrays
We've seen how to use the for...in and the for...each loops to loop over Arrays. However, it's very common to use the normal for loop to iterate over Arrays. The advantages of doing so are that we have access to the index (unlike for...each), and we get the index in int form (unlike for...in).
So at its most basic, it looks like this:
1 |
var ary:Array = ["one", "two", "three", "four", "five"]; |
2 |
for (var i:int = 0; i < ary.length; i++) { |
3 |
trace(ary[i]); |
4 |
}
|
However, when doing this, it's advisable to follow a few rules in the name of optimization. For this simple 5-iteration loop, it's not a big deal, but I encourage you to make it a habit to simply always use these ideas.
First, looking up ary.length each time in the condition is time-consuming. Unless we're actually altering the length of the Array in the loop, we should get that value once, store it in a variable, and use the variable in the check:
1 |
var ary:Array = ["one", "two", "three", "four", "five"]; |
2 |
var len:int = ary.length; |
3 |
for (var i:int = 0; i < len; i++) { |
4 |
trace(ary[i]); |
5 |
}
|
Second, make sure that your numeric datatypes match between your "i" variable and your "len" variable. Comparisons are fast between and int and another int, or a Number and another Number, but if you make "i" an int and make "len" a Number, that comparison is significantly slower.
Third, when getting an item out of the Array, be sure to store it in a variable, as well. As an extra optimization trick, declare the variable outside of the loop. Also, put a datatype on the variable. All of this adds up to greater speed when using that item out of the Array.
1 |
var ary:Array = ["one", "two", "three", "four", "five"]; |
2 |
var len:int = ary.length; |
3 |
var item:String; |
4 |
for (var i:int = 0; i < len; i++) { |
5 |
item = ary[i]; |
6 |
trace(item); |
7 |
}
|
Step 9: The Mission
Now that we've gotten through the dry theory, let's build something fun: a simple shoot-em-up game, based loosley (extremely loosely) on the classic Asteroids. We'll use loops to iterate over collections of bullets and enemies, and perform hit tests on each. Don't expect too much in terms of game play; our purpose is to use loops in a practical setting, not learn the ins and outs of game design.
A starter FLA is provided with basic artwork. As usual for the AS3 101 series, we're more interested in learning to program, and less in the graphical production of a project. In the interest of time, we'll assume you started with the FLA provided in the source files.
A quick tour of the FLA is in order, however. There exist a few items in the library, most of which are on the stage. There is the player's ship:

This is on the stage at the left, facing right. It has an instance name of ship. The ship will move vertically in response to the player's mouse movement, and will never change its rotation or its horizontal position. You may notice that the registration point of the ship is centered, to make it easier to position to the mouse.
There is a symbol called Asteroid, representing something for the player to fight.

This isn't placed on stage, but is instead prepared for use with ActionScript. You might notice in the library that this symbol has an entry under "Linkage". This lets us work with the symbol, and set up individual instances, through code. Asteroids will move in a random direction, and wrap around the edges of the stage.
There's another symbol called Bullet:

It is not on the stage, but has a Linkage identifier, like the Asteroid symbol. Bullets will become attached to the stage programatically, and shoot from the player's ship toward the right. If there's a collision between any bullet and any asteroid, both of them are removed.
This actually leads to one of the tricks involved in programming Arrrays and loops. We'll get into the details soon enough, but keep an eye out for a few gotchas with our looping.
Finally, there's a "You win!" sign, placed on stage in the center with the instance name win.



This will be invisible until the player shoots all four asteroids, at which point game play will stop and this will appear.
Step 10: Start Coding
As is usual practise when writing timeline scripts, create a new layer and name it something like "code" or "script," and lock it. As a matter of organization, never mix your layers with artwork and code. Also, try to keep all code in a single script, as much as is possible.
With your layer created and selected, press F9 (Opt-F9 on the Mac) to open the Actions panel. Click the pin icon at the bottom to keep it from losing context if you click around the application.
The first thing we want to do is make sure that the win screen is hidden. So the very first bit we'll write is:
1 |
win.visible = false; |
Step 11: Arrays for Storage
The next bit of our script will be to create two Arrays that will hold all on-screen asteroids (in one Array) and all on-screen bullets (in the other). Both Arrays are empty for now. We'll create bullets and asteroids as we need them and stuff them into the Arrays. Our next few lines look like this:
1 |
var asteroids:Array = []; |
2 |
var bullets:Array = []; |
We'll be manipulating these Arrays in just a little bit, so if you need a primer on Arrays, please refer to my last tutorial on Arrays.
Step 12: Create Asteroids
We know that we want to create 4 asteroids, so we'll use a simple while loop to create and set them up.
1 |
while (asteroids.length < 4) { |
2 |
var asteroid:MovieClip = new Asteroid(); |
3 |
asteroid.x = 450 + Math.random() * 200 - 100; |
4 |
asteroid.y = Math.random() * (stage.stageHeight - asteroid.height); |
5 |
addChild(asteroid); |
6 |
asteroids.push(asteroid); |
7 |
}
|
Here we make a new instance of the Asteroid symbol. Then we position it randomly (we're placing the x within 100 pixels of 450, and the y anywhere within the height of the stage). Next we use addChild() so we can see it, and then store it in the asteroids Array using push.
If the new Asteroid() stuff is new to you, don't worry, we'll be covering display list topics in the next AS3 101 tutorial.
Notice why this while loop works: it keeps running until the length of the Array is 4 (or higher). And in every iteration of the Array, we end up adding one item to the Array. Therefore, the loop runs 4 times.
Why did I use a while loop and not a for loop? Partly because this seemed like a cleaner approach. In the end, it doesn't really matter, but since we don't really need the counter variable which is typically used in for loops, we could save a little typing by using a while loop. I'll admit, though; it's partly because it seemed like a good spot to use a while loop for the tutorial's sake. Just remember that there is always more than one way to achieve the same result, and the path you choose has as much to do with personal preference as it does with "the right way" to do things (oh, and by the way, there is no "right way" to do things in programming).
Step 13: Create an Animation Tick
To drive the game, we'll need to do many things every time the stage updates, at the frame rate. We'll set up an event listener for the ENTER_FRAME event, and do most of our logic in the listener function. For now, just set up the skeleton:
1 |
addEventListener(Event.ENTER_FRAME, onTick); |
2 |
|
3 |
function onTick(e:Event):void { |
4 |
trace("game going..."); |
5 |
}
|
If you like, you may test this to make sure the trace fires repeatedly over time.
Step 14: Move the Ship
With the onTick function set up, we can start moving things around. We'll start with the ship.
We'll simply match the ship's y to the mouse's y:
1 |
function onTick(e:Event):void { |
2 |
ship.y = stage.mouseY; |
3 |
}
|
This asks the stage for the y coordinate of the mouse. This is different from asking another MovieClip for mouse coordinates; you get the values back in the coordinate space of the MovieClip you asked. Using the stage, we know we're in the primary coordinate space.
If you test this now, you should see that the ship moves up and down in response to the mouse.
Step 15: Start Moving the Asteroids
We can put the asteroid motion logic in the onTick function as well, but it's a bit more involved. First, we'll use a loop to roll through the asteroid Array. Then for each asteroid, we'll add to (or subtract from) the current x and y by a specific amount. This has the effect of moving things incrementally with each frame, producing the illusion of smooth movement over time. Place the following code after the code to move the ship:
1 |
for each (var asteroid:MovieClip in asteroids) { |
2 |
asteroid.x += 3; |
3 |
asteroid.y += 4; |
4 |
}
|
Here we're using the for each loop for convenience – we don't need to know the Array index (and we won't, even after fleshing this loop out a bit). Test this movie and the asteroid should move, although their movement is less than ideal, to be sure. Asteroids don't typically fly in formation.
Step 16: Wrap Asteroids Around the Edges
In our game right now, the world is flat and you simply disappear off the edge and never come back if you go too far. Let's make the world round, so that going off in one direction means that eventually you'll show up again coming from the other direction. We'll do this by checking the x and y of each asteroid after you move them. If they are positioned so that the whole asteroid is off-screen, we'll adjust the x or y so that they come back on the other side. Fill out the for each loop from the last step with this:
1 |
for each (var asteroid:MovieClip in asteroids) { |
2 |
asteroid.x += 3; |
3 |
asteroid.y += 4; |
4 |
if (asteroid.x < -asteroid.width) { |
5 |
asteroid.x = stage.stageWidth; |
6 |
}
|
7 |
else if (asteroid.x > stage.stageWidth) { |
8 |
asteroid.x = -asteroid.width; |
9 |
}
|
10 |
if (asteroid.y < -asteroid.height) { |
11 |
asteroid.y = stage.stageHeight; |
12 |
}
|
13 |
else if (asteroid.y > stage.stageHeight) { |
14 |
asteroid.y = -asteroid.height; |
15 |
}
|
16 |
}
|
We do four checks using if statements. We check the x position of each asteroid twice, once for the left edge and once for the right edge. Then we check the y position twice, for the top and the bottom. In all cases, we are checking to see if the MovieClip is completely off the edge, and then repositioning it so that it's completely off the opposite edge.
For example, take the first if statement. If the x value is less than 0, the asteroid is starting to go off the screen. But it might still be partially visible, so we need to check that the x is less than 0 minus the width of the clip. That is, if the clip is 100 pixels wide, we need to make sure that the asteroid is at least an x of -100 before it's fully obscured, at which point we can take action. Remember, the registration point should be in the top left corner of the clip.
So when we go to reposition an asteroid that has moved too far to the left, we simply plop it down at the stage width. This should be just off to the right. But the asteroid is moving to the left, so we'll see it start to enter the stage on the next frame.
If you look through the remaining three conditions, you'll see a pattern emerge from this logic.
Go ahead and test now. While the asteroids are still glued together, they at least wrap around the edges.
Step 17: Move Asteroids Individually
To get the asteroids to have independent motion, each one needs to have its own velocity, not the "3" and "4" that we plugged in earlier. To do this, we'll use a Point object to represent a single asteroid's x and y velocities. We'll associate each Point with its asteroid clip by using a Dictionary. To start, create the Dictionary outside of the onTick function and before the while loop that creates the asteroids.
1 |
var velocities:Dictionary = new Dictionary(); |
Then, right after that, we'll put values into the Dictionary. We'll loop over the asteroids Array (using for each again), and use the asteroid clip as the key, and a new Point as the value. The Point's x and y values will be determined using random numbers.
Then we'll put values into the Dictionary. We'll add a line to the while loop that sets up the asteroids. Below is the full loop; the three new lines are at the end.
1 |
while (asteroids.length < 4) { |
2 |
var asteroid:MovieClip = new Asteroid(); |
3 |
asteroid.x = 450 + Math.random() * 200 - 100; |
4 |
asteroid.y = Math.random() * (stage.stageHeight - asteroid.height); |
5 |
addChild(asteroid); |
6 |
asteroids.push(asteroid); |
7 |
var xVel:Number = Math.random() * 6 - 3; |
8 |
var yVel:Number = Math.random() * 6 - 3; |
9 |
velocities[asteroid] = new Point(xVel, yVel); |
10 |
}
|
Now, in the onTick function, we can get these velocities back out of the Dictionary, using the asteroid key, and apply the velocity values to the position:
1 |
for each (var asteroid:MovieClip in asteroids) { |
2 |
var vel:Point = velocities[asteroid]; |
3 |
asteroid.x += vel.x; |
4 |
asteroid.y += vel.y; |
5 |
// Asteroids loop continues...
|
If this step is a little confusing, remember that I covered Dictionaries in my tutorial on conditionals.
Here is the whole script as it should stand currently:
1 |
win.visible = false; |
2 |
|
3 |
var asteroids:Array = []; |
4 |
var bullets:Array = []; |
5 |
var velocities:Dictionary = new Dictionary(); |
6 |
|
7 |
while (asteroids.length < 4) { |
8 |
var asteroid:MovieClip = new Asteroid(); |
9 |
asteroid.x = 450 + Math.random() * 200 - 100; |
10 |
asteroid.y = Math.random() * (stage.stageHeight - asteroid.height); |
11 |
addChild(asteroid); |
12 |
asteroids.push(asteroid); |
13 |
var xVel:Number = Math.random() * 6 - 3; |
14 |
var yVel:Number = Math.random() * 6 - 3; |
15 |
velocities[asteroid] = new Point(xVel, yVel); |
16 |
}
|
17 |
|
18 |
addEventListener(Event.ENTER_FRAME, onTick); |
19 |
|
20 |
function onTick(e:Event):void { |
21 |
ship.y = stage.mouseY; |
22 |
|
23 |
for each (var asteroid:MovieClip in asteroids) { |
24 |
var vel:Point = velocities[asteroid]; |
25 |
asteroid.x += vel.x; |
26 |
asteroid.y += vel.y; |
27 |
if (asteroid.x < -asteroid.width) { |
28 |
asteroid.x = stage.stageWidth; |
29 |
}
|
30 |
else if (asteroid.x > stage.stageWidth) { |
31 |
asteroid.x = -asteroid.width; |
32 |
}
|
33 |
if (asteroid.y < -asteroid.height) { |
34 |
asteroid.y = stage.stageHeight; |
35 |
}
|
36 |
else if (asteroid.y > stage.stageHeight) { |
37 |
asteroid.y = -asteroid.height; |
38 |
}
|
39 |
}
|
40 |
}
|
Test the movie now, and the asteroids should each have their own path of motion. You may wish to tweak the values used to set up the Points to try and adjust the nature of the motion.
Step 18: Fire a Bullet
Now, let's get to fragging! When the player clicks their mouse button, we'll fire off a bullet. We need to start by setting up an event listener for MOUSE_DOWN. Add the following line back where you add the ENTER_FRAME listener:
1 |
stage.addEventListener(MouseEvent.MOUSE_DOWN, fire); |
And then add that fire function at the very end of the script:
1 |
function fire(me:MouseEvent):void { |
2 |
var b:Bullet = new Bullet(); |
3 |
b.x = 110; |
4 |
b.y = ship.y; |
5 |
addChild(b); |
6 |
bullets.push(b); |
7 |
}
|
This will call the "fire" function every time the mouse button is pressed down. Why did we add the listener to the stage? Because the MOUSE_DOWN event only fires if the mouse cursor is over the display object when the event occurs. So, if we had added the event to the ship, we'd get a lot of missed fires, anytime the mouse was not directly over the ship. Using the stage assures that the mouse will always be over something (unless you actually move outside the Flash Player).
The fire function is a little lengthy but relatively straight forward. Here is where we take advantage of the linkage identifier set up for the Bullet symbol in the library. We create a new Bullet clip, position it, and then use addChild() to make sure we can actually see it. Lastly, we make sure we keep track of the bullet by pushing it into the bullets Array.
If you find this logic a bit confusing, don't worry, we'll be covering display list topics in our next installment of AS3 101. This code will make perfect sense if you can wait a few weeks.
If you test now, you should see that it works, although the bullets don't move. Let's change that next.
Step 19: Move the Bullets
Back in the onTick function, we need to loop over the bullets Array and move each bullet. This will be reminiscent of moving the asteroids, but we'll need to treat things a little differently. For one, we know exactly how to move every bullet, so there is no need to track velocities. As the bullets all move horizontally to the right, there is no need to check any stage boundaries other than the right edge. For reasons that will be clearer in the next step, we'll also go for the "traditional" for loop in lieu of the for each loop. We will need that Array index, and finer control over the loop, so we'll stick with the classics.
Add this block to the end of your onTick function:
1 |
for (var i:int = 0; i < bullets.length; i++) { |
2 |
var bullet:Bullet = bullets[i]; |
3 |
bullet.x += 3; |
4 |
if (bullet.x > stage.stageWidth + 2) { |
5 |
trace("Need to remove bullet: " + bullet); |
6 |
}
|
7 |
}
|
This should be fairly self-explanatory; all we're doing is getting every bullet out of the bullets Array and moving it 3 pixels to the right. Then, if the x is so high that it's off the right, we trace a message. We'll do our actual clean up in the next step.
Test the movie, and the bullets should move. But just because the bullets leave the stage, they aren't gone! The traces should prove that. They're still in the Array, still on stage, and still moving. We need to remove theses bullets from both the stage and the Array.
Step 20: Clean up Stray Bullets
Remove that trace and replace it with:
1 |
removeChild(bullet); |
This will remove the movie clip from the stage, which is good. But we also need to remove the item from the Array, otherwise the Array will continue to grow and grow, and we'll keep looping over more and more bullets, and the first bullets we fire will keep moving so far, even though they can't be seen. We need to make sure we remove all references to the stray bullet, so that the garbage collector can remove it fully from the application. This helps keep memory usage manageable.
It's easy to remove an item from an Array, as long as you know its index (and you can always use the Array method indexOf to find that). We just use the splice method. The second parameter of splice is how many items to delete from the Array, starting with the index specified in the first parameter. So...
1 |
bullets.splice(i, 1); |
...when added just below the removeChild line, will remove the same bullet clip from the Array, as well.
However, there is a problem, and this is a common one, even for experienced developers. The problem is subtle, but enough to throw off more complicated programs. Let's say we have 10 bullets in the Array (indices 0 to 9), and we determined that the bullet at index 2 needs to be removed. The i variable is 2. So we remove index 2. Indices 3 through 9 slide forward to fill in the gap, becoming indices 2 to 8.
However, the loop rolls on, and i is incremented. i is now 3, and then we get the third item out of the Array. If you were paying attention, you will notice that the item directly after the item that was removed never gets any attention. It was at index 3 when i was 2, and then it slid down to index 2 just as i became 3. Now, in our case it will just get picked up in the next execution of the onTick function, but this is a tricky thing that has stymied many-a-programmer.



To fix this, we can simply decrement i at the very end of the loop, after removing the bullet.
1 |
i-- |
Using our previous example, this would put i from 2 back down to 1, just after item 3 became item 2. Then i gets incremented as the loop runs again, and is now 2. It can then be used to pick up the next item.
Note, too, that for this to work, we need to do exactly what I advised against and use the Array length directly in the loop declaration. If we had stored the length in a variable, like I suggested, it wouldn't be able to pick up the changes in the Array length. In most cases, you don't modify the length of the Array as you loop over it, so the advice I gave is sound. In this case, however, it would cause more problems than it would solve.
The entire bullet loop should look like this:
1 |
for (var i:int = 0; i < bullets.length; i++) { |
2 |
var bullet:Bullet = bullets[i]; |
3 |
bullet.x += 3; |
4 |
if (bullet.x > stage.stageWidth - 100 + 2) { |
5 |
removeChild(bullet); |
6 |
bullets.splice(i, 1); |
7 |
i--; |
8 |
}
|
9 |
}
|
Test the movie to make sure everything works. If all went well, things appear exactly the way they did last time, only now we know our memory management is sound.
Step 21: Check For Collisions
To keep things simple, we won't worry about collisions between asteroids and the ship. We'll just focus on the bullets and the asteroids.
We'll once more add code to the onTick function. After the bullet movement loop we worked on in the last two steps, add this code:
1 |
for (i = 0; i < asteroids.length; i++) { |
2 |
asteroid = asteroids[i]; |
3 |
for (var j:int = 0; j < bullets.length; j++) { |
4 |
bullet = bullets[j]; |
5 |
if (asteroid.hitTestObject(bullet)) { |
6 |
removeChild(asteroid); |
7 |
removeChild(bullet); |
8 |
asteroids.splice(i, 1); |
9 |
bullets.splice(j, 1); |
10 |
i--; |
11 |
break; |
12 |
}
|
13 |
}
|
14 |
}
|
That's quite a bit of code, so let's break it down.
We start by looping over the asteroids Array. Because we are checking for collisions and potentially removing asteroids in the course of the loop, we are using a standard for loop while eschewing the variable to store the Array length.
And then we get a reference to the asteroid for this iteration. And then we start a nested loop. Notice the use of the variable j to keep this nested loop's counter variable separate from the outer loop's. And the first thing done in the nested loop is get a reference to a bullet.
What this accomplishes so far is the ability to mix and match every asteroid-bullet combination. We don't care about bullet-to-bullet, or asteroid-to-asteroid, so between every combination of i and j we should be able to compare every bullet to every asteroid.
And comparison is the next step: we use a simple hitTestObject call on the asteroid to see if it's hitting the bullet. If it is, we execute the rest of the code owned by the condition. Otherwise, we just keep rolling through the loop and check the next combination.
If we do get a hit, we use removeChild to visually remove both of the elements. And then we use splice to remove them from their respective Arrays.
Finally, we decrement i, as discussed earlier. But why don't we decrement j? Because if we found a hit for the asteroid, then clearly no other bullet can hit it. There is no point in continuing to loop over the bullets Array to look for hits between an already-declared-dead asteroid and another bullet. So instead, we make use of the break keyword and stop the bullet loop where it is. That lets us pick up the outer loop again and check the next asteroid.
Step 22: Did We Win?
One last step, and that's to check to see if we won. We can tell by checking the asteroids Array's length after going through the preceding step's loop. If the length is 0, then we have no more asteroids and we've won!
1 |
if (asteroids.length == 0) { |
2 |
win.visible = true; |
3 |
setChildIndex(win, numChildren - 1); |
4 |
removeEventListener(Event.ENTER_FRAME, onTick); |
5 |
}
|
At this point, we can remove the ENTER_FRAME listener, and show the congratulatory placard by turning it's visible property to true, and making sure it is on top of all other visual elements by using setChildIndex.
Below is the complete script, for reference:
1 |
win.visible = false; |
2 |
|
3 |
var asteroids:Array = []; |
4 |
var bullets:Array = []; |
5 |
var velocities:Dictionary = new Dictionary(); |
6 |
|
7 |
while (asteroids.length < 4) { |
8 |
var asteroid:MovieClip = new Asteroid(); |
9 |
asteroid.x = 450 + Math.random() * 200 - 100; |
10 |
asteroid.y = Math.random() * (stage.stageHeight - asteroid.height); |
11 |
addChild(asteroid); |
12 |
asteroids.push(asteroid); |
13 |
var xVel:Number = Math.random() * 6 - 3; |
14 |
var yVel:Number = Math.random() * 6 - 3; |
15 |
velocities[asteroid] = new Point(xVel, yVel); |
16 |
}
|
17 |
|
18 |
|
19 |
addEventListener(Event.ENTER_FRAME, onTick); |
20 |
stage.addEventListener(MouseEvent.MOUSE_DOWN, fire); |
21 |
|
22 |
function onTick(e:Event):void { |
23 |
ship.y = stage.mouseY; |
24 |
|
25 |
for each (var asteroid:MovieClip in asteroids) { |
26 |
var vel:Point = velocities[asteroid]; |
27 |
asteroid.x += vel.x; |
28 |
asteroid.y += vel.y; |
29 |
if (asteroid.x < -asteroid.width) { |
30 |
asteroid.x = stage.stageWidth; |
31 |
}
|
32 |
else if (asteroid.x > stage.stageWidth) { |
33 |
asteroid.x = -asteroid.width; |
34 |
}
|
35 |
if (asteroid.y < -asteroid.height) { |
36 |
asteroid.y = stage.stageHeight; |
37 |
}
|
38 |
else if (asteroid.y > stage.stageHeight) { |
39 |
asteroid.y = -asteroid.height; |
40 |
}
|
41 |
}
|
42 |
|
43 |
|
44 |
for (var i:int = 0; i < bullets.length; i++) { |
45 |
var bullet:Bullet = bullets[i]; |
46 |
bullet.x += 3; |
47 |
if (bullet.x > stage.stageWidth + 2) { |
48 |
removeChild(bullet); |
49 |
bullets.splice(i, 1); |
50 |
i--; |
51 |
}
|
52 |
}
|
53 |
|
54 |
for (i = 0; i < asteroids.length; i++) { |
55 |
asteroid = asteroids[i]; |
56 |
for (var j:int = 0; j < bullets.length; j++) { |
57 |
bullet = bullets[j]; |
58 |
if (asteroid.hitTestObject(bullet)) { |
59 |
removeChild(asteroid); |
60 |
removeChild(bullet); |
61 |
asteroids.splice(i, 1); |
62 |
bullets.splice(j, 1); |
63 |
i--; |
64 |
break; |
65 |
}
|
66 |
}
|
67 |
}
|
68 |
|
69 |
if (asteroids.length == 0) { |
70 |
win.visible = true; |
71 |
setChildIndex(win, numChildren - 1); |
72 |
removeEventListener(Event.ENTER_FRAME, onTick); |
73 |
}
|
74 |
|
75 |
|
76 |
}
|
77 |
|
78 |
|
79 |
|
80 |
function fire(me:MouseEvent):void { |
81 |
var b:Bullet = new Bullet(); |
82 |
b.x = 110; |
83 |
b.y = ship.y; |
84 |
bullets.push(b); |
85 |
addChild(b); |
86 |
}
|
Step 23: In Summary...
By now you should be a regular pro at loops. Combine that with your knowledge of Arrays and you're well on your way to handling dynamic content. In fact, that's where things are heading with AS3 101. Next up is the display list, and after that XML. With everything you've learned so far in this series, along with those next two, you'll wonder how you ever got along building Flash sites before. Data-driven content is a liberating experience.
We never got into a subject known as iterators, which are objects dedicated to traversing Arrays and other collections without looping as we've done it here. They have the advantage of keeping the Array itself out of sight and protected from destructive hands, but they're a bit of an advanced subject. I mention it because other languages do provide iterator or enumerator objects, and in those languages it's more common to loop over a collection using the iterator. It's possible (and even advisable, in my opinion) to create your own iterators in ActionScript. But we needed to draw the line somewhere for this tutorial.