Learning Canvas: Making a Snake Game

Published by Chris Coyier

Note from the editor: The following is a guest post by Nick Morgan aka Skilldrick. I don't know much about canvas, so it's a pleasure to be able to share an article about a current web technology that is outside of my current range of knowledge.

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 .getContext('2d').

First we set the fill style of the context, which takes the form of a CSS colour, e.g. #f00 or 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 and draw. 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 unshift and pop, respectively).

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 fillStyle to #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.)