Dec 3, 2011
Posted on Dec 3, 2011 in Hints and Tips | 10 comments
So far, we’ve got a never-ending stream of enemies that our avatar must avoid; one touch, and it’s game over. But so what? Since there’s no way to track the player’s progress, they have no idea whether they did better in their latest round than they ever did before. In this tutorial, you’ll learn how to keep score, how to display it on the canvas, and how to let the player know when they’ve beaten their own records.
Refresher
In the first and second parts of this tutorial, we’ve covered a number of concepts: drawing images to the canvas, detecting mouse actions, using if and while statements, storing variables in arrays, and the idea of variable scope.
You can download the source files from the series up to this point if you’d like to dive straight in here, though I recommend reading all parts in order.
Our game’s JavaScript file initialises a bunch of variables (including two arrays to store enemy x- and y-coordinates) outside of all functions, so that their contents are available to all functions. It also contains a function called setUpGame() which is run when the player first clicks the canvas; this loads the images, starts listening for any mouse movements, and sets up a “tick” function to be run every 25 milliseconds.
When the mouse is moved, we move the position of the avatar, as stored in two variables – one for the x-coordinate and one for the y-coordinate – but we don’t immediately redraw the avatar’s image at this new location; all redrawing is handled in the tick function.
The tick function does four things:
- There’s a one-in-twenty chance that it’ll add a new enemy by pushing new x- and y-coordinates onto the relevant arrays.
- It increments the y-coordinates of each enemy by looping through that array.
- It redraws the avatar and enemy images according to their current coordinates.
- It checks for a collision between the avatar and each enemy, giving an alert if one occurs.
All clear?
Warm Up Challenge
If it’s been a while since you read the second 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 series so far.
Easy
Swap the avatar and enemy images around so that the player controls a smiley face that’s avoiding the falling skulls.
(How many different ways can you figure out how to do this? I can think of three, off the top of my head.)
Medium
At the moment, there’s a flat one-in-twenty chance that a new enemy will be created in any given tick. I want you to make it so that there’s a one-in-one chance on the first tick, a one-in-two chance on the second, a one-in-three chance on the third, and so on.
To make this more challenging, do it in reverse: a one-in-1,000 chance on the first tick, a one-in-999 chance on the second, a one-in-998 chance on the third, and so on. (After the thousandth tick, make it a steady one-in-one chance.)
Hard
Rather than waiting for the enemies to appear one by one, have the game start with twenty enemies already on screen.
To make this more challenging, make them spread out around the canvas, and don’t allow them to overlap the avatar or each other.
Keeping Time
What’s the simplest way to measure how well the player is doing this round? The easiest thing I can think of is to keep track of how long it’s been since they hit an enemy. And since hitting an enemy means game over, we only need to keep track of how long they’ve been playing.
To do this, we’ll just create a variable, set it to 0 when the round starts, and increment it every tick. Let’s call this variable ticksSurvived. And think: since we need to access it over and over again, it needs to be defined outside of all the functions, at the top of the JS file:
var enemyYPositions = []; //empty square brackets means new empty array
var enemyXPositions = [];
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
var ticksSurvived = 0;
Now, we’ll give handleTick() yet another task to do: increment ticksSurvived. Put this after the collision detection; after all, if the avatar hits an enemy, it hasn’t actually survived the tick:
function handleTick() {
//...
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;
}
ticksSurvived = ticksSurvived + 1;
}
To display this, for now, we’ll just alter the alert that appears when the avatar hits an enemy:
function handleTick() {
//...
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! Ticks survived: " + ticksSurvived);
}
currentEnemyNumber = currentEnemyNumber + 1;
}
ticksSurvived = ticksSurvived + 1;
}
That’s a bit weird; we’ve used the + operator to add a string to a number. We can add another string to the end:
function handleTick() {
//...
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! You survived " + ticksSurvived + " ticks.");
}
currentEnemyNumber = currentEnemyNumber + 1;
}
ticksSurvived = ticksSurvived + 1;
}
Maybe this doesn’t seem odd to you, but some programming languages hate this. JavaScript doesn’t care. It knows that "some" + "string" equals "somestring", and assumes that you want to treat ticksSurvived as a string in this situation.
(Some things to try out:
12 + 6
"12" + "6"
"12" + 6
12 + "6"
Do they all do what you expect them to do?)
Anyway, let’s give this new code a try.

Click to try it out.
Great!
As before, when you click OK, the alert box appears again, because all it does is pause the ticks rather than actually stopping them. But note that the number in the box increases by one; this is proof that JavaScript doesn’t turn the number into a string permanently; it only uses it as a string for the purposes of adding it to another string.
While we’re on the subject of this alert box: don’t you find it annoying?
Try Again
At the moment, the only way to start a new round is to refresh the page. Let’s make it easier and less irritating to have another go, by making the “OK” button on the alert box reset the game.
Since the alert effectively pauses the game, whatever we put on the line after the alert will run once the player hits OK. Let’s make it call a new function, startNewGame():
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! You survived " + ticksSurvived + " ticks.");
startNewGame();
}
currentEnemyNumber = currentEnemyNumber + 1;
}
So what do we need startNewGame() to do?
Perhaps we should just call setUpGame() again – but no, nothing in that needs doing twice: we don’t need to load the images or add the mouse event listener or set up the ticks again.
Have a think about what would be needed, and try out your ideas. My solution is below; take a look when you’re ready.
function startNewGame() {
enemyXPositions = [];
enemyYPositions = [];
ticksSurvived = 0;
}
That’s all. You can do more if you wish, but this is the minimum required.
Note that we don’t have to do anything with the canvas – we don’t need to clear it, or draw anything to it, or manipulate the images – because this is all done in handleTick(). We don’t have to go through all the elements in the arrays, one by one, either, because the arrays are simply a list of coordinates that we use to stamp the enemy images onto the canvas; the enemies don’t exist as actual objects.

Click to try it out.
So this is great – the player can play the game over and over again to keep trying for a better score. Except… how do they know whether they’ve beaten their previous score? At the moment, they just have to remember they top score so far, or write it down. We can do better than that.
Remember the Best Score
How should we store the top score? In another variable, of course!
var enemyYPositions = []; //empty square brackets means new empty array
var enemyXPositions = [];
var avatarX = 0;
var avatarY = 0;
var avatarImage;
var enemyImage;
var ticksSurvived = 0;
var mostTicksSurvived = 0;
Naturally, it has to live outside of the functions; by now, I’m sure you understand why.
To begin with, we set it to 0, of course – the player hasn’t even completed a round. When the avatar hits an enemy, let’s update the new score as required:
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! You survived " + ticksSurvived + " ticks.");
if (ticksSurvived > mostTicksSurvived) {
mostTicksSurvived = ticksSurvived;
}
startNewGame();
}
currentEnemyNumber = currentEnemyNumber + 1;
}
Let’s also tell the player that they beat their old high score:
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! You survived " + ticksSurvived + " ticks.");
if (ticksSurvived > mostTicksSurvived) {
alert("New high score!");
mostTicksSurvived = ticksSurvived;
}
startNewGame();
}
currentEnemyNumber = currentEnemyNumber + 1;
}
Take a look:

Click to try it out.
Excellent. Now let’s give the player a little more information on how much better they did:
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! You survived " + ticksSurvived + " ticks.");
if (ticksSurvived > mostTicksSurvived) {
alert("You beat your old high score by " + (ticksSurvived - mostTicksSurvived) + " ticks!");
mostTicksSurvived = ticksSurvived;
}
startNewGame();
}
currentEnemyNumber = currentEnemyNumber + 1;
}
Note here that ticksSurvived and mostTicksSurvived are treated as numbers when subtracting one from the other, but the resulting expression (ticksSurvived - mostTicksSurvived) is treated as a string when added to the other strings! This can get really confusing, if you’re not careful.

Click to try it out.
All right, so now the player knows how well they were doing, after they get game over, we should give them an indication of how well they are doing, while they’re still playing.
Drawing the Score to the Canvas
It’s really easy to write text into a canvas – and no, we don’t need to piece it together from different images of different letters!
Remember that to draw an image to the canvas we call canvas.getContext("2d").drawImage(). Writing text is very similar: we call canvas.getContext("2d").fillText().
We must pass three arguments to fillText():
- A string containing the text to write.
- The x-coordinate at which to write it.
- The y-coordinate at which to write it.
There’s an optional fourth argument:
- The maximum width that the text is allowed to take up on screen.
…but we won’t worry about that for now.
To test this out, head to handleTick(), find the line where we draw the avatar to the canvas, and draw a sample line of text just afterwards:
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
gameCanvas.getContext("2d").fillText("Testing, testing, one, two, three.", 100, 100);
Try it out:

Click to try it out.
Note that, since this gets drawn after the avatar and before the enemies, the enemies get drawn on top of it, while the avatar goes underneath:

Click to try it out.
Like I say, the first argument required is a string, and – as we’ve seen – we can construct a string by adding a string to a number. So, this should work:
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, avatarX, avatarY);
gameCanvas.getContext("2d").fillText("Score: " + ticksSurvived, 100, 100);
…and indeed it does!

Click to try it out.
But it’s a bit of a mess, floating there underneath the enemies with that tiny font. Let’s sort that out.
Tidy Up the Text
First, let’s move the text so that the enemies move underneath it. This just means drawing it after all of the enemies are drawn, so move the relevant line down:
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;
}
gameCanvas.getContext("2d").fillText("Score: " + ticksSurvived, 100, 100);
Now, let’s move it to the top-left of the screen. That’s, (0, 0) so this should work, right?
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;
}
gameCanvas.getContext("2d").fillText("Score: " + ticksSurvived, 0, 0);
Hmm. Nope. We can’t see the score at all. This is because it’s using (0, 0) to place the bottom-left corner of the text. We need to move the text down a bit, then:
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;
}
gameCanvas.getContext("2d").fillText("Score: " + ticksSurvived, 0, 10);
Check it out:

Click to try it out.
Okay, so far so good. Now, let’s change the font itself. We do this by setting the canvas’s context’s font property to a CSS string representing the font. If you’re not familiar with CSS, don’t worry; at this stage, all you need to know is that it contains the font’s size and the fontface:
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;
}
gameCanvas.getContext("2d").font = "10px Impact";
gameCanvas.getContext("2d").fillText("Score: " + ticksSurvived, 0, 10);
(“px” stands for “pixels”.)
Note that we have to set the font before drawing the text!
Take a look:

Click to try it out.
It works – but the Impact font is really hard to read at that size. Let’s make it bigger:
gameCanvas.getContext("2d").font = "18px Impact";
gameCanvas.getContext("2d").fillText("Score: " + ticksSurvived, 0, 10);

Click to try it out.
Unfortunately, this doesn’t work so well, because the font is still 10px from the top of the canvas, but now it’s 16px tall!
We could keep changing the y-position of the font to fix this, but there’s an alternative: we’ll make it so that the position we specify determines the top-left corner of the text, rather than the bottom-left corner. It’s easy:
gameCanvas.getContext("2d").font = "18px Impact";
gameCanvas.getContext("2d").textBaseline = "top";
gameCanvas.getContext("2d").fillText("Score: " + ticksSurvived, 0, 10);
Of course, now, there’s a 10px gap between the top of the text and the top of the canvas:

Click to try it out.
…but that’s easy to fix now:
gameCanvas.getContext("2d").font = "18px Impact";
gameCanvas.getContext("2d").textBaseline = "top";
gameCanvas.getContext("2d").fillText("Score: " + ticksSurvived, 0, 0);
Well, actually, I like a bit of padding, so let’s do this:
gameCanvas.getContext("2d").font = "18px Impact";
gameCanvas.getContext("2d").textBaseline = "top";
gameCanvas.getContext("2d").fillText("Score: " + ticksSurvived, 5, 5);

Click to try it out.
Much better.
Different Fonts
Perhaps you’re wondering whether there’s a list of fonts we can use. Well, yes and no. See, if you pick a font that the player doesn’t have installed on their computer, then the text will just be displayed in the default font. Remember, JavaScript is drawing the text on the fly, using the player’s computer’s resources.
We’re fine using Impact, because it’s installed on every computer – but does that mean we can’t use any font other than the few in this list?
Fortunately, no. We can use any font we like, as long as we give the user access to it somehow. And for this, we’re going to use that CSS file – you know, the one we haven’t touched since the start of the series.
Suppose we have a font called “Really-Awesome”. This will exist on your computer, somewhere, as a file – probably a .TFF file (“True Type Font”). Let’s suppose that file is called ReallyAwesomeFont.ttf.
Now suppose you upload this font to your website – reallyawesomewebsite.com – so that there’s a direct URL to it: http://reallyawesomewebsite.com/fonts/ReallyAwesomeFont.ttf.
You can then let the player’s browser know about it by adding this to your CSS file:
@font-face {
font-family: 'Really-Awesome';
src: url('http://reallyawesomewebsite.com/fonts/ReallyAwesomeFont.ttf');
}
With this line in your CSS, you can alter your code like so:
gameCanvas.getContext("2d").font = "18px Really-Awesome";
gameCanvas.getContext("2d").textBaseline = "top";
gameCanvas.getContext("2d").fillText("Score: " + ticksSurvived, 5, 5);
…and it’ll work, because their browser will look up the “Really-Awesome” font in the stylesheet, and find the URL to the TTF. Great!
I’m not going to demonstrate this because I don’t own the redistribution rights to any fonts; if I upload some and give you the link to the TTF as part of this tutorial, that’s not really fair. But there is an alternative…
Google Web Fonts
Google has assembled a large collection of fonts that you can use in your project in your CSS, using a similar principle as above. Take a look at the collection.
There are a few criteria by which you can search for fonts, and you can enter some sample text to see how it’ll be displayed:

Google Web Fonts.
I’m going to choose Iceland. When I click Quick Use, it gives me this HTML:
<link href='http://fonts.googleapis.com/css?family=Iceland' rel='stylesheet' type='text/css'>
If you load http://fonts.googleapis.com/css?family=Iceland in your browser, you’ll see that it’s the same kind of thing as we wrote from scratch before:
@media screen {
@font-face {
font-family: 'Iceland';
font-style: normal;
font-weight: 400;
src: local('Iceland'), local('Iceland-Regular'), url('http://themes.googleusercontent.com/static/fonts/iceland/v1/F6LYTZLHrG9BNYXRjU7RSw.woff') format('woff');
}
}
It has a few more details, and the font is in WOFF format rather than TTF, but you get the idea.
You can use whichever font you like (and it doesn’t have to be a Google Web Font), but for the purpose of this tutorial I’m going to assume that you’re using Iceland. So, edit game.html and paste the font reference into it:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src="js/main.js"></script>
<link rel="stylesheet" href="css/style.css" />
<link href='http://fonts.googleapis.com/css?family=Iceland' rel='stylesheet' type='text/css'>
</head>
<body>
<canvas id="gameCanvas" onclick="setUpGame();" width="400" height="300"></canvas>
<p>Click inside the box to play. Warning: extremely basic!</p>
<p>Make sure you have <a href="http://google.com/chrome/" rel="external">Chrome</a>.</p>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/html5-avoider-game-tutorial-keeping-score">HTML5 Avoider Game Tutorial: Keeping Score</a>.</p>
</body>
</html>
Now, back in main.js, change the font from Impact to Iceland:
gameCanvas.getContext("2d").font = "18px Iceland";
gameCanvas.getContext("2d").textBaseline = "top";
gameCanvas.getContext("2d").fillText("Score: " + ticksSurvived, 5, 5);
Take a look:

Click to try it out.
Cool!
Challenge: Show the Best Score
Now that the current score is on screen at all times, it’s only natural that the player will want to see what they’re trying to beat.
Using what you’ve learnt, draw their best score at the top-right of the canvas. This is a little trickier than it seems: you’ll have to decide what to do (if anything) when the current score overtakes the current top score!
Saving Scores Between Sessions
You’ve probably noticed by now that the high score is reset when you reload the page. This makes sense – after all, we run this line right at the start:
var mostTicksSurvived = 0;
…but even without that line, the high score still wouldn’t persist. All variables get reset and unassigned when you leave the page.
However, there is an alternative: every browser sets aside 5MB of storage for each web site domain. You can store any string you like in this 5MB storage, and it’ll stay there even if the user closes their browser and restarts their computer.
It’s called local storage, and it’s really easy to use! To save something to it, you just need to give it two strings:
- a name for the item, and
- the value of the item.
To retrieve it, you just need the name that you originally used.
A Quick Example
Here’s a quick example: let’s add something to the local storage right at the start of the setUpGame() function:
function setUpGame() {
localStorage.setItem("exampleItem", "This is a great example.");
Save the file, and load your game. Then close the tab.
Now, edit your JS file again, delete the line you just added, and replace it with a line that should retrieve the item:
function setUpGame() {
alert(localStorage.getItem("exampleItem"));
So, just to be clear, there is now nothing in the code that sets the value of "exampleItem".
When you load the game this time, you should see an alert with "This is a great example." – proof that the string has been saved between sessions.
We can remove this item from the local storage using localStorage.removeItem():
function setUpGame() {
localStorage.removeItem("exampleItem");
I recommend you do this now, and then delete the line entirely.
You can also clear everything in the local storage at once, using localStorage.clear() (no arguments required). Okay – not quite everything. Your page can only affect the local storage space assigned to the domain on which it is hosted; I can’t clear your local storage for google.com from a page hosted at tutsplus.com, and vice-versa.
Saving the Best Score
Now that we’ve seen an example, let’s put it into practice.
Whenever a new high score is set, let’s save it to the local storage. It only gets set in one place: when the avatar collides with an enemy and the current score is higher than the best score. So, add the call to localStorage.setItem() in the appropriate place:
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! You survived " + ticksSurvived + " ticks.");
if (ticksSurvived > mostTicksSurvived) {
alert("You beat your old high score by " + (ticksSurvived - mostTicksSurvived) + " ticks!");
mostTicksSurvived = ticksSurvived;
localStorage.setItem("bestScore", mostTicksSurvived);
}
startNewGame();
}
currentEnemyNumber = currentEnemyNumber + 1;
}
When the page is first loaded, we should check to see whether there already is a high score saved in the local storage, and assign mostTicksSurvived to that value, if so. (You don’t need to repeat this check once the page is loaded, unless you’re worried about the user playing the game in two separate tabs at once.)
How do we check whether a value exists? All we have to do is put it inside an if condition:
if (localStorage.getItem("thisItemDoesNotExist")) {
alert("This alert will never appear!");
}
The alert() above never gets called because localStorage.getItem("thisItemDoesNotExist") does not exist. Easy, right? So at the start of setUpGame(), we can just write:
function setUpGame() {
if (localStorage.getItem("bestScore")) {
mostTicksSurvived = localStorage.getItem("bestScore");
}
Have a go. Load the page, set a high score, then beat it. Reload the page, then get a lower score – does it tell you you beat your old score?

Click to try it out.
Great! By the way, if you took the challenge earlier then you’ll be able to see your previous best score in the top-right corner of the canvas, and this will now carry over from session to session.
Sidenote: Strong and Weak Typing
We’re about done for this part of the series, but I just want to point out that, once again, we’ve been treating a string as a number and a number as a string: local storage only saves string values, yet we save a number to it when we save the best score, and use the value we retrieve from it as a number later on.
This is acceptable, because JavaScript is what’s called “weakly-typed”. Other programming languages are “strongly-typed”, which means that if you say that something is a number, then it stays a number; if you say something is a string, then it stays a string.
In a strongly-typed language, if you want to add the string "Score: " to the number 32, then you have to explicitly tell the language to treat 32 as a string, perhaps like so:
"Score: " + (32 as String)
Also, in a strongly-typed language, when you define a variable, you also specify what type it is:
var greeting:String = "Hello.";
var score:Number = 1000;
But JavaScript doesn’t worry about these things. This doesn’t make it better or worse than a strongly-typed language, just different.
Wrapping Up
That’s it for this part of the tutorial. Now your game has both a game over condition and a means of keeping score. Plus, you learned about drawing text to a canvas, choosing fonts, and using the local storage.
If you’d like to challenge yourself before the next part, have a go at making these changes:
- Easy: Draw the best score on the canvas, before the player clicks it.
- Medium: Store the player’s top five scores.
- Hard: Save the positions of all the enemies to local storage, and restore them in the next session (you may have to do extra research here! Hint: look into JSON.)
Enjoy!



View full post on Activetuts+
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+
Sep 17, 2011
Posted on Sep 17, 2011 in Hints and Tips | 10 comments
In this tutorial (the first of a series), you’ll learn the basics of HTML5 game development with JavaScript and the <canvas> element. You don’t need to have any programming experience, or even any HTML experience (apart from this one article). Let’s get started!
Introduction
It would be difficult to have missed the “HTML5 vs Flash” articles that have sprung up all over the web, particularly since Steve Jobs’s Thoughts on Flash last year, and Microsoft’s announcement this week that Windows 8′s web browser won’t support Flash on tablets by default. I’m not going to get into that debate here; whatever your opinion, there’s no harm in learning HTML5. Even if you know Flash, it doesn’t hurt to have another tool in your kit.
This tutorial does not require you know know Flash, or to have had any experience of programming before. In fact, everything that you need to know before you get started is explained in this single article: Get Up to Speed With HTML. That’s it! If you can follow that, you can follow this.
I’m basing this series on my Flash tutorial series, which in turn was based on an even older Flash tutorial by a guy named FrozenHaddock (to whom I am very grateful for letting me use his ideas). This isn’t a direct port of either tutorial, however; I’ll be completely rewriting the code and the explanations to suit HTML5.
A couple of notes:
- Cross-browser compatibility is a real and important issue in web development, but we’re going to be a little selfish and focus on making sure our game works in exactly one browser: Chrome. Rest assured, we will deal with other browsers (including mobile) in other Activetuts+ tutorials, but for the time being, we’ll stick with Chrome, so that we don’t have to split our focus.
- Clean code is more important in HTML5 than in a lot of other platforms, because the underlying programming language (JavaScript) will let you get away with a lot. So, we’re going to make sure that you get in the habit of writing decent code… eventually. We’ll be a little messy at the start, just to get things rolling and avoid making you scroll through thousands of words on “best practices” before actually getting to do anything.
In this first part of the tutorial, we’ll just be setting everything up and putting in some very basic game mechanics. Future parts will add multiple spawning enemies, high scores, menu screens, multiple levels, and all that stuff.
Enough talk – let’s get started!
Setting Up
First thing to do is create a .html file. You can use a basic text editor for this, or spend a few hundred dollars on software specifically designed for HTML development; personally, I’d stick with free software for now. Here are three recommendations: TextEdit (for Mac), Notepad++ (for Windows), and Sublime Text 2 (for Windows, OS X, and Linux). Take your pick.
Create a new file, and enter the following:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
</head>
<body>
</body>
</html>
If you don’t understand what any of that does, read my basic guide to HTML.
Create a new folder on your hard drive called AvoiderGame, and save this HTML file inside it as game.html. If you load it right now, it’ll just show a blank white page (as you know), so put a paragraph of text in there just to make sure everything’s okay. I’ll add a link to this tutorial, but you could enter anything you like – your name and website, perhaps?
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
</head>
<body>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p>
</body>
</html>
JavaScript
Okay, now, you will not be surprised to hear that we will soon be writing some JavaScript – remember, JavaScript lets web pages do things, and that’s exactly what we need for making games. We’ll put all our JavaScript in an external file, in order to keep things tidy, and put this file in a separate folder, to keep things tidier still.
So, create a new folder, called js inside your AvoiderGame folder. Then, create a new, empty text file, and save it as main.js inside this new AvoiderGame/js/ folder.
Alter your HTML to refer to this JS file:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src="js/main.js"></script>
</head>
<body>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p>
</body>
</html>
Note that I haven’t written src="http://active.tutsplus.com/...whatever.../js/main.js", or src="C:\AvoiderGame\js\main.js"; this way, we’re telling the HTML page, “look for a js folder in the same directory as you, and then use the main.js file that’s inside it.” It’s called a relative path.
If you want to test that this is working, put alert("Working!"); in your JS file, then load the HTML page. If you get a dialog box, everything’s okay.
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src="js/main.js"></script>
</head>
<body>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p>
</body>
</html>
CSS
While we’re at it, let’s link an external CSS file as well; we can use it to make the text look nicer, and we might need to use CSS in the game later.
Create a new folder inside AvoiderGame called css, and then create a new, empty text file called style.css inside that. Modify your HTML like so:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src="js/main.js"></script>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p>
</body>
</html>
I’m going to modify my CSS file to match some styles we often use on demo pages here at Activetuts+; feel free to copy it, come up with your own, or leave yours blank:
body {
background: #ffffff;
text-align: center;
padding: 20px;
color: #575757;
font: 14px/21px Arial,Helvetica,sans-serif;
}
a {
color: #B93F14;
}
a:hover {
text-decoration: none;
}
ol {
width: 600px;
text-align: left;
margin: 15px auto
}
This tutorial isn’t about CSS, so if you don’t understand that, don’t worry about it. (If you’re curious, you can look up the meaning of those CSS attributes on W3Schools.com.)
Okay, that’s the dull setup out of the way. You can see how the page looks by clicking here, and you can download the entire source so far in a ZIP file here. Let’s create our avatar!
Get Your Head in the Game
We need an image that will represent our player’s character in this game. Use whatever you like – a photograph of your face, your Twitter avatar, a picture you’ve drawn – but make sure it has a transparent background, that it’s roughly circular, and that it’s about 30x30px.
The original tutorial on which this one is based used a skull. I’m not sure why, but I suspect it was an attempt to subvert games’ typical anti-skeleton stance; after all, under our skin, doesn’t every one of us have a skull?
I’m not one to break with tradition, so I’ll use a skull here too. You can download mine by right-clicking it, if you don’t want to make your own:
And before you ask: yes, I am available for commission.
Whatever you choose, save it as avatar.png inside a new folder, called img inside AvoiderGame. Your folder structure now looks like this:
/AvoiderGame/
game.html
/css/
style.css
/js/
main.js
/img/
avatar.png
So how do we get this into our game? If you’ve been paying attention, you’ll probably suggest this:
<img src="img/avatar.png" alt="Avatar" />
And, it’s true, that would put the avatar in our page! But it’s not what we’re going to use.
Canvas
An img element shows a single image, loaded from a PNG or JPG (or whatever) file. The canvas tag, new to HTML5, can generate an image dynamically, made up of other, smaller images, text, primitive shapes, and so much more, if you desire. Its contents can be modified at any point, so you can give the illusion of motion – or of interaction, if you make the contents change according to what the user does.
We create a canvas in the same way that we create any other HTML element:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src="js/main.js"></script>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<canvas></canvas>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p>
</body>
</html>
…though, if you look at this, you won’t be able to see anything there. It’s invisible, so the only effect it has is to move the text down a little.
With CSS, we can give it an outline so that we can tell it apart from the background. Add this to your CSS:
canvas {
border: 1px solid black;
}
Check it out. It’s kinda small, though; let’s make it 400px by 300px (old-school TV dimensions):
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src="js/main.js"></script>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<canvas width="400" height="300"></canvas>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p>
</body>
</html>
That’s better. Now, I said we could add images to the canvas dynamically, so let’s do that next.
Functions
Remember in the HTML guide I showed you how to make things happen when you clicked HTML elements? Here’s a quick recap:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src="js/main.js"></script>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<canvas onclick="alert('Clicked the canvas');" width="400" height="300"></canvas>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p>
</body>
</html>
If you click the canvas, you’ll get a dialog box message. This is because alert() is a JavaScript function: it’s a shortcut to a few lines of code. We can write our own functions in our JS file. Open main.js and enter the following:
function alertSeveralTimes() {
alert("Hello!");
alert("Look, we can run several messages in a row.");
alert("Annoyed yet?");
}
(Delete the original alert("Working!"); if you haven’t already.)
Do you see how this works? We have created a new function called alertSeveralTimes(), whose contents are inside the curly braces ({ and }). When we tell the browser to run this alertSeveralTimes() function, it will run each of the alerts in turn, one after the other.
Let’s try it out:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src="js/main.js"></script>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<canvas onclick="alertSeveralTimes();" width="400" height="300"></canvas>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p>
</body>
</html>
Try it! We’ve effectively bundled up several alert() functions into one bigger function called alertSeveralTimes(), and told it to run whenever the canvas is clicked.
You might be wondering why the alert("Working!") ran as soon as we opened the page, but the alertSeveralTimes() didn’t, even though they were both in the same place (at the top of main.js). It’s because of that magic keyword function; when the browser sees this, it doesn’t think, “aha, this is some code I must run immediately!”, it thinks, “aha, this is some code I must bundle up into a new function, which I can run later!”
Anyway. Now let’s make our function do something to the canvas. Making it load an image is a little tricky, so we’ll start with something a bit simpler: changing its size.
Modifying the Canvas
One of the most amazing features of JavaScript is its ability to change the HTML of the page. Check this out; modify your JS file so that it contains this:
function changeCanvasSize() {
gameCanvas.width = 600;
gameCanvas.height = 800;
}
You can probably guess what this is doing: it takes the canvas element, and modifies its width and height attributes (we don’t need to use quotes around numbers in JavaScript, unlike in HTML attributes).
Except… how does it know that gameCanvas refers to the canvas that we have in our page?
Well, it doesn’t… yet. We have to make it realise that.
First, we have to give the canvas element an id (short for “identification”) attribute; this is just a name that we use so that we can refer to it in JavaScript later:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src="js/main.js"></script>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<canvas id="gameCanvas" onclick="alertSeveralTimes();" width="400" height="300"></canvas>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p>
</body>
</html>
While we’re at it, let’s make the onclick attribute point to our new changeCanvasSize() function:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src="js/main.js"></script>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<canvas id="gameCanvas" onclick="changeCanvasSize();" width="400" height="300"></canvas>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p>
</body>
</html>
This still isn’t quite enough. We have to let the JavaScript know that it’s dealing with an element from the HTML page (or ‘HTML document’, as it’s more correctly known):
function changeCanvasSize() {
document.getElementById("gameCanvas").width = 600;
document.getElementById("gameCanvas").height = 800;
}
Now, I know, this doesn’t seem entirely logical. Why is gameCanvas suddenly in quotes? Why do we use document.getElementById("gameCanvas") instead of just, say, getDocumentElement("gameCanvas"), or even document.gameCanvas? I promise, this will all become clear during the tutorial series, but for now, just go with it, please.
Test out your new code. The canvas resizes itself as soon as you click on it. Awesome!
Now, I should warn you: programmers are lazy. We hate writing the same code over and over again, and if there’s any way we can reduce the typing required, we’ll take it. So, let me introduce you to a nice shorthand way of referring to the canvas:
function changeCanvasSize() {
var gameCanvas = document.getElementById("gameCanvas");
gameCanvas.width = 600;
gameCanvas.height = 800;
}
See how that works? Just as the function keyword says, “hey, wrap all this code up under the name changeCanvasSize(), please”, the var keyword says, “hey, use the word gameCanvas to refer to the HTML element with an ID of “gameCanvas”, please”. Then (in lines 3 and 4, above), we can use this new shorthand gameCanvas in please of the longer document.getElementById("gameCanvas") – because they refer to the same thing.
That’s important: we haven’t created a new canvas; we’ve just made gameCanvas refer to the existing canvas element.
However, it is possible to use var to create something new…
Click to Skull
Like I said, we’re moving towards adding an image to the (currently empty) canvas. But before we can do that, we have to load the image. And before we can do that, we have to have something to load the image into.
Modify your JS like so:
function changeCanvasSize() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
gameCanvas.width = 600;
gameCanvas.height = 800;
}
(I’ve added a blank line to clearly separate the var statements from the rest.)
Here, we’re using the var keyword again – but this time, it says something subtly different: “hey, create a new Image object and use the word avatarImage to refer to it from now on, please.” The Image object is basically like a img element; the crucial difference here is, it’s not in the HTML. We’ve created this brand new element, but it’s nowhere in the HTML; it’s just floating around in the JavaScript aether. I find that a bit weird.
Just like an img element in the page, this Image is pretty much useless without setting its src, so do that next:
function changeCanvasSize() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
gameCanvas.width = 600;
gameCanvas.height = 800;
avatarImage.src = "img/avatar.png";
}
(Once again I’m using a blank line to keep bits of code that do different things separated from each other (like paragraphs in text), and once again I’m using a relative path to refer to a file’s location.)
So this is now loading the image, but you’ll have to take my word for it at the moment, since it’s still out there in the aether where we can’t see it. However, we can check its other attributes:
function changeCanvasSize() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
gameCanvas.width = 600;
gameCanvas.height = 800;
avatarImage.src = "img/avatar.png";
alert(avatarImage.width);
}
We’re telling it to show us a dialog box containing the value of the width attribute of our image. Check it on your code and see what you get; I get 29, which is exactly right.
With just one more line of code, we can draw the avatar on the canvas:
function changeCanvasSize() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
gameCanvas.width = 600;
gameCanvas.height = 800;
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, 0, 0);
}
Let’s break this down:
gameCanvas.getContext("2d"): We don’t actually draw directly on the canvas, we draw onto what’s called a “drawing context”; this lets us determine whether we want to draw in 2D or 3D. Okay, there’s no 3D context at the moment, but this is letting us plan for the future.
drawImage(): Pretty straightforward. This is a function that lets us draw an image onto the context of a canvas.
avatarImage: This is the image object we’ve got floating around in the aether, remember?
0, 0: These are the coordinates at which we want to draw the image. In school, you’re taught that (0, 0) is the bottom-left of the page; on a computer, it’s the top-left (the x-axis points to the right, and the y-axis points downwards).
Take a look. It works! (If it doesn’t, remember that you should be viewing this in Chrome; I don’t guarantee that this will work in any other browser.)
Multiple Skulls
The drawImage() function works like a potato stamp:
It just takes the contents of the image object and clones them onto the canvas; of course, we’re dealing with pixels, not paint, but you get the idea.
This means we can add multiple skulls to the canvas, like so:
function changeCanvasSize() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
gameCanvas.width = 600;
gameCanvas.height = 800;
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, 0, 0);
gameCanvas.getContext("2d").drawImage(avatarImage, 100, 50);
gameCanvas.getContext("2d").drawImage(avatarImage, 200, 130);
gameCanvas.getContext("2d").drawImage(avatarImage, 300, 270);
}
Check it out, skull party. We can also make the skull appear in a random place each time:
function changeCanvasSize() {
var gameCanvas = document.getElementById("gameCanvas");
var avatarImage = new Image();
gameCanvas.width = 600;
gameCanvas.height = 800;
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100);
}
Math.random() give you a random number between 0 and 1, so Math.random() * 100 gives you a random number between 0 and 100; this means that the coordinates of the new skull are anywhere between (0, 0) and (100, 100). Take a look!
But hold on – why is there only one skull at a time now? Is it something to do with it being a new function? Does the canvas get cleared every time you run a function?
No. The canvas is cleared every time you modify its height or width, even if you don’t change either. So, if we change our JS like so:
function changeCanvasSize() {
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);
}
…then we can keep adding new skulls.
In fact, let’s change the name of the function to drawAvatar(), and tidy things up a bit:
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);
}
Don’t forget to change the onclick attribute of canvas in your HTML:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Avoider Game</title>
<script src="js/main.js"></script>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<canvas id="gameCanvas" onclick="drawAvatar();" width="400" height="300"></canvas>
<p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p>
</body>
</html>
Okay, now let’s get that avatar moving.
Adding Interactivity
I want to make the avatar follow the mouse. We can use the same principle that animators do: if we keep erasing the contents of the canvas, and then re-drawing the avatar at a different position, the avatar will appear to move. So all we have to do is keep redrawing the avatar at the mouse’s coordinates, and we’re set!
How do we do that, though?
A Grand Event
Judging by what we’ve done so far, you might guess that we’d add a onmousemove event attribute to the canvas (which would be triggered every time the user moved their mouse), then make it run drawAvatar(), but specifically at the mouse’s current coordinates. This is inspired, but unfortunately doesn’t really work, simply because it doesn’t offer an easy way to obtain the mouse’s coordinates.
However, it is very close to what we want to do! Take a look at this:
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);
}
This does roughly the same thing as the above suggestion; the redrawAvatar() function (which we haven’t written yet) will be called whenever the mouse moves over the canvas. But there’s one big difference.
Notice how we write redrawAvatar, rather than redrawAvatar() in the code above, whereas in our HTML page, we put drawAvatar() – with “parentheses” (round brackets) – in the onclick event attribute of our canvas. The full reason for that is a little complicated to go into now (though you’ll understand by the end of the series), but it has one very important upshot: it allows us to obtain the mouse’s coordinates.
When the mouse moves, the browser creates a new object – just like when we created a new Image in our JavaScript earlier. This object has certain attributes that have something to do with the thing that triggered its creation; in this case, because the mouse moved, it contains the coordinates of the mouse. Brilliant!
So how do we access it? Well, this new object (which is called a MouseEvent, for reasons that you might be able to guess) gets passed to the redrawAvatar() function. Er, but we haven’t written that yet, so let’s do that now. Add this code to your JS file:
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) {
}
Aha – this time, the way we define the function is a little different: we’ve added the word mouseEvent in-between those parentheses. This is because we are expecting the browser to pass a MouseEvent object to our new function, just like when we passed the coordinates to the drawImage() function.
Since we’ve given it a name, we can access the attributes of this new object:
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) {
alert(mouseEvent.x);
}
Test this out; you’ll have to click the canvas before anything will happen, because it’s inside that function that we make the browser start paying attention to mouse movements.
You’ll notice that the dialog box only appears when the mouse moves over the canvas element. You might also notice something odd about the number: it’s too big! I’m getting numbers of over 900, even though the width of the canvas is only 400.
This is because mouseEvent.x gives the mouse’s x-position from the edge of the page, rather than the edge of the canvas. We can use mouseEvent.offsetX to get the mouse’s x-position from the edge of the canvas:
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) {
alert(mouseEvent.offsetX);
}
Much better!
So, to recap: addEventListener() makes the browser listen for certain events – such as the movement of the mouse – and then run a function when this event is “heard”. The browser creates a new object (like a MouseEvent), and passes it to that function.
It’s a little hard to wrap your head around, but don’t worry; we’ll be using it a lot, so you’ll get the hang of it!
Move Your Head
We’ve nearly got movement. In fact, I recommend you have a go at making the avatar follow the mouse on your own before reading further. You’ll probably come very close!
There’s one big thing that’ll trip you up, though: the word var – which, you’ll remember, you can use to set a shorthand – only “lasts” within the function in which it was defined.
This means that if you try to do, say:
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) {
gameCanvas.width = 400;
alert(mouseEvent.offsetX);
}
…it won’t work, because gameCanvas means nothing outside of drawAvatar()!
So, if you didn’t get it the first time, have another go.
My code is here if you want to check yours (click the little triangle):
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();
avatarImage.src = "img/avatar.png";
gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY);
}
Tada! Oh, wait, dang, I forgot to erase the canvas by changing its width or height. Cool effect, but let’s try that again:
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();
avatarImage.src = "img/avatar.png";
gameCanvas.width = 400;
gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY);
}
Try it out now. Success!
Wait, What Was That For?
When you take another look at your code in a few days’ time, you’re likely to have forgotten what a lot of it is for. In particular, I suspect you’ll forget why you need to resize the canvas.
Fortunately, there’s a way to remind yourself: comments.
Look at this:
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();
avatarImage.src = "img/avatar.png";
gameCanvas.width = 400; //this erases the contents of the canvas
gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY);
}
The browser ignores everything on the line after a //. This means you can write whatever you like there, and it won’t be run as code. It’s called an inline comment and is very important. To help get yourself into the habit of commenting your code, go through it now and write comments after each line that you think you might have trouble understanding after a few days’ break.
Commenting may seem like a waste of time. I think a lot of new programmers assume that when they first start out; it only takes one bad experience with trying to update your own, uncommented code (or worse: someone else’s!) to convince you of its worth, though
Hide the Mouse
At the moment, you’ve got a big stupid mouse cursor hovering over your avatar:
We can fix this with a little CSS. Modify your stylesheet like so:
canvas {
border: 1px solid black;
cursor: none;
}
In most browsers, this will make your cursor disappear when it’s on top of the canvas… but not in Chrome.
Chrome doesn’t support cursor: none;, but it does allow you to replace the cursor with a PNG file of your choice. So, you can create a PNG that’s completely transparent, put it in your img folder, and use that, like so:
canvas {
border: 1px solid black;
cursor: url("../img/transparentcursor.png"), none;
}
(I’ve had to put ../ at the start of the URL because, in CSS, relative paths are relative to the folder of the CSS file, not the HTML file, and “..” says, “the folder above this one”. Also, I’ve put , none after this, because it means that if any browsers don’t support using PNGs for cursors, they’ll use the none attribute instead. Can you see why I wanted to avoid focusing on cross-browser compatibility?)
Unfortunately, this doesn’t work either, because if you use a completely transparent PNG, Chrome just shows a solid black rectangle instead. Thanks, Chrome.
So, instead, I’ve made a 1x1px PNG that’s almost transparent (the single pixel is white, with an opacity of 1%). You can download it here. Copy it to your img folder, then modify your CSS stylesheet:
canvas {
border: 1px solid black;
cursor: url("../img/almosttransparent.png"), none;
}
Test it out. It does work, after all that effort.
Make an Enemy
We’ve accomplished a lot so far, but our avoider game still doesn’t have anything to avoid! The last thing we’ll do, in this part of the series, is create an enemy.
We need an image to represent this. Draw whatever you like, but make sure that it’s roughly circular, and about 30x30px. I’m going to take my cue from FrozenHaddock again. He picked a smiley face for his game’s enemy; I’m not sure why, but I suspect it was a comment on the over-pervasiveness of smileys in modern conversation; a yearning for the days of emotions over emoticons, where poets would pour their hearts into a single sentence of text, rather than simply typing semicolon close-parenthesis ell oh ell. Or maybe it’s just because smiley faces are easier to draw. Regardless, here’s mine:
Call yours enemy.png and put it in the img directory.
You can probably figure out how to draw this enemy (unmoving) on the canvas – if so, give it a shot! Once again, I recommend you do this before reading on.
Here’s my solution (click the little triangle):
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);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150);
}
Your method might be different to mine – that’s okay, as long as it works! But for consistency, please copy my method into your code.
Note that I keep the var statements at the top of their respective functions. This isn’t strictly necessary, but it keeps things tidy, so I recommend it.
Besides that, nothing here should surprise you. There’s no particular significance to the coordinates (250, 150) that I’ve chosen.
Try it out!
Putting the “Avoid” in “Avoider Game”
We’re not going to worry about making the enemy move in this part of the tutorial; that topic deserves more space than I can afford it here. But we will check for a collision between the avatar and the enemy!
First, we’ll do something simpler: we’ll consider a certain area of the screen “off-limits” and pop up a dialog box if the avatar moves into that area.
Modify your redrawAvatar() function like so:
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);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150);
if (mouseEvent.offsetX < 100) {
alert("Too far left!");
}
}
Try it out. If your avatar goes too far to the left side of the screen, the dialog box appears.
Let’s take a closer look at the code:
if (condition) {
outcome;
}
The if statement is a way of checking whether something has happened. It’s made up of two parts: a condition, inside the parentheses, and a result, inside the curly braces. If the condition is true, then the outcome is called.
In our case, the condition is mouseEvent.offsetX < 100. The < symbol means "less than", and remember that mouseEvent.offsetX is the cursor's horizontal distance from the left edge of the canvas. So, this is checking whether the cursor is within 100 pixels of the left edge of the canvas. If it is, then...
...the outcome is run. And in our case, the outcome is alert("Too far left!");, the dialog box function we've used a number of times.
Make sense? Okay, good, because I'm going to make it more complicated:
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);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150);
if (mouseEvent.offsetX < 100 || mouseEvent.offsetY < 100) {
alert("Too far left, or too far up!");
}
}
In this code, we've introduced a new operator: ||. || means (and is pronounced) "or". The if statement therefore reads:
"If the mouse is within 100 pixels of the left edge of the canvas, OR the mouse is within 100 pixels of the top edge of the canvas, show the dialog box."
Try this out, and you'll see that you can't get anywhere near the top or the left of the canvas.
How about this:
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);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150);
if (mouseEvent.offsetX > 150 && mouseEvent.offsetX < 250) {
alert("Stay out of the middle!");
}
}
The && operator means (and is pronounced) "and", and > means "greater than". So, our if statement now reads:
"If the mouse is more than 150 pixels away from the left edge of the canvas AND the mouse is less than 250 pixels away from the left edge of the canvas, show the dialog box."
Test it out, and you'll see that we effectively have an invisible "stripe" down the middle of the canvas where we can't put our mouse.
We're not restricted to just using two clauses at once; check this out:
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);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150);
if (mouseEvent.offsetX > 150 && mouseEvent.offsetX < 250 && mouseEvent.offsetY > 100 && mouseEvent.offsetY < 200) {
alert("Stay out of the center!");
}
}
We've now effectively drawn a 100x100px box, in the centre of the canvas, where we can't put our mouse.
Can you see where we're going with this?
Rather than a 100x100px box in the middle of the canvas, we should use a 30x30px box positioned where our enemy is.
This is the last thing we're going to do in this part of the tutorial, so once again I recommend you have a go yourself. It's pretty fiddly - you'll probably want to get some paper out to draw the avatar and the enemy and label some coordinates - but you can do it if you use what you've learned so far.
My solution is below.
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);
gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150);
//my avatar is 30px wide and the enemy is at x=250, so I have to check whether mouseEvent.offsetX is within 30px
//either side of x=250 (i.e., from 220 to 280)
//similarly, since my avatar is 33px tall, I have to check whether mouseEvent.offsetX is within 33px ABOVE y=150
//but since enemy is only 30px tall, I also check whether mouseEvent.offsetX is within 30px BELOW y=150
//therefore, I check from (117 to 180)
if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) {
alert("You hit the enemy!");
}
}
Try it out here!
Next Time
That's it for the first part of the tutorial. In the next part, we'll get that enemy moving, and we'll add more of them, so that this can start to be a real game!
In the meantime, why not experiment with what you've learned? You could try adding multiple (unmoving) enemies yourself, perhaps with different graphics. Or you could add several avatars on screen at once, all following the mouse in different ways. What happens if you use something like mouseEvent.offsetX + 100 or 300 - mouseEvent.offsetY in your call to gameCanvas.getContext("2d").drawImage()?
I hope you've enjoyed this so far. If anything's not clear, please ask about it in the comments
(One other quick note: at the moment, your code should work just fine on your own computer, but won't work if uploaded to the web. My demos work online, because I did a sneaky trick. Don't worry, I'll explain how to solve this in a future part of the tutorial.)



View full post on Activetuts+
Jul 26, 2011
Posted on Jul 26, 2011 in Hints and Tips | 0 comments
Fans of the Activetuts+ Facebook page can access a new bonus tutorial, this month a sequel to Daniel Ramirez’s Homing Missile tutorial. Check out the preview here!
Introduction
In the previous tutorial we had a homing missile chasing after a single target. This tutorial will show you how to convert your homing missiles into heat-seeking missiles for multiple targets.
Preview the Final Effect
Note how, this time, you can move the cannon with the mouse, and shoot multiple missiles at once.
All you have to do is Like us…
Not On Facebook?
Don’t worry, the tutorial will be posted on Activetuts+ in a month’s time!



View full post on Activetuts+
Apr 8, 2011
Posted on Apr 8, 2011 in Hints and Tips | 0 comments
We’ve just launched a free newsletter you can use to keep up to date with new tutorials and articles on Activetuts+. At the end of each week you’ll get a digest of all the tutorials and articles published that week, mailed straight to your inbox. If there’s a big announcement coming, newsletter subscribers will often be the first to know.
And as a welcome for new newsletter subscribers, you’ll get immediate access to a secret tutorial we’ve never published anywhere on this site!
The newsletter is free to join and ad-free. Look for the ‘Join our newsletter!’ heading in the sidebar and join nearly 1,200 other subscribers (and counting). If you want to see a preview of the newsletter, read on!
The Activetuts+ Weekly Digest
To join, look for ‘Join our newsletter’ in the sidebar on the right!
Get an Exclusive Tutorial
Recreate the Cover Flow Effect using Flash’s Native 3D
by Dru Kepple
You’ve undoubtedly seen the “Cover Flow” view in effect. It’s all over the place on Apple’s stuff. And you’ve also probably seen a number of implementations of Cover Flow in Flash. In this tutorial, you’ll get one more. We’ll leverage the built-in 3D capabilities of Flash Player 10, and build our own XML-driven version of Cover Flow.



View full post on Activetuts+
Page 1 of 9912345...102030...»Last »