Oct 30, 2011
Posted on Oct 30, 2011 in Hints and Tips | 10 comments
In the first part of this series, you learned the basics of using JavaScript and the canvas element to make a very simple HTML5 avoider game. But it’s too simple – the single enemy doesn’t even move – there’s no challenge! In this tutorial, you’ll learn how to create a never-ending stream of enemies, all falling from the top of the screen.
Refresher
In the first part of the tutorial we covered quite a few concepts: drawing images to the screen, interacting between HTML and JavaScript, detecting mouse actions, and the if statement. You can download the source files here if you want to dive in to this part of the tutorial, though I recommend reading all parts in order.
Our game’s HTML page contains a canvas element, which triggers a JavaScript function called drawAvatar() when it is clicked. That function is inside a separate file called main.js, and it does two things:
- Draws a copy of
avatar.png to the canvas.
- Sets up an event listener to call another function, called
redrawAvatar(), whenever the mouse moves over the canvas.
The redrawAvatar() function is also inside main.js; unlike drawAvatar() it accepts a parameter – called mouseEvent – which is automatically passed to it by the event listener. This mouseEvent contains information about the mouse’s position. The function does four things:
- Clears the canvas.
- Draws a copy of
avatar.png to the canvas, at the mouse’s position.
- Draws a copy of
enemy.png to the canvas, at a specified position.
- Checks to see whether the avatar and enemy are close enough to each other to be overlapping, and displays an
alert() if so.
All clear? If not, try the warm up challenge.
Warm Up Challenge
If it’s been a while since you read the first part of the series (or if you just want to check that you understand what’s going on), have a go at these little exercises. They’re completely optional and separate to the actual tutorial, so I recommend working on a copy of your project rather than the original. You can complete all of these exercises using only information from the first part of the series.
Easy
Remember that drawImage() works like a potato stamp. Use it to create an unbroken ring of enemies around the edge of your canvas, like this:
(If you get bored of copying and pasting all those statements, feel free to make it a smaller ring – you could resize the canvas, too, if you like.)
Medium
Make the “you hit the enemy” alert appear whenever the avatar hits the edge of the ring. (To test this, remember that you can hit Enter to dismiss the alert; you don’t have to click OK.)
Hard
That alert will come up when you try to move your mouse from outside the canvas to inside it, which is really annoying if you’ve already clicked the canvas. Make it possible to move into the canvas without triggering the alert – but once inside, make the alert appear whenever the avatar touches the ring of enemies.
Make the Enemy Move
We’re going to make the enemy fall down from the top of the screen. For now, we’ll focus on making it move rather than on detecting a collision, so “comment out” the lines in redrawEnemy() that deal with collisions, like so:
//if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
// alert("You hit the enemy!");
//}
Remember: two forward slashes tell the browser “ignore everything on this line from here on”. Earlier on we used this to create comments – little reminders of what certain bits of code do – but here we’re using it for another purpose: stopping certain bits of code from running without completely deleting them. This makes it easy for us to put the code back in later.
At the moment, the enemy is redrawn, in the same position, whenever we move the mouse:
gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150);
Do you remember the Math.random() function from the first part of the tutorial? It returns a random number between zero and one; multiplying it by 300 (the height of the canvas) would give us a number between 0 and 300. What happens if we draw the enemy at a random y-position between 0 and 300 every time the mouse was moved? Let’s try it out; modify that line like so:
gameCanvas.getContext("2d").drawImage(enemyImage, 250, Math.random() * 300);
Try it out here!
It looks like the enemy is moving randomly up and down a certain line, but only when the mouse is moved. Of course, it’s not actually moving; it’s “teleporting” from one position to the next, but this gives the illusion of movement.
We’d get a better illusion if it only moved in one direction. We can achieve this by making sure the enemy’s y-position only increases, and never decreases.
Consider the following code:
function redrawAvatar(mouseEvent) {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
var enemyImage = new Image();
var enemyY = 0;
avatarImage.src = "img/avatar.png";
enemyImage.src = "img/enemy.png";
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY);
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
//if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
// alert("You hit the enemy!");
//}
}
See what I’m doing? I set the value of enemyY to 0 at the top of the function, then increased it by one pixel before drawing the enemy. In this way, I’m aiming to make the enemy’s y-position increase by one pixel every time redrawAvatar() is run.
However, there’s a flaw in my logic. The line var enemyY = 0; will reset the enemyY variable to 0 every time redrawAvatar() is run, which means that it’ll always be drawn at a y-position of 1 (because it’ll be increased at line 12).
We need to only set it to 0 once. What if we do that in drawEnemy()? After all, that function is only run once:
function drawAvatar() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
var enemyY = 0;
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100);
gameCanvas.addEventListener("mousemove", redrawAvatar);
}
function redrawAvatar(mouseEvent) {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
var enemyImage = new Image();
avatarImage.src = "img/avatar.png";
enemyImage.src = "img/enemy.png";
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY);
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
//if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
// alert("You hit the enemy!");
//}
}
Try it out here!
Unfortunately, this doesn’t work at all. The problem lies in a concept called scope. If you use the var keyword to define a variable within a function, then the variable will only be accessible within that function. This means that our redrawAvatar() function cannot access the same enemyY variable that was defined in drawAvatar().
However, if we define a variable outside of all functions, it can be accessed by any one of them! So, try this:
var enemyY = 0;
function drawAvatar() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100);
gameCanvas.addEventListener("mousemove", redrawAvatar);
}
function redrawAvatar(mouseEvent) {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
var enemyImage = new Image();
avatarImage.src = "img/avatar.png";
enemyImage.src = "img/enemy.png";
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY);
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
//if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
// alert("You hit the enemy!");
//}
}
Try it out here!
It works – the enemy slides down the screen. However, it only does so while we’re moving the mouse. That’s an interesting game mechanic, but it’s not what I was aiming for.
Make the Enemy Move on Its Own
It’d be much better if the enemy appeared to move of its own accord – meaning, it moves regardless of whether or not the player is moving the mouse. We can do this by triggering its movement (its “teleportations”) based on time rather than on mouse movement.
We can do this by using the setInterval() function. It works like this:
setInterval(functionName, period);
Here, functionName is the name of a function we want to run over and over again, and period is the amount of time (in milliseconds) we want to pass between each call to that function.
Let’s see how this looks:
var enemyY = 0;
function drawAvatar() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100);
gameCanvas.addEventListener("mousemove", redrawAvatar);
setInterval(redrawEnemy, 1000);
}
function redrawAvatar(mouseEvent) {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
avatarImage.src = "img/avatar.png";
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY);
//if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
// alert("You hit the enemy!");
//}
}
function redrawEnemy() {
var enemyImage = new Image();
enemyImage.src = "img/enemy.png";
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
}
I’ve moved all the code that deals with moving and drawing the enemy to the new redrawEnemy() function, and I’ve set it to be called every 1,000 milliseconds (every second) using a setInterval() call in drawAvatar(). (Unlike when using an event listener, no parameters automatically get passed to redrawEnemy() when we call it from setInterval().)
Try it out here! Click the canvas, then don’t move your mouse.
There are a few things wrong with this:
- The enemy leaves a trail – this is because the canvas isn’t cleared in
redrawEnemy().
- The enemy moves really slowly – perhaps 1000 milliseconds is too long to wait.
- When the avatar is moved, the enemy disappears – this is because the enemy is only drawn in
redrawEnemy(); in redrawAvatar() the canvas is cleared and the avatar is redrawn, but not the enemy.
Let’s fix these one at a time. First, we’ll clear the canvas in redrawEnemy():
function redrawEnemy() {
var enemyImage = new Image();
enemyImage.src = "img/enemy.png";
gameCanvas.width = 400; //this erases the contents of the canvas
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
}
Try it out here!
Hm. Now the avatar disappears whenever the enemy is drawn, and vice-versa. Of course, this makes sense; we clear the canvas in both redrawEnemy() and redrawAvatar(), but never draw both the enemy and the avatar at the same time.
What if we moved the enemy in redrawEnemy() – by increasing the value of enemyY – but actually drew it in redrawAvatar()?
var enemyY = 0;
function drawAvatar() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100);
gameCanvas.addEventListener("mousemove", redrawAvatar);
setInterval(redrawEnemy, 1000);
}
function redrawAvatar(mouseEvent) {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
var enemyImage = new Image();
enemyImage.src = "img/enemy.png";
avatarImage.src = "img/avatar.png";
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
//if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
// alert("You hit the enemy!");
//}
}
function redrawEnemy() {
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
}
Try it out here!
It sort of works, but we’re back to that problem where the enemy only moves while you’re moving the mouse. However, this time it’s slightly different; to make this more obvious, we can increase the enemy’s speed by reducing the period. Set it to 25 (that’s 1/40th of a second, meaning redrawEnemy() will run 40 times per second):
setInterval(redrawEnemy, 25);
Try it out here!
Compare this with the earlier version where the enemy only moved when the mouse was moving. See the difference? In the new one, the enemy’s position keeps changing, but it does so “behind the scenes”; the actual image of the enemy only moves when the mouse is moved. If you wait a second or so before moving the mouse, the enemy image jumps down the screen to catch up with its actual position.
Separating the enemy’s actual position from the enemy’s image’s position like this is going to let us solve our problem.
Before we move on, are you getting confused by the function names? I am. redrawEnemy() isn’t actually drawing the enemy at all. Let’s rename them to something a bit easier to keep track of.
drawAvatar() is run when we start the game, and it sets everything up, so let’s rename it to setUpGame()
redrawAvatar() is run whenever the mouse moves, so let’s rename it to handleMouseMovement()
redrawEnemy() is run every fraction of a second; it’s as if there’s a clock that ticks 40 times a second, and each tick triggers the function. So, let’s rename it to handleTick()
Don’t forget you have to rename all the references to the functions as well, in the event listener and the setInterval(). Here’s what it’ll look like:
var enemyY = 0;
function setUpGame() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100);
gameCanvas.addEventListener("mousemove", handleMouseMovement);
setInterval(handleTick, 25);
}
function handleMouseMovement(mouseEvent) {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
var enemyImage = new Image();
enemyImage.src = "img/enemy.png";
avatarImage.src = "img/avatar.png";
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
//if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
// alert("You hit the enemy!");
//}
}
function handleTick() {
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
}
(You’ll also need to change the HTML page, so that the canvas’s onclick attribute is "setUpGame();" rather than "drawAvatar();".
I think this makes it easier to see what’s going on:
- When the mouse moves, we move the avatar’s position, draw the avatar in its current position, and draw the enemy in its current position.
- When the clock ticks, we move the enemy’s position.
- We need to draw the enemy and the avatar at the same time (i.e. in the same function).
- If we only draw the enemy when the mouse moves, then the enemy’s movement is not smooth.
This makes it easier in turn to see a possible solution:
- When the mouse moves, move the avatar’s position.
- When the clock ticks, move the enemy’s position, draw the avatar in its current position, and draw the enemy in its current position.
Let’s implement that. All we need to do is move the drawing code from handleMouseMovement() to handleTick(), right? Like this:
var enemyY = 0;
function setUpGame() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100);
gameCanvas.addEventListener("mousemove", handleMouseMovement);
setInterval(handleTick, 25);
}
function handleMouseMovement(mouseEvent) {
//if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
// alert("You hit the enemy!");
//}
}
function handleTick() {
var avatarImage = new Image();
var enemyImage = new Image();
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
enemyImage.src = "img/enemy.png";
avatarImage.src = "img/avatar.png";
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
}
Hmm. That’s not right. We’ve got nothing left in handleMouseMovement(). Ah – but that’s because we haven’t separated the avatar’s image’s position from the avatar’s actual position, like we did with the enemy. So let’s do that:
var enemyY = 0;
var avatarX = 0;
var avatarY = 0;
function setUpGame() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100);
gameCanvas.addEventListener("mousemove", handleMouseMovement);
setInterval(handleTick, 25);
}
function handleMouseMovement(mouseEvent) {
avatarX = mouseEvent.offsetX;
avatarY = mouseEvent.offsetY;
//if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
// alert("You hit the enemy!");
//}
}
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
var enemyImage = new Image();
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
enemyImage.src = "img/enemy.png";
avatarImage.src = "img/avatar.png";
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
}
We have to create new variables to store the avatar’s actual x- and y-positions, and define those variables outside of any function, so that we can access them from anywhere.
Try it out here!
This works! (If it’s a little jerky, then try closing some tabs or restarting Chrome; that worked for me.) We now have a moving enemy. Took a while to get there, but the actual code we’ve ended up with isn’t too complex, I hope you’ll agree.
If you want a challenge, try re-introducing the collision detection alert box. Don’t worry if you have troubles; we’ll go through this again a bit later.
In the mean time, we’ll look at a problem you probably haven’t come across yet…
Loading the Images From a Server
As I mentioned in the first part of this series, if you put your game onto a web server as it is now, it won’t work correctly, even though they work fine when running from your computer. My demos work because I’ve made a slight modification to the code; here’s how the game runs without that code:
Try it out here.
What’s going on? Well, it’s to do with the images. Every time the clock ticks, we create a new image and set its source to point to an actual image file. This doesn’t cause problems when the image file is on your hard drive, but when it’s on the Internet, the page might try to download the image before drawing it – leading to the flickering that we can see in the demo.
Perhaps you can already guess at a solution, based on what we’ve done in this tutorial so far. Just like with the enemy’s and avatar’s positions, we can move the enemy’s and avatar’s images outside of the functions, and re-use them over and over again, without having to define them and set their values in the handleTick() function each time.
Take a look:
var enemyY = 0;
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
function setUpGame() {
var gameCanvas = document.getElementById("gameCanvas");
avatarImage = new Image();
enemyImage = new Image();
enemyImage.src = "img/enemy.png";
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100);
gameCanvas.addEventListener("mousemove", handleMouseMovement);
setInterval(handleTick, 25);
}
function handleMouseMovement(mouseEvent) {
avatarX = mouseEvent.offsetX;
avatarY = mouseEvent.offsetY;
//if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
// alert("You hit the enemy!");
//}
}
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
}
Try it out here – no flickering!
In case you’re wondering: we could have moved lines 9-12 outside of the functions as well. I chose to put them in setUpGame() simply because they seemed to be more about, well, setting up the game.
Make Another Enemy
It’s actually really easy to make another enemy appear on the screen. Remember that images are like potato stamps; that means there’s nothing stopping us from drawing the enemy image onto the canvas in two different places within the same tick:
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
gameCanvas.getContext("2d").drawImage(enemyImage, 100, enemyY);
}

Click to try it out.
Simple!
You can put them at different heights, like so:
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
gameCanvas.getContext("2d").drawImage(enemyImage, 100, enemyY - 50);
}

Click to try it out.
This is a bit messy, though. Instead, how about just creating an enemyY2 variable?
var enemyY = 0;
var enemyY2 = -50;
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
//...
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
enemyY2 = enemyY2 + 1;
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
gameCanvas.getContext("2d").drawImage(enemyImage, 100, enemyY2);
}
Now you can set the initial positions of enemyY and enemyY2 to whatever you want, without having to change the code in handleTick().
Make Five Enemies
Try extending what we’ve just done so that there are five enemies, all with different starting points. Take a look at my code if you need to. Here’s a hint: you only need to add code outside of the functions and inside the handleTick() function – no need to touch setUpGame() or handleMouseMovement().
var enemyY = 0;
var enemyY2 = -50;
var enemyY3 = -75;
var enemyY4 = -120;
var enemyY5 = -250;
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
//...
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
enemyY = enemyY + 1; //increase enemyY variable by one pixel. If enemyY is 10, then enemyY + 1 is 11, etc.
enemyY2 = enemyY2 + 1;
enemyY3 = enemyY3 + 1;
enemyY4 = enemyY4 + 1;
enemyY5 = enemyY5 + 1;
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyY);
gameCanvas.getContext("2d").drawImage(enemyImage, 130, enemyY2);
gameCanvas.getContext("2d").drawImage(enemyImage, 300, enemyY3);
gameCanvas.getContext("2d").drawImage(enemyImage, 50, enemyY4);
gameCanvas.getContext("2d").drawImage(enemyImage, 190, enemyY5);
}

Click to try it out.
Make Ten Enemies
Oh, this is going to get tedious, right? Maintaining all those enemies, and adding three lines of code for each one. Yuck.
Allow me to introduce arrays. Take a look at this:
var enemyYPositions = [0, -50, -75, -120, -250];
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
//...
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
enemyYPositions[0] = enemyYPositions[0] + 1;
enemyYPositions[1] = enemyYPositions[1] + 1;
enemyYPositions[2] = enemyYPositions[2] + 1;
enemyYPositions[3] = enemyYPositions[3] + 1;
enemyYPositions[4] = enemyYPositions[4] + 1;
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyYPositions[0]);
gameCanvas.getContext("2d").drawImage(enemyImage, 130, enemyYPositions[1]);
gameCanvas.getContext("2d").drawImage(enemyImage, 300, enemyYPositions[2]);
gameCanvas.getContext("2d").drawImage(enemyImage, 50, enemyYPositions[3]);
gameCanvas.getContext("2d").drawImage(enemyImage, 190, enemyYPositions[4]);
}
This gives us the exact same result as before, but:
- All of the enemies’ y positions are defined in a single line, and
- We get an easy way to refer to any of these positions:
enemyYPosition[enemyNumber].
This type of variable is called an array; it’s a way of holding a list of values (or even of other variables), and lets us retrieve any item from that list using a number. Note that the first element of an array is number 0, the second value is number 1, and so on – we call arrays “zero-based” for this reason.
Looping
Now take a look at this section of code:
enemyYPositions[0] = enemyYPositions[0] + 1;
enemyYPositions[1] = enemyYPositions[1] + 1;
enemyYPositions[2] = enemyYPositions[2] + 1;
enemyYPositions[3] = enemyYPositions[3] + 1;
enemyYPositions[4] = enemyYPositions[4] + 1;
We’re doing the same thing, over and over again, to different items in the array. Each line of code is the same as all of the others, except that the number inside the square brackets changes. This is great, because we can write code to say “do this same thing five times, but changing one number each time”. For example:
var currentNumber = 0;
while (currentNumber < 5) {
alert(currentNumber);
currentNumber = currentNumber + 1;
}
If you put this in your JS file (in setUpGame(), for example), it would make the page display five alert boxes: the first would say “0″; the second would say “1″; and so on up to “4″. In other words, it’s equivalent to doing this:
var currentNumber = 0;
alert(currentNumber);
currentNumber = currentNumber + 1;
alert(currentNumber);
currentNumber = currentNumber + 1;
alert(currentNumber);
currentNumber = currentNumber + 1;
alert(currentNumber);
currentNumber = currentNumber + 1;
alert(currentNumber);
currentNumber = currentNumber + 1;
This is because the while statement acts like a repeated if statement. Remember, if works like this:
if (condition) {
outcome;
}
“If condition is true, then run outcome.”
while works like this:
while (condition) {
outcome;
}
“As long as condition remains true, keep running outcome.”
It’s a subtle difference, but a really important one. An if block will run just once, if the condition is true; a while block will run over and over again until the condition stops condition stops being true.
For this reason, the outcome – the code that’s run inside the while block – usually contains some code that will, eventually, cause condition to stop being true; if it didn’t the code would just repeat itself forever. In our alert box example, we increased the value of currentNumber until it “currentNumber < 5" was no longer true (when currentNumber reached 5, it was no longer less than 5, which is why we never see an alert box containing the number 5).
Running code over and over again like this is called "looping", and the while block is called a "loop". Let's now take this code:
enemyYPositions[0] = enemyYPositions[0] + 1;
enemyYPositions[1] = enemyYPositions[1] + 1;
enemyYPositions[2] = enemyYPositions[2] + 1;
enemyYPositions[3] = enemyYPositions[3] + 1;
enemyYPositions[4] = enemyYPositions[4] + 1;
...and put it into a loop:
var currentEnemyNumber = 0;
while (currentEnemyNumber < 5) {
enemyYPositions[currentEnemyNumber] = enemyYPositions[currentEnemyNumber] + 1;
}
Great! Or is it?
Actually, that's not quite right: we're not changing the value of currentEnemyNumber. This means that we'll just increase the value of enemyYPositions[0] over and over again, forever, without ever changing the other enemies' y-positions or ever exiting the loop.
So, we need to do this:
var currentEnemyNumber = 0;
while (currentEnemyNumber < 5) {
enemyYPositions[currentEnemyNumber] = enemyYPositions[currentEnemyNumber] + 1;
currentEnemyNumber = currentEnemyNumber + 1;
}
Now it's great.
Can we apply the same thinking to our other repetitive code? I'm referring to this:
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyYPositions[0]);
gameCanvas.getContext("2d").drawImage(enemyImage, 130, enemyYPositions[1]);
gameCanvas.getContext("2d").drawImage(enemyImage, 300, enemyYPositions[2]);
gameCanvas.getContext("2d").drawImage(enemyImage, 50, enemyYPositions[3]);
gameCanvas.getContext("2d").drawImage(enemyImage, 190, enemyYPositions[4]);
Unfortunately, something like this won't work:
var currentEnemyNumber = 0;
while (currentEnemyNumber < 5) {
gameCanvas.getContext("2d").drawImage(enemyImage, 250, enemyYPositions[currentEnemyNumber]);
currentEnemyNumber = currentEnemyNumber + 1;
}
...because not all enemies have an x-position of 250. But we can make it work, if we move all the enemies x-positions to another array:
var enemyXPositions = [250, 130, 300, 50, 190];
var enemyYPositions = [0, -50, -75, -120, -250];
//...
var currentEnemyNumber = 0;
while (currentEnemyNumber < 5) {
gameCanvas.getContext("2d").drawImage(enemyImage, enemyXPositions[currentEnemyNumber], enemyYPositions[currentEnemyNumber]);
currentEnemyNumber = currentEnemyNumber + 1;
}
This has the added benefit of keeping all the enemies' x- and y-positions in one neat location, rather than spread out across several lines.
Let's look at the code in context:
var enemyYPositions = [0, -50, -75, -120, -250];
var enemyXPositions = [250, 130, 300, 50, 190];
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
//...
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
var currentEnemyNumber = 0;
while (currentEnemyNumber < 5) {
enemyYPositions[currentEnemyNumber] = enemyYPositions[currentEnemyNumber] + 1;
currentEnemyNumber = currentEnemyNumber + 1;
}
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
currentEnemyNumber = 0;
while (currentEnemyNumber < 5) {
gameCanvas.getContext("2d").drawImage(enemyImage, enemyXPositions[currentEnemyNumber], enemyYPositions[currentEnemyNumber]);
currentEnemyNumber = currentEnemyNumber + 1;
}
}
Note that, on line 22 above, I've reset currentEnemyNumber to 0; if I didn't, the loop for drawing the enemy images wouldn't run even once, as condition would already be false from the earlier loop that moves the enemies. Also note that, when I do this, the loop that draws the enemies doesn't immediately detect that condition is now true again and start moving all the enemies.
It all works just as it did before.

Click to try it out.
The biggest benefit to this is in how easy it is to add another five enemies. We only need to change four lines of code:
var enemyYPositions = [0, -50, -75, -120, -250, -280, -305, -330, -340, -400];
var enemyXPositions = [250, 130, 300, 50, 190, 200, 220, 60, 100, 110];
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
//...
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
var currentEnemyNumber = 0;
while (currentEnemyNumber < 10) {
enemyYPositions[currentEnemyNumber] = enemyYPositions[currentEnemyNumber] + 1;
currentEnemyNumber = currentEnemyNumber + 1;
}
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
currentEnemyNumber = 0;
while (currentEnemyNumber < 10) {
gameCanvas.getContext("2d").drawImage(enemyImage, enemyXPositions[currentEnemyNumber], enemyYPositions[currentEnemyNumber]);
currentEnemyNumber = currentEnemyNumber + 1;
}
}

Click to try it out.
Simple!
Make Fifteen Enemies
Here's another exercise for you: modify the code so that it creates fifteen enemies. Again, you only have to alter four lines of code. Take a look at my code if you're not sure:
var enemyYPositions = [0, -50, -75, -120, -250, -280, -305, -330, -340, -400, -425, -450, -500, -520, -550];
var enemyXPositions = [250, 130, 300, 50, 190, 200, 220, 60, 100, 110, 30, 300, 150, 190, 90];
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
//...
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
var currentEnemyNumber = 0;
while (currentEnemyNumber < 15) {
enemyYPositions[currentEnemyNumber] = enemyYPositions[currentEnemyNumber] + 1;
currentEnemyNumber = currentEnemyNumber + 1;
}
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
currentEnemyNumber = 0;
while (currentEnemyNumber < 15) {
gameCanvas.getContext("2d").drawImage(enemyImage, enemyXPositions[currentEnemyNumber], enemyYPositions[currentEnemyNumber]);
currentEnemyNumber = currentEnemyNumber + 1;
}
}
Obviously we could continue on like this. But I'm already finding it irritating to change lines 14 and 23 above, and have forgotten to do so a couple of times.
Fortunately we can automate this, in a way. The number - 5, 10, 15, or whatever - is equal to the number of items in either the enemyXPositions[] or enemyYPositions[] array. We call this the array's length, and can retrieve it from either array by using the .length property - like so:
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
var currentEnemyNumber = 0;
while (currentEnemyNumber < enemyXPositions.length) {
enemyYPositions[currentEnemyNumber] = enemyYPositions[currentEnemyNumber] + 1;
currentEnemyNumber = currentEnemyNumber + 1;
}
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
currentEnemyNumber = 0;
while (currentEnemyNumber < enemyXPositions.length) {
gameCanvas.getContext("2d").drawImage(enemyImage, enemyXPositions[currentEnemyNumber], enemyYPositions[currentEnemyNumber]);
currentEnemyNumber = currentEnemyNumber + 1;
}
}
Or, to be a bit neater:
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
var currentEnemyNumber = 0;
var numberOfEnemies = enemyXPositions.length;
while (currentEnemyNumber < numberOfEnemies) {
enemyYPositions[currentEnemyNumber] = enemyYPositions[currentEnemyNumber] + 1;
currentEnemyNumber = currentEnemyNumber + 1;
}
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
currentEnemyNumber = 0;
while (currentEnemyNumber < numberOfEnemies) {
gameCanvas.getContext("2d").drawImage(enemyImage, enemyXPositions[currentEnemyNumber], enemyYPositions[currentEnemyNumber]);
currentEnemyNumber = currentEnemyNumber + 1;
}
}
Now you can make as many enemies as you want, just by adding new numbers to the enemyXPositions[] and enemyYPositions[] arrays.
Re-Introduce Collision Detection
Remember how collision detection worked? We've had the code sitting in handleMouseMovement() (though commented out) for a while:
if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
alert("You hit the enemy!");
}
It's basically checking whether two rectangles - one that moves with the cursor, and one that sits still on the canvas - are overlapping. But it's hard to understand from that code, so let's take a fresh look.
First, let's look at it in terms of horizontal overlap:
Here, the avatar and the enemy are not overlapping. We have: avatarX < avatarX + 30 < enemyX < enemyX + 30
Here, the avatar and the enemy are overlapping. We have: avatarX < enemyX < avatarX + 30 < enemyX + 30
Still overlapping. We have: enemyX < avatarX < enemyX + 30 < avatarX + 30
No longer overlapping. We have: enemyX < enemyX + 30 < avatarX < avatarX + 30
Taking all these together, we can see that the enemy and avatar are overlapping horizontally if:
avatarX < enemyX and enemyX < avatarX + 30
...or:
enemyX < avatarX and avatarX < enemyX + 30
In other words, for horizontal overlap, this condition must be true:
(avatarX < enemyX && enemyX < avatarX + 30) || (enemyX < avatarX && avatarX < enemyX + 30)
(Remember, && means "and"; || means "or".)
It's not hard to come up with a similar condition for vertical overlap:
(avatarY < enemyY && enemyY < avatarY + 33) || (enemyY < avatarY && avatarY < enemyY + 30)
(We use 33 here because the avatar is 33 pixels tall, but only 30 pixels wide.)
For the enemy and avatar to be overlapping, they must be overlapping both horizontally and vertically - so we use the && operator to combine these two conditions:
( (avatarX < enemyX && enemyX < avatarX + 30) || (enemyX < avatarX && avatarX < enemyX + 30) ) && ( (avatarY < enemyY && enemyY < avatarY + 33) || (enemyY < avatarY && avatarY < enemyY + 30) )
That's a long condition! But it's actually quite simple, now that we've broken it down. Let's put it into our code. First, delete the old collision detection code from handleMouseMovement(); we'll put this in handleTick(), after we've moved and redrawn the enemy and avatar, so that it seems fairer.
To start with, we'll just check for a collision between the avatar and the first enemy:
function handleTick() {
//...
if ( ( (avatarX < enemyXPositions[0] && enemyXPositions[0] < avatarX + 30) || (enemyXPositions[0] < avatarX && avatarX < enemyXPositions[0] + 30) ) && ( (avatarY < enemyYPositions[0] && enemyYPositions[0] < avatarY + 33) || (enemyYPositions[0] < avatarY && avatarY < enemyYPositions[0] + 30) ) ) {
alert("You hit the first enemy!");
}
}
It looks messy (so you should probably add a comment to remind yourself of how it works later), but it's got everything we need. Does it work?
Try it out here!
It does work! Now we should make it work for all the enemies - and of course we can use a loop to do that:
currentEnemyNumber = 0;
while (currentEnemyNumber < numberOfEnemies) {
if ( ( (avatarX < enemyXPositions[currentEnemyNumber] && enemyXPositions[currentEnemyNumber] < avatarX + 30) || (enemyXPositions[currentEnemyNumber] < avatarX && avatarX < enemyXPositions[currentEnemyNumber] + 30) ) && ( (avatarY < enemyYPositions[currentEnemyNumber] && enemyYPositions[currentEnemyNumber] < avatarY + 33) || (enemyYPositions[currentEnemyNumber] < avatarY && avatarY < enemyYPositions[currentEnemyNumber] + 30) ) ) {
alert("You hit an enemy!");
}
currentEnemyNumber = currentEnemyNumber + 1;
}
Note that this goes inside handleTick(), at the end, so we have to reset currentEnemyNumber to 0. We also need to change the alert box text, since it might not be the first enemy that causes the alert to appear.
Try it out here!
All right, this is really shaping into a game! Okay, sure, the alert box is kind of annoying, but it serves our purposes for now.
There's one more big addition I'd like us to make in this part...
Make Infinitely Many Enemies
We can add more and more enemy positions to our arrays - a hundred, a thousand, whatever - but eventually the supply will run out, and the player will have no more enemies to dodge. We need to be able to create new enemies and add their positions to the arrays while the game is running.
When we want to change the value of a specific item inside enemyXPositions, it's easy: we just write enemyXPositions[3] = 100, or whatever. But how can we add something to the array? Writing enemyXPositions = [100] (or whatever) will just replace the array with a new one, containing just one item.
The answer is in the arrays' .push() function; this allows us to add an item to an array without creating a new one. To demonstrate this, let's delete all the items from our arrays, and then use .push() to add new ones:
var enemyYPositions = []; //empty square brackets means new empty array
var enemyXPositions = [];
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
function setUpGame() {
var gameCanvas = document.getElementById("gameCanvas");
avatarImage = new Image();
enemyImage = new Image();
enemyImage.src = "img/enemy.png";
avatarImage.src = "img/avatar.png";
enemyYPositions.push(0);
enemyXPositions.push(250);
gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100);
gameCanvas.addEventListener("mousemove", handleMouseMovement);
setInterval(handleTick, 25);
}

Click to try it out.
It works fine; the enemy still moves and the collision detection still works. It's exactly the same as if we'd started the code with:
var enemyYPositions = [0];
var enemyXPositions = [250];
So what happens if we push the new values onto the arrays inside handleTick(), rather than inside setUpGame()?
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
var currentEnemyNumber = 0;
var numberOfEnemies = enemyXPositions.length;
enemyYPositions.push(0);
enemyXPositions.push(250);

Click to try it out.
Hm. It's creating new enemies in the same positions at such a fast rate that they're overlapping. (The one at the top of the screen appears to be above all of the others because that's the last one to get drawn.)
Let's try to fix this by creating the enemies at random starting x-positions. Remember that Math.random() gets us a random number between 0 and 1, so to get a random number between 0 and 400 - the width of the canvas - we can use Math.random() * 400:
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
var currentEnemyNumber = 0;
var numberOfEnemies = enemyXPositions.length;
enemyYPositions.push(0);
enemyXPositions.push(Math.random() * 400);

Click to try it out.
Argh!
All right, so maybe they're still coming in at too fast a rate...
Less Enemies Per Second, Please
Right now, the enemies are being created at a rate of one new enemy per tick - and since a tick is 25ms, there are 40 ticks per second, and therefore 40 new enemies per second.
Let's reduce this to something more manageable: about two enemies per second.
Since that's 1/20th of our current rate, we could achieve this by keeping track of how many ticks have passed, and creating a new one on tick number 20, tick number 40, tick number 60, and so on. But I think it'll be more fun if instead we make there be a 1/20 chance of a new enemy being creates on any given tick. This way, sometimes we'd create more than two new enemies in one second, and sometimes we'd create less, but it'd average out to two per second. The uncertainty involved would make the game a little more exciting (though perhaps "exciting" is a poor choice of words at this early stage of the game's development...).
How can we do this, then? Well, we know that Math.random() create a random number between 0 and 1. Since these random numbers are evenly spread out between 0 and 1, that means there's a 1/20 change of the number generated being somewhere between 0 and... well, 1/20.
In other words, the chance of Math.random() < 1/20 being true is 1/20.
So, let's change our code to make use of this fact:
function handleTick() {
var gameCanvas = document.getElementById("gameCanvas");
var currentEnemyNumber = 0;
var numberOfEnemies = enemyXPositions.length;
if (Math.random() < 1/20)
{
enemyYPositions.push(0);
enemyXPositions.push(Math.random() * 400);
}

Click to try it out.
Much better! Feel free to experiment with that condition to find a value that works for you.
Wrapping Up
That's it for this part of the series. We've now built a rudimentary game - not a polished game, not a particularly fun game, but a game nonetheless.
If you'd like to challenge yourself before the next part, have a go at making these changes:
- Easy: The enemies 'pop' onto the top of the canvas; make them slide in, instead.
- Easy: Some of the enemies are created partially off the side of the canvas; stop this happening.
- Medium: The enemies all move at exactly the same speed, which is pretty dull; allow them to have different speeds.
- Hard: The avatar can move off the right edge of the canvas, making it impossible for any enemies to touch it (assuming you've completed the second easy challenge). Make sure the avatar stays inside the boundaries.
Enjoy!



View full post on Activetuts+
Oct 27, 2011
Posted on Oct 27, 2011 in Hints and Tips | 10 comments
For this chapter of AS3 101, we will be diving into the mechanics of the Flash event system. If you’ve been following along so far, you’ll have seen events in use, dating all the way back to the first episode of the series. The editor and I felt that it was time to write up something to be formally included in the curriculum, so if you’ve ever seen those lines of code about adding event listeners or dispatching events, and not quite caught on, then this is the tutorial for you.
There already exists an Activetuts+ tutorial on the basics of Events, so the focus of this tutorial will be on dispatching events from your own classes, including creating custom event types and objects.
To be successful with this tutorial, you should feel comfortable with writing and using your own classes in ActionScript 3, as well as feeling secure with using the existing events provided by Flash, such MouseEvent.CLICK or Event.ENTER_FRAME. We will be focusing primarily on dispatching custom events from custom classes.
Preview
We’ll spend a lot of time on the theory for this tutorial, but in the end we’ll build a simple slider control that dispatches its own events:
Step 1: Why Use Event Dispatching?
This example is actually a rather simple example of why you’d want to dispatch your own events. When you write your own classes, you are ideally keeping them in their own black boxes and encapsulated. But you still need to have the various objects interoperate in order to create a useful program.
The event model provided by ActionScript 3 is a rather good and convenient way to facilitate communication between your classes, while maintaining a separation of responsibilities in your classes. So if we write our own custom class, such as the ActiveSlider class showcases above, we have a need to enable other objects to be aware of when the slider changes its value. If the slider can dispatch its own change event, then other objects that need to know that information can easily subscribe and be notified.
Personally, I find the need to dispatch my own events in my classes so common that my class template sets up each new class with the boilerplate it needs to be able to do so. As you learn to keep objects discrete, you’ll turn to event dispatching as the most common technique to do so.
Step 2: How to Dispatch Your Own Events
I have good news: dispatching your own events is actually very simple. It is one of the core tenets of ActionScript 3, built in to the Flash Player, and as such there is only one thing you need to do in order to gain the ability to dispatch events. This one thing is:
Extend EventDispatcher
That’s it: when writing your class, use the line:
public class MyClass extends EventDispatcher {
Of course, you need to import EventDispatcher, which is in the flash.events package. You will most likely need other classes in the package, so it might be most convenient to simply import the package with a wildcard.
import flash.events.*;
Step 3: Dispatch
Now you’re set up to dispatch an event. All you need to do now is call a method provided by EventDispatcher named dispatchEvent. Easy to grasp, no?
When you call dispatchEvent, you must provide at least one argument, an Event object. All built-in Event objects are in the flash.events package, so here’s where that wildcard import comes in handy. Each type of Event object will have its own requirements, but most often you simply need to pass it just one argument, as well. This argument is the event type, which is a String that names the event, like "click" or "complete". These are more commonly written as MouseEvent.CLICK or Event.COMPLETE, but the end result is the same; it’s an identifier that separates one type of event from another, and allows one Event object to manage multiple event types.
So, putting it all together, if you wanted to dispatch a "complete" event, you could do so like this:
dispatchEvent(new Event(Event.COMPLETE));
Just drop that line (or one like it) in whichever method it’s appropriate in your class. Your class will then utilize its inherited event dispatching system and any listeners will be notified for you. Speaking of listeners, let’s take a brief look at those, as well.
Step 4: Listen
Any other object in your application can listen for your custom events now. More good news: this is no different than registering for events for the built-in classes. In the previous step, we set up our hypothetical class to dispatch a COMPLETE event. To listen for that event, we could write this line somewhere else in our program:
var myObject:MyClass = new MyClass();
myObject.addEventListener(Event.COMPLETE, myCompleteHandler);
function myCompleteHandler(e:Event):void {
trace("My object completes me.");
}
And that’s it. This should look familiar to anyone who has hooked up a COMPLETE listener to a Loader, for instance, so I won’t dwell any further on this.
Step 5: Where to Dispatch
Where you actually place the dispatchEvent line of code requires some consideration. Typically, it should be the last line of code of the method in which it is written. This is so that any other code that also runs in that method can set properties or otherwise update the internal state of the object. By dispatching after this internal update is complete, the object is in a “clean” state at the time of the dispatch.
Consider, for example, our working example. Let’s say the COMPLETE event is all about the processing of some data; a bunch of data so large that it will take several seconds to completely process, so the object’s purpose is to handle the processing asynchronously so as not to block the UI. And we’re dispatching the COMPLETE event as a way of saying that the data has been processed.
Now suppose that the main method in question looks something like this:
private function processDataChunk():void {
_data += someDataProcess();
if (done()) {
closeData();
}
}
OK, not very realistic, but it illustrates the point. We keep building up the internal data until some other internal logic determines we’re done, at which point we then write out some final bits of data to close the operation.
Now, let’s add the dispatchEvent call:
private function processDataChunk():void {
_data += someDataProcess();
if (done()) {
dispatchEvent(new Event(Event.COMPLETE));
closeData();
}
}
What’s the issue with this approach? Any code that executes within listeners for the COMPLETE event will run before the closeData method is called. Therefore, the state of the dispatcher changes more than once within the span of the processDataChunk method, and is not “stable” until after the closeData call. Yet, we tell all of our listeners that we’re complete before that call. This could lead to some hard-to-track-down bugs where one object claims to be COMPLETE but really isn’t. The obvious solution is to switch some lines around:
private function processDataChunk():void {
_data += someDataProcess();
if (done()) {
closeData();
dispatchEvent(new Event(Event.COMPLETE));
}
}
Step 6: Custom Events
You’re all set up to dispatch your own events. Now, what should you dispatch? There are a few options to consider:
- Simply reuse an event object and event type already provided by the Flash Player
- Reusing an existing event object, but provide a custom event type
- Re-Dispatch an existing event
- Create a custom event object
- Push vs. pull
This first option we’ve already seen in our previous examples. We have a need to dispatch an event related to the completion of some process, and as it happens Flash provides an event type (COMPLETE) associated with an event object (Event) that fits our criteria. We have no need to provide additional data with the event. Dispatching an Event.COMPLETE event is all we need.
We’ll explore these other options in the forthcoming steps.
Step 7: Custom Event Types
As hinted at back in the “Dispatch” step, event types are simply String identifiers. They can technically be any String you like. It’s usually sufficient to make it a single word (like “complete” or “click”) or very short phrase (like “ioError” or “keyFocusChange”); it only has to be unique within a given event dispatcher’s realm of available events.
In addition, Event objects (including subclasses, like MouseEvent or ProgressEvent) don’t really care what event type they are given when instantiated. An EventDispatcher will gladly dispatch events of any type identifier and of any class (as long as it’s the Event class or a subclass).
The upshot of this is that you can make up your own event type String, dispatch it, and set up listeners with it, and everything will be just fine. This is helpful when you want to dispatch an event but can’t necessarily find a good representation of the nature of the event in the built-in classes.
As an example, you might have a class that acts as a coordinator for several things at once: load some XML, load some images based on the XML data, and create a layout based on the loaded images’ sizes, ready for an initial animation, at which point you’d like to dispatch an event. While the COMPLETE event might be suitable, you may feel that a “ready” event more appropriately encapsulates the meaning.
This is as simple as deciding on the String to use, and then using it. Use it both when adding listeners, and when dispatching the event. If the String matches, the event will get to where it needs to go. For example, this is a partial listing of a hypothetical class:
public class MyClass extends EventDispatcher {
// ... A bunch of other stuff not shown.
private function determineReadiness():void {
if (everythingIsReady) {
dispatchEvent(new Event("ready"));
}
}
}
And code from elsewhere in the same program:
var myObject:MyClass = new MyClass();
myObject.addEventListener("ready", onObjectReady);
function onObjectReady(e:Event):void {
// Do stuff now that it's ready.
}
And that would work.
However, it’s worth mentioning while we’re here that typing in matching Strings all over the place is not a good practice. The possibility for error is high, and the event system won’t tell you that you’ve typed "raedy" instead of "ready". The fact that the event system is flexible and simple to use – just pass it any old String for the event type – is also something of a weakness. Your dispatcher will gladly accept a listener for anything, even a "raedy" event. It doesn’t really reconcile what event types are registered against what event types are actually dispatched.
To help prevent this, the standard approach is to simply put the String you want to use in a static constant somewhere, and then never use that literal String again. Only use the constant. Of course, the possibility of typos is just a great as before, but if you’re using a READY constant and not the "ready" literal String, a mistype will trigger a compiler warning. You’ll be able to correct your error quickly and easily. A mistype with the literal Strings produces no compiler error, nor does it produce a run-time error. The only thing that happens is that the SWF does not seem to work properly, because the event listener doesn’t fire.
With this in mind, it’s most common to store these constants on the related Event class. We’ll get to custom Event classes in just a few steps. But in the situation outlined in this step (i.e., we’re reusing an Event class but not an event type), I find it more convenient to simply store that constant on the dispatcher class. So we might choose to do this:
public class MyClass extends EventDispatcher {
public static const READY:String = "ready";
// etc.
private function determineReadiness():void {
if (everythingIsReady) {
dispatchEvent(new Event(READY));
}
}
}
And:
var myObject:MyClass = new MyClass();
myObject.addEventListener(READY, onObjectReady);
function onObjectReady(e:Event):void {
// Do stuff now that it's ready.
}
This gives us the safety of storing event types in constants, while not forcing the inconvenience of creating a whole Event class that we don’t need. I must stress that this is a stylistic choice, and you may feel free to use this technique or not. I felt it warranted explanation, so that you could make your own informed decision. In either regard, you should definitely store your custom event type Strings in static constants. Where those static constants are defined is up to you.
Step 8: Re-Dispatching Events
There are several times when one class (let’s call it ClassX) owns a property that is typed as another class (we’ll call that one ClassY), while itself being owned by a third class (how about ClassZ?). ClassX is listening for an event from ClassY, but not only do we want to have ClassX respond to the event, we also want to consider that ClassX should dispatch a similar (or even the same) event so that ClassZ can also take further action.
As a more concrete example, we have a class (this will be “ClassX“) that is a sort of data manager. It loads an XML document, parses it, and stores data from the XML in its own properties. So it has an URLLoader object (this would be “ClassY“), and listens for the Event.COMPLETE event for when the XML document is loaded.
Then we have a main document class that owns the data manager (the document class is “ClassZ“). It’s coordinating the data loading with other UI elements, so it wants to know when the data is loaded and ready, so it can proceed to create and layout UI elements based on the data.
public class DataManager extends EventDispatcher {
private var _xmlLoader:URLLoader;
public function DataManager() {
_xmlLoader = new URLLoader(new URLRequest("some.xml"));
_xmlLoader.addEventListener(Event.COMPLETE, onLoadComplete);
}
// ... A bunch of other stuff
private function onLoadComplete(e:Event):void {
// ... XML parsing
// With the XML parsed, we'd like to dispatch another event to signal being done.
}
}
We could do this:
dispatchEvent(new Event(Event.COMPLETE));
But we could also do this:
dispatchEvent(e);
Here we simply re-dispatch the existing event. Not only do we reuse the event type and the Event class, but we’re actually reusing the entire Event object as it was passed in to our own listener.
It’s not rocket science, but it is a handy little technique that is surprisingly not so obvious.
“But wait,” you must be thinking, “if we redispatched an event that originated from the URLLoader object, wouldn’t the target of the event still be _xmlLoader when it gets back out to the document class?” And you would have a very good and thoughtful point, and I would be proud of you for thinking so studiously, but you would be wrong.
A rather magical thing happens when redispatching events. The target property gets set to the current dispatcher. You can find a working example of the code in this step in the download package, entitled redispatch.
Actually, it’s not all that magical. When calling dispatchEvent, if the Event object that is passed in already has a target set, then the clone method is called on the Event, creating an identical but discreet copy of the original Event, except for the value contained in target.
Step 9: Custom Event Objects
Everything mentioned so far is something you will want to know. But there will come a point when the best thing to do is to dispatch your own custom event. Not just a custom event type, but a whole custom Event class.
The process for doing this is straightforward, you just need to follow a few steps. We’ll discuss these in due course. Note that quite a bit of this is boilerplate code, and you could easily create a template for an Event subclass and change just a few key pieces and be off and running. The general steps, in shortened-as-if-you-knew-what-I-was-talking-about form:
- Subclass
Event
- Call
super(...)
- Store event types in public static constants
- Declare private properties to hold custom data
- Create public getters to provide read-only access to the custom info
- (Optional) Override the
clone method
- (Optional) Override the
toString method
To explain these process more deeply, we’ll get a start on our slider project and create the SliderEvent we will need for that. So we need to start our project before we can write some code, so a quick diversion in the next step, then we’ll start writing a custom Event class.
Step 10: Create the Project Structure
We’ll keep things pretty simple for this one, but we’ll nonetheless create packages for our classes.
Start by creating a folder for the entire project. Mine will be called slider.
Inside of this, create a com folder, and inside of that, an activetuts folder.
Now create two folders inside of activetuts: events and ui. Your final folder structure should look something like this:
Now back to our Event class.
Step 11: Subclass Event
First, create a new text file in the slider/com/activetuts/events folder, and call it SliderEvent.as. We’ll pop in the boilerplate for any class first:
package com.activetuts.events {
public class SliderEvent {
public function SliderEvent() {
}
}
}
There should be nothing surprising here, and if you have ActionScript templates for your text editor, you shouldn’t even have to type that much in.
Now, we’ll modify this so that it extends Event.
package com.activetuts.events {
import flash.events.Event;
public class SliderEvent extends Event {
public function SliderEvent() {
}
}
}
As you can see, we simply import the Event class, add extends Event to the class definition.
Step 12: Call super
Our superclass can handle a lot for us, which is great, but we do need to make sure we initialize the superclass properly when we initialize our subclass. We need to set up the constructor with arguments matching those found on Event‘s constructor, and pass those along with a call to super.
package com.activetuts.events {
import flash.events.Event;
public class SliderEvent extends Event {
public function SliderEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=true) {
super(type, bubbles, cancelable);
}
}
}
This is, so far, basic subclassing techniques. In fact, depending on your editor’s prowess with templates, you may be able to specify a super class when you create the file and have all of this done for you. Flash Builder, for example, is able to do this.
Step 13: Store Event Types in Public Static Constants
Presumably, there will be one or more event types associated with this event class. Just like the COMPLETE event is associated with the Event class, and the CLICK even with the MouseEvent class, our custom event class will likely have custom event types.
This is as simple as writing a line like the following for every event type you would like to add:
public static const EVENT_TYPE:String = "eventType";
Let’s do that now for the SliderEvent class.
package com.activetuts.events {
import flash.events.Event;
public class SliderEvent extends Event {
public static const CHANGE:String = "sliderChange";
public function SliderEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=true) {
super(type, bubbles, cancelable);
}
}
}
We could theoretically use our class now. We can use the SliderEvent in dispatchEvent, and listen for and create events with the SliderEvent.CHANGE event type.
But we won’t stop there. There is more to consider. But before we do more code-writing, we need to take another detour into theory.
Step 14: Push vs. Pull
When an event is dispatched, sometimes it’s enough to simply know that the event has occurred. For example, most times that you are interested in Event.ENTER_FRAME, Event.COMPLETE, or TimeEvent.TIMER events, you probably just want to know that the event happened. There are other times, though, when you probably want to know more. When listening to MouseEvent.CLICK, you might be interested in whether the Shift key was held down, or the coordinates of the mouse at the time of the click. If you’re listening to ProgressEvent.PROGRESS, you will most likely want to know the actual progress of the load; that is, how many bytes have loaded and how many there are to load in total.
The difference between these two methodologies is sometimes known as “push” and “pull.” Those terms refer to how the event listener gets data related to the event. If the data is “pushed” then there is data stored within the event object, and in order to get the data the listener merely need to use properties on the event object. If data is to be “pulled,” though, generally the event object has very little information contained within – just the necessities: the type, the target, etc. This target, though, is indispensable, as it provides access to the event dispatcher to the event listener, allowing the listener to get the data it needs from the dispatcher.
In other words, you can either push a bunch of data to the listener inside the event object, or you can require the listener to pull the data out of the dispatcher as needed.
The pros and cons of each technique are somewhat balanced, in my opinion, and the path you choose for your event object depends on the situation at hand, and not a little bit on personal preference.
Exhibit A:
| |
Pros |
Cons |
| Push |
- Data is easily accessible in the event object
|
- You may be pushing data that is unneeded. Bloating the event object with a bunch of data that is rarely used could lead to memory and/or performance issues.
|
| Pull |
- Very easy to write. You probably don’t need a custom
Event class to execute a pull event.
- Very easy to use. If it’s just an
Event class, the only required argument is the event type.
|
- If data commonly pulled out of the dispatcher is expensive to compute and return, you may be taking a hit on performance by requiring the dispatcher to continually hand out that information.
- Some data might be difficult to pull, e.g. the
KeyboardEvent has a keyCode property to detail the key that was pressed to trigger the event.
|
This might be a good discussion for the comments; I’m sure many of you reading have passionate feelings about which methodology is better. Personally, I try to find the method that works best for the situation.
Having said that, it’s worth noting that to this point our SliderEvent class is rather “pull-ish.” For the sake of illustration, and because it’s not a terrible idea (though I did come up with several of those), we’ll continue on with making this an event that pushes data along with it; namely the value of the slider when it was changed.
Step 15: Declare Private Properties to Hold Custom Data
To implement a push event, we need to have a place to store the data being pushed. We’ll add a private property for that purpose.
You should still have SliderEvent open (if not… what are you waiting for?). Add the highlighted line:
package com.activetuts.events {
import flash.events.Event;
public class SliderEvent extends Event {
public static const CHANGE:String = "sliderChange";
private var _sliderValue:Number;
public function SliderEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=true) {
super(type, bubbles, cancelable);
}
}
}
Next we’ll modify the constructor so that we can accept a value parameter, and set the private property with that:
public function SliderEvent(type:String, sliderValue:Number, bubbles:Boolean=false, cancelable:Boolean=true) {
_sliderValue = sliderValue;
super(type, bubbles, cancelable);
}
This way we can easily create the SliderEvent and set up its push data in one line.
Why use private properties? In this case, we want to protect the data. In my opinion the data related to an event is immutable, so long as it’s associated with the event. Being able to change the data of an event object is like editing a history textbook. To be fair, this is my opinion and the standard used by Adobe with their built-in classes is to use writable properties (technically they use private properties and public getters and setters, but the end result is the same).
Step 16: Create Public Getters for the Custom Data
So the next step would be to make sure we can access the data being pushed. A private property by itself is not useful to this purpose. Therefore, we need to write a public getter for the _sliderValue property. We will choose to not write a setter, so that the property becomes read-only (as discussed in the last step).
Add this method to the class:
public function get sliderValue():Number {
return _sliderValue;
}
This adds a getter so we can access the sliderValue in property-like fashion. I’m choosing not to add the matching setter. You can add one if you feel it’s worthwhile.
Step 17: Override the clone method
I mentioned the clone method a little while ago. You probably won’t call clone much yourself, but it’s not a bad idea to override the clone method so that your custom event plays nicely with the event system.
Add this method to your class:
override public function clone():Event {
return new SliderEvent(type, sliderValue, bubbles, cancelable);
}
First, notice the signature of this method. We’re using override because this method is declared in Event, and we’re inheriting it. It also returns an object of type Event. Make sure, when writing your clone override, that you put the correct return type in. It’s easy to forget and put the type of your class there, but that will cause an incompatible override error, because the return types need to match.
All we’re really doing in the meat of the event is creating a new SliderEvent and passing in the same values that we have stored in the current event object. This creates an identical but discreet copy: a clone.
This is an optional step, but it’s a quick win and ensures that your custom event plays well with the rest of the event system.
Step 18: Override the toString method
One last thing, and again this is optional. But it’s also very useful as a debug tool, so it usually pays for itself within a few uses.
In case you haven’t been told yet, the toString method exists on all objects (it’s declared and defined in Object, the über-class from which all other classes inherit, whether you like it or not). It can be called explicitly, but the handy thing is that it get called automatically in a number of cases. For example, when you pass object to the trace function, any object that is not already a String will have toString called on it to make sure it is formatted nicely for the Output panel. It even gets called when working with Objects together with Strings, as with concatenation. For example, if you write this:
"The answer to life, the universe, and everything is " + 42;
ActionScript is smart enough to convert 42 into a String representation of the Number before concatenating the String. Trying to add a String and a Number is bad news, but converting a Number to a String and then concatenating it with another String is just fine.
So when you write your own classes, you can provide a toString method, which takes no arguments and returns a String, and return whatever String you like.
In the case of Event objects, Adobe helpfully provides a formatToString method to help all Events look similar when traced. We’ll use it in the method we’re about to add to our class:
override public function toString():String {
return formatToString("SliderValue", "type", "sliderValue");
}
First, note the method signature. Again, it’s an override so we have that keyword. It’s public, it takes no parameters, and returns a String (which should be obvious).
Next, note the single line in the method body. We call formatToString, which is defined in Event, so it’s easy to use. The first argument we pass to it is the String name of the class. After that, the arguments are open-ended. You can pass in one, 15, or none. We’re passing in two. No matter how many you pass in, they should all be Strings, and they should match property names on your class. "type" is defined by Event, but "sliderValue" is defined by our own class. Either way, what happens is that the name of the property is printed, followed by an equals sign, which is followed by the actual value of that property. In short, it will end up looking like this:
[language="text"]
[SliderValue type="sliderChange" sliderValue=0.34146341463414637]
This is entirely non-functional but very useful. It can provide a quick glimpse into the event when things aren’t working the way you think they should.
Step 19: Building the Slider
At this point, we’ve been through the key concept of this tutorial: writing a custom Event class. But we really need to put it to the test. We’ll spend the remainder of our time building the simple slider application that was previewed at the beginning of the tutorial.
We already have a project folder structure; we just need a few more files. We’ll start with the FLA file.
Create a new Flash file (ActionScript 3.0, of course) and save it as ActiveSlider.fla in the root of your project folder. I’m going to assume that you don’t need step-by-step details on how to put this simple FLA together, and instead I’ll lay out the key elements. You can use the FLA file found in the start folder of the download package for reference, as well, or even just copy that FLA to your project folder and call this step done.
There are three main objects on the stage.
- The slider track. This is a long, narrow strip that indicates to where the slider can be moved. The slider moves “in” the track.
- Needs to be a Movie Clip
- For easiest math, should have artwork arranged so that the registration point is at the top left corner
- Name it
track_mc
- Place in the upper center; it should take up most of the width of the stage and have space below it.
- The slide grip. This is a button-sized element that indicates the current position of the slider. It’s the piece that moves along the track and responds to the mouse.
- Needs to be a Movie Clip.
- Again, for math, should have the registration point at the top left
- Name it
grip_mc
- Place it over the track, so that it is vertically centered with the track, and somewhere within the left and right ends of the track
- It should be stacked on top of the track, so that the grip obscures the track (put it on a layer higher)
- The output field. This is a text field that, for our own demonstration purposes, displays the current value of the slider.
- Needs to be a dynamic Text Field.
- Name it
output_tf
- Fonts are inconsequential; set it to whatever you like and embed as necessary
- Place it in the lower portion of the stage, so that it doesn’t conflict with the space required by the slider.


Other than hooking up the document class, which we’ll write in two steps, the FLA is ready for business.
Step 20: The ActiveSlider Class
The main UI class with which we’ll work is the ActiveSlider class. This will extend EventDispatcher, target the two Movie Clips on the stage, and set up mouse interactivity for slider behavior. Most exciting of all, it will dispatch a SliderEvent.
Start by making a new class file called ActiveSlider.as in the com/activetuts/slider folder of your project. This class isn’t too intense (at least, not for our purposes here. A slider class could get much more involved), and I’ll just present the code in full and discuss as we go:
package com.activetuts.slider {
import flash.display.*;
import flash.events.*;
import flash.geom.*;
Nothing exciting, just setting up the package and imports.
public class ActiveSlider extends EventDispatcher {
private var _track:Sprite;
private var _grip:Sprite;
private var _grabOffset:Point;
We’ll need these three properties. The first two keep track of the Sprites (or MovieClips) that make up the slider. The third is used while dragging the slider grip; it helps keep the grip’s position offset from the mouse by an amount relative to where the grip was clicked in the first place.
public function ActiveSlider(track:Sprite, grip:Sprite) {
_track = track;
_grip = grip;
if (_grip.parent != _track.parent) {
throw new Error("The track and the grip Sprites are not in the same container.")
}
_grip.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
if (_grip.stage) {
addStageListener();
} else {
_grip.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
}
This is the constructor. It accepts two Sprite arguments, which get passed on to the first two of those properties for storage. It then does a simple check to make sure the two Sprites are in the same coordinate space by checking that their parent properties reference the same object. If they don’t, then our math for placing the grip might be unreliable, so we throw an error as a way of alerting the developer. The rest of the constructor is devoted to adding two event listeners. The first is a MOUSE_DOWN event and straight forward. But the second is trying to add a MOUSE_UP event to the stage, which might or might not exist depending on whether the grip Sprite is on the display list or not. The next two methods might make this a little clearer:
private function onAddedToStage(e:Event):void {
_grip.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
addStageListener();
}
private function addStageListener():void {
_grip.stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
}
The onAddedToStage method is an event listener for the ADDED_TO_STAGE event, which was set up in the constructor, but only if the grip Sprite did not already have a reference to the stage. The addStageListener method simply adds the MOUSE_UP event listener to the stage. So, if there is a stage reference in the constructor, we call addStageListener directly. If there is not a stage reference, we set up the ADDED_TO_STAGE event, and when the grip is added to the display list, and thus has a stage reference, the onAddedToStage method fires which then in turn calls addStageListener. It also removes the ADDED_TO_STAGE event listener, because we only need to do this once.
private function onDown(e:MouseEvent):void {
_grabOffset = new Point(e.localX, e.localY);
_grip.addEventListener(Event.ENTER_FRAME, onFrame);
}
private function onUp(e:MouseEvent):void {
_grip.removeEventListener(Event.ENTER_FRAME, onFrame);
}
Next we have our two mouse event listeners. In onDown, the key line is to then add an ENTER_FRAME event listener. In onUp, we remove that listener. Also in onDown, we make note of where on the grip the mouse click actually happened, and store that in _grabOffset. This will play into our onFrame method next.
private function onFrame(e:Event):void {
_grip.x = _grip.parent.mouseX - _grabOffset.x;
var gripBounds = _grip.getBounds(_grip.parent);
var trackBounds = _track.getBounds(_grip.parent);
if (gripBounds.x < trackBounds.x) {
_grip.x = _track.x;
} else if (gripBounds.right > trackBounds.right) {
_grip.x = trackBounds.right - gripBounds.width
}
trace(this.value);
}
This is the main meat of our slider logic. This method fires repeated on an ENTER_FRAME event, but only when the mouse has been pressed down on the grip, and only for as long as the mouse remains pressed.
First, we set the grip’s x property to match the mouse position, only we need to offset it based on the original mouse position, so that it moves smoothly and doesn’t jump so that its left edge is at the mouse.
The next two lines calculate the bounding rectangles of both the grip and track Sprites. We’ll use these rectangles in the upcoming math, so we’ll keep things tidier by pre-calculating the rectangles and storing them in variables.
Then there is the if block. This just constrains our slider to within the track. It’s a simple check to see if the x of the grip, as calculated in the first line of the method, is lower than (to the left of) the x of the track. If it is, it would be too far, so we need to move the grip to that minimum value. Similarly, we check to see if the grip’s right edge is greater than (to the right of) the track’s right edge, and if so we need to reel it back in to that maximum value.
Finally, we have a reliable grip position, and for now we just trace the current slider value, which is calculated in the final bit of code for the class:
private function get value():Number {
return (_grip.x - _track.x) / (_track.width - _grip.width);
}
}
}
This is a simple getter, although the math being used for the return value might be a little confusing. It determines the current grip position as a percentage of the range of motion of the grip. It would be more obvious if it were just this:
return _grip.x / _track.width;
…which is reasonable but doesn’t take into account that the grip doesn’t actually travel the entire width of the track. It goes all the way to the left edge, but only as far right as the right edge of the track minus the width of the grip. So this is more accurate:
return _grip.x / (_track.width - _grip.width);
This makes the width by which we divide the range of motion. However, there may still be a gotcha in that the track may be offset to the left or right from its container, meaning that the values we get aren’t quite right. We need to neutralize that by subtracting the x position of the track from the grip’s x, and we end up with this:
return (_grip.x - _track.x) / (_track.width - _grip.width);
For reference, here is the complete class, with my ramblings left out:
package com.activetuts.slider {
import flash.display.*;
import flash.events.*;
import flash.geom.*;
public class ActiveSlider extends EventDispatcher {
private var _track:Sprite;
private var _grip:Sprite;
private var _grabOffset:Point;
public function ActiveSlider(track:Sprite, grip:Sprite) {
_track = track;
_grip = grip;
if (_grip.parent != _track.parent) {
throw new Error("The track and the grip Sprites are not in the same container.")
}
_grip.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
if (_grip.stage) {
addStageListener();
} else {
_grip.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
}
private function onAddedToStage(e:Event):void {
_grip.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
addStageListener();
}
private function addStageListener():void {
_grip.stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
}
private function onDown(e:MouseEvent):void {
_grabOffset = new Point(e.localX, e.localY);
_grip.addEventListener(Event.ENTER_FRAME, onFrame);
}
private function onUp(e:MouseEvent):void {
_grip.removeEventListener(Event.ENTER_FRAME, onFrame);
}
private function onFrame(e:Event):void {
_grip.x = _grip.parent.mouseX - _grabOffset.x;
var gripBounds = _grip.getBounds(_grip.parent);
var trackBounds = _track.getBounds(_grip.parent);
if (gripBounds.x < trackBounds.x) {
_grip.x = _track.x;
} else if (gripBounds.right > trackBounds.right) {
_grip.x = trackBounds.right - _grip.width
}
trace(this.value);
}
private function get value():Number {
return (_grip.x - _track.x) / ((_track.width - _grip.width) - _track.x);
}
}
}
We have not added in our SliderEvent class yet; we’ll take a separate step to do that. But first, we need our document class so we can actually use the ActiveSlider.
Step 21: The Document Class
We need one more file to make it work: our document class. Make a new class file named SliderDemo in the project root folder. Add in the following code:
package {
import flash.display.*;
import flash.events.*;
import com.activetuts.slider.ActiveSlider;
public class SliderDemo extends Sprite {
private var _slider:ActiveSlider;
public function SliderDemo() {
_slider = new ActiveSlider(track_mc, grip_mc);
}
}
}
This is much simpler that our ActiveSlider class. It really just sets up an ActiveSlider into the property named _slider.
Go back to the FLA file, and enter SliderDemo into the document class field, and you should be able to try this out. The slider should be able to move back and forth, and constrain itself to the width of the track.
Now for one last task. We need to dispatch, and listen for, a SliderEvent.CHANGE event. We’ll do that next.
Step 22: Dispatching the SliderEvent
Go back to the ActiveSlider class, and make a single-line change to the onFrame method:
private function onFrame(e:Event):void {
_grip.x = _grip.parent.mouseX - _grabOffset.x;
var gripBounds = _grip.getBounds(_grip.parent);
var trackBounds = _track.getBounds(_grip.parent);
if (gripBounds.x < trackBounds.x) {
_grip.x = _track.x;
} else if (gripBounds.right > trackBounds.right) {
_grip.x = trackBounds.right - _grip.width
}
dispatchEvent(new SliderEvent(SliderEvent.CHANGE, this.value));
}
We’ve removed the trace and replaced it with a real, live event dispatch. For this to work, though, we need to import the SliderEvent class:
package com.activetuts.slider {
import flash.display.*;
import flash.events.*;
import flash.geom.*;
import com.activetuts.events.SliderEvent;
Step 23: Listening For the SliderEvent
Finally, go back to the SliderDemo class, and change it so it listens for and reacts to the SliderEvent:
package {
import flash.display.*;
import flash.events.*;
import com.activetuts.slider.ActiveSlider;
import com.activetuts.events.SliderEvent;
public class SliderDemo extends Sprite {
private var _slider:ActiveSlider;
public function SliderDemo() {
_slider = new ActiveSlider(track_mc, grip_mc);
_slider.addEventListener(SliderEvent.CHANGE, onSliderChange);
}
private function onSliderChange(e:SliderEvent):void {
trace(e);
output_tf.text = e.sliderValue.toString();
}
}
}
We’re again importing the SliderEvent class, and after creating the ActiveSlider, adding a listener called onSliderChange to the slider. That method is the biggest addition, but still is a regular event listener. It’s much like any other event listener, only we’re making sure to type the event argument as SliderEvent, because that’s what we’re getting.
The first line of code is a bit superfluous, but I wanted to see what happens when you trace our SliderEvent object. You’ll see, when you run this, a typical-for-Flash formatting of the event object.
The second line does what we were after to begin with. It simply grabs the sliderValue property, turns it into a String, then sticks that String into the TextField on the stage, so we can see the slider value in the movie.
Step 24: Wrapping Up
When you start rolling your own custom events, you start working with ActionScript 3 the way it was meant to be used. Events help you decouple classes from each other, and a well structured event flow in your application can really make the difference between something that is easy to work with and something that is buggy and temperamental. With this (theoretically) final instalment of AS3 101, you should be well on your way to becoming a ninja.
Thanks for reading, and I’ll see you back here on Activetuts+ before you know it!



View full post on Activetuts+