Learning Canvas: Making a Snake Game
Published by Chris Coyier
Of all the new elements being added in HTML5, I think
canvas is one of the most exciting. Canvas gives you a fixed-size drawing surface and a selection of basic commands to draw on that surface.
In this tutorial I'm going to show you how to make a simple snake game using canvas. You know, like you used to get on your Nokia phone.
Making the canvas and our first rectangle
Let's make a
<canvas> element, and draw something on it. In this code snippet, I've created the canvas element, set its dimensions, and
drawn a pink rectangle on it.
The first thing to note here is that we don't call drawing methods on the canvas, but on its drawing context. Currently canvas only has one context, which we acquire by calling
First we set the fill style of the context, which takes the form of a CSS colour, e.g.
rgba(100, 100, 100, 0.5). Then we call
fillRect, which takes the coordinates of the top-left hand corner of the rectangle, its width, and its height. Notice that x/y coordinates in canvas start at the top-left hand corner of the canvas - x increases to the right and y increases downwards.
Making a game loop
Now, canvas wouldn't be too much fun if you couldn't make things move. The standard way to make movement in a game is with the game loop. This is a function that gets called at a fixed interval to update the state of the game and display the changes.
I've started adding in some organisation to the code now. To keep the global scope clear, I'm only using one global variable, called
JS_SNAKE. I'm then making an object called
game which will hold all the game-related functions.
game is an object with one public method,
init, and a number of private variables and functions.
The loop basically does three things. First, it updates the game's state (in this case adding 2 to the x position and 4 to the y). Then it updates the display, taking into account the updated state. Finally, it calls
setTimeout, passing the
gameLoop function and the time to wait until calling it again.
This results in a function called every 500ms which will draw a rectangle in a different position each time. We need to call
clearRect each time to clear the canvas, otherwise we'd be drawing over the previous frame each time.
Drawing a snake
Now comes the fun bit: drawing the snake. The snake is a collection of square blocks. Each frame we add a new head and remove the tail, thus moving the snake forward.
The snake object has two public methods,
advance moves the snake one square to the right, and
draw actually draws the snake to the canvas.
advance makes a copy of the head of the snake, moves it forward by 1, then adds the new head to the front of the position array and removes the tail (using
draw calls an internal function,
drawSection, which takes a block position and draws a square. At the beginning of
draw we save the context, and at the end restore it. Saving the context saves its previous settings, so any changes we make can be reverted. In this case, I want to change the
#33a, but I want it to revert to whatever it was before when
draw has finished.
Moving the snake
We now need to hook up the keyboard so we can control the snake. I'm not going to go into detail on the code, but it's fairly straightforward.
There's a new function called in
game.init() which sets the event handlers. So far, the only event we care about is
keydown. If the key pressed is any of the arrow keys (keycodes 37, 38, 39 or 40), then we tell the snake to set a new direction.
setDirection checks to see if the new direction is valid. This is to stop going back on yourself, e.g. pressing left when you're travelling right. If the new direction is valid, then it sets
nextDirection to the new direction. This is then used in
advance so we know where the new head position should be.
Adding an apple
Now the snake needs something to eat, so let's give it an apple.
There's not much to this, except we're drawing a circle for the first time. To draw a circle in canvas, you need to start a new path, then draw a circle, and then fill the path. To start the path, call
beginPath() on the context. To draw the circle call
arc() on the context.
arc takes 6 arguments, the x coordinate of the centre, the y coordinate, the starting angle, the finishing angle, and a boolean indicating whether to draw clockwise or not. The angle is measured in radians, and 360° is 2 * pi radians.
Finishing it off
Now that you know the basics you can view the source code of the final snake game and see what's
going on. It adds the technicalities of collision detection and score-keeping.
(For those playing at home, remember to click into the canvas area so that it can capture your keyboard events. It's presented here in an iframe which cannot receive events from the outside page.)