Accelerated Game Programming with HTML5 and canvas

Welcome to my short tutorial about HTML5 game programming. I’ll try to briefly explain how to create simple games using HTML5, canvas elements and JavaScript. Some code will be omitted during this tutorial but feel free to view my game demo – I have intentionally not minified the source code.

Note: This blog post was written in 2010, general idea still works but now browsers support more interesting features that helps with browser based game development.
I will try to write updated ‘part 2′ post.

HTML5 comes with lots of fantastic features such as :

  • canvas element
  • video and audio support
  • local storage
  • offline web applications
  • geolocation

…and much more. I won’t be covering HTML5 specifications in this post but you can read more about HTML5 here : http://diveintohtml5.org/.

Games were created in HTML before HTML5 so why write this post now, did anything change? Well yes, <canvas> elements come in very handy when creating client-side games.

Canvas and JavaScript can be used to draw and animate game elements easily.

Typical Game class

Before we start with our game implementation and canvas elements I would like to quickly show you the structure of a typical Game class:

function Game() {
this.Initialize = function () {
// initialize all game variables
}
this.LoadContent = function () {
// load content – graphics, sound etc.
// since all content is loaded run main game loop
// Calls RunGameLoop method every ‘draw interval’
this.GameLoop = setInterval(this.RunGameLoop, this.DrawInterval);
}

this.RunGameLoop = function (game) {
this.Update();
this.Draw();
}

this.Update = function () {
// update game variables, handle user input, perform calculations etc.
}

this.Draw = function () {
// draw game frame
}
}

This is a typical JavaScript game skeleton. Interesting bit here is the “setInterval” method. When all resources are loaded, we can start the main game loop, collect user input, perform calculations and render our game every X ms. This is needed for games that perform some calculations in the background, AI movement, animations etc. For more static games that need to redraw content only based on user input we can modify this skeleton class and get rid of the game loop.


function Game() {
this.Initialize = function () {
// initialize all game variables
}

this.LoadContent = function () {
// load content – graphics, sound etc.
var ourGame = this;
$(document).bind('keyup', function (event) {
ourGame.Update(event);
ourGame.Draw();
});

}

this.Update = function (event) {
// update game variables, handle user input, perform calculations etc.
}

this.Draw = function () {
// draw game frame
}
}

Here both the game update and redrawing will happen only in response to user input. This approach is less CPU intensive but is only possible for simple games.

I’m going to show you an example of a quite simple, classic game that we all know: Sokoban. There are lots of sokoban clones made for every available platform but I haven’t seen any written using canvas elements yet.

Getting started

Let’s start with creating our HTML5 page with a single canvas element:


<html>
<head>
<title>Sokoban</title>
</head>
<body>
<canvas id="canvas" width="800" height="500">
Canvas not supported.
</canvas>
</body>
</html>

That’s it! Now we can see… a blank page in the browser supporting canvas elements: Chrome, Firefox and Safari are “Canvas not supported” in Internet Explorer 8 and older versions.

Before we can start drawing on canvas we need to get the drawing context. Canvas exposes one or more drawing contexts but we will focus on the most popular and supported one – “2d” context.

Let’s add a reference to JavaScript file straight after our closing canvas tag:

<script type="text/javascript" src="../Scripts/test01.js"></script>

This is part of this JavaScript file.


var _canvas = document.getElementById('canvas');
var _canvasContext = null;
//check whether browser supports getting canvas context
if (_canvas && _canvas.getContext) {
_canvasContext = _canvas.getContext('2d');
// ... drawing here ...
}

I will quickly cover canvas context drawing methods that will be used during this tutorial. The only methods that we need are :

  • drawImage(img, x, y);
  • fillRect(x, y, width, height);
  • strokeRect(x, y, width, height);
  • fillText(‘Text’, x, y);

Note: It’s important to remember that canvas has it’s beginning (0,0) in upper left corner.

Those methods are very straight-forward. drawImage is drawing a specified Image object or <img> on canvas in location specified by x, y. fillRect and strokeRect are both used for drawing rectangles, the only difference is that the first method is drawing a rectangle filled with colour and the second one is drawing an empty rectangle with coloured border. fillText is used to put text on canvas.

A working demo can be found here: http://demo2.felinesoft.com/Sokoban/Home/Test01

JavaScript source that is rendering our 2 rectangles, text and an image can be found here:

http://demo2.felinesoft.com/Sokoban/Scripts/Test01.js

This is how the test page should look like :
Test page 01

Double buffering

Since we now have a game skeleton and know how to draw on canvas the only thing that is left before actual game implementation is double buffering. In our game double buffering does not come in that handy since we won’t have any animating effects that can flicker, but since this post should be your starting point in the land of canvas game programming I figured that I’ll quickly show you how to implement simple double buffering on canvas.

The idea behind double buffering is to reduce flickering by first drawing in memory buffer and later drawing the entire image from the memory onto the screen.

We only need to modify our canvas JavaScript slightly:

_canvas = document.getElementById('canvas');
if (_canvas && _canvas.getContext) {
_canvasContext = _canvas.getContext('2d');
_canvasBuffer = document.createElement('canvas');
_canvasBuffer.width = _canvas.width;
_canvasBuffer.height = _canvas.height;
_canvasBufferContext = _canvasBuffer.getContext('2d');
}

Now instead of drawing on _canvasContext object we should draw on _canvasBufferContext and after the drawing is done call just one line:

_canvasContext.drawImage(_canvasBuffer, 0, 0);

This will draw whole content of our canvas buffer onto the screen and that’s it!

So what exactly are we trying to write?

We need to write a simple Sokoban game. The goal of the game is to move all “boxes” to marked fields. Game rules:

  • Boxes can only be pushed, not pulled.
  • Only one box can be moved at a time

For more information about Sokoban visit: http://en.wikipedia.org/wiki/Sokoban

We will need to create a few JavaScript classes for our game :

  • Main “Game” class
  • “Map” representation
  • Player
  • Map statistics – this will need to display level number, number of moves / pushes performed
  • Class for every “drawable” element :
    • Wall
    • Box
    • Box on Goal
    • Floor
    • Empty space
    • Goal
    • Image Repository – this class will be storing all images used in our game. Instead of loading and creating new instance of Image we will be able to get our images from here.

Map representation

In my example all maps are stored in one XML file. I’m using the following format:


<?xml version='1.0'?>
<Levels>
<Level No="1" Width="19" Height="11">
<Row>    #####</Row>
<Row>    #   #</Row>
<Row>    #$  #</Row>
<Row>  ###  $##</Row>
<Row>  #  $ $ #</Row>
<Row>### # ## #===######</Row>
<Row>#   # ## #####  ..#</Row>
<Row># $  $          ..#</Row>
<Row>##### ### #@##  ..#</Row>
<Row>    #     #########</Row>
<Row>    #######</Row>
</Level>
....
</Levels>

Explanation of character representation:

  • ‘#’ – wall
  • ‘$’ – box
  • ‘ ‘ or ‘=’ – empty space
  • ‘ ‘ – Floor
  • ‘@’ – player
  • ‘.’ – goal
  • ‘*’ – box on goal
  • ‘+’ – player on goal

I’ve created a base class for all elements that can be drawn:


function DrawableElement() {
this.GetImage = function () {
//implement this method in your sub-class if your element is represented by image
//this should return an Image object
return null;

}

this.GetFillStyle = function () {
//implement this method in your sub-class if your element is represented by filled rectangle
//this should return canvas "fillStyle" string
return null;

}

this.ImageRepository = null;

}

Sample sub-class (ImageRepository implementation omitted but can be found by looking at demo page source files):


function Floor(){
this.GetImage = function () {
return this.ImageRepository.Floor;
}
}

JavaScript inheritance creation:


Floor.prototype = new DrawableElement();
Floor.prototype.constructor = Floor;

I’m using very simple WCF service to return a specific level. You can store each level in a separate XML file, just change ajax url parameter to url path pointing to your xml file, for example:

“http://<domain>/level_” + levelNumber +  “.xml”,

It’s very easy to access web service or direct xml file using jQuery :


$.ajax({
type: "GET",
url: "http://localhost/SokobanWCF/SokobanService.svc/GetMap/" + levelNumber,
dataType: "xml",
success: this.ParseXmlMap,
context: this

});

jQuery is also very useful for parsing XML, the following code demonstrate how a map can be constructed ( part of ParseXmlMap method ):


$(xml).find("Row").each(function () {
var wall = false;
var row = $(this).text();
for (var x = 0; x < mapWidth; x++) {
// some rows are shorter than map width - fill rest with Empty elements

if (x >= row.length) {
mapRef[x][y] = new Empty();
} else {
switch (row[x]) {
case " ":
// if we had wall already that mean we need to insert a Floor element,
// for Empty elements that are between walls on some maps we are using '=' character
if (wall) {
mapRef[x][y] = new Floor();
}
else {
mapRef[x][y] = new Empty();
}
break;

case "#":
mapRef[x][y] = new Wall();
wall = true;
break;

case "$":
mapRef[x][y] = new Box();
break;

(...)

As you can see each map/level is represented using two dimensional Array. I’m iterating through every XML element – <Row> and later through every character within the row value.

Since downloading and parsing XML document can take a while I’m using a “Loaded” flag on Map object to determine whether document parsing has ended and the map is ready to be draw. In my Game class in LoadContent method I’m setting the timer to check whether map is ready ( every 50 ms ) :


this.InitialUpdateDraw = setInterval(this.InitialUpdateRun, this.CheckInterval);

and InitialUpdateRun method :

this.InitialUpdateRun = function (ev) {
if (_map.Loaded && _imageRepository.Loaded()) {
document.sokobanGame.Update(ev);
document.sokobanGame.Draw();
//we don't need timer anymore
clearInterval(document.sokobanGame.InitialUpdateDraw);
}

}

This is needed since when the level is loaded we need to update game variables and display the level. Later, all game Update and Draw methods will only be called after user performs some sort of action.

Updated method for Map is very simple. We only need to check for end game conditions and update number of moves / pushes.


this.Update = function () {
//check for end game conditions
if (this._goals > 0 && this._goalsAchived == this._goals) {
this.Finished = true;
}

_levelStatistics.Update();
}

Finally the Map Draw method. Thanks to DrawableElement base class and inheritance we just need to iterate through the two dimensional Array and the correct image will be draw for each element because of our JavaScript implementation of Polymorphism.


this.Draw = function (canvasContext) {
var xCanvasPos = 20;
var yCanvasPos = 20;
var tileSize = 30;

for (var y = 0; y < this._height; y++) {
xCanvasPos = 20;
for (var x = 0; x < this._width; x++) {
var img = this._map[x][y].GetImage();
if (img != null) {
// draw image
canvasContext.drawImage(img, xCanvasPos, yCanvasPos);
} else {
// draw rectangle
canvasContext.fillStyle = this._map[x][y].GetFillStyle();
canvasContext.fillRect(xCanvasPos, yCanvasPos, tileSize, tileSize);
}

xCanvasPos += tileSize;

}
yCanvasPos += tileSize;
}
// this is used to properly position level statistics according to the level
this.OnScreenWidht = xCanvasPos;
this.OnScreenHeight = yCanvasPos;
_levelStatistics.Draw(canvasContext);
}

Player movement

Player movement is being handled on keyup event. I used WASD buttons to handle user movement. Just as a reminder W = up, S = Down, A = Left, D = Right.

Move direction is represented by MoveDirection enumeration.

Handler method for user movement :

this.KeyCheck = function (event) {
var KeyID = event.keyCode;
switch (KeyID) {
case 87: // W
this.Move(this.MoveDirection.Up);
break;

case 65: // A
this.Move(this.MoveDirection.Left);
break;

case 68: // D
this.Move(this.MoveDirection.Right);
break;

case 83: // S
this.Move(this.MoveDirection.Down);
break;

}

}

Obviously the user cannot move two boxes or go through walls. We need a method to validate the user movement:


this.ValidateMove = function (targetCell, nextCell) {
var posToMove = targetCell.constructor;
if (posToMove == Wall) {
// wall is next, player cannot move there
return false;

}

var nextObject = nextCell.constructor;
if ((posToMove == Box || posToMove == BoxOnGoal) && (nextObject == Wall || nextObject == Box || nextObject == BoxOnGoal)) {
//player attempts to push box, if the next element after the box is wall or another box – player cannot move
return false;
}

return true;

}

The only interesting bit here is the “constructor” property. This property returns a function that was used to create object stored in this cell. That way we can easily check what type of elements surrounds the player.

In previous section, while creating Wall, Floor and DrawableElement base class I’ve created inheritance using the following way:

Floor.prototype = new DrawableElement();
Floor.prototype.constructor = Floor;

Second line is very important here, without this line returned function for Floor object would be DrawableElement…this would prevent us from using move validation as described above.

I’ll omit the full code responsible for changing player position; it’s just assigning player object to new position on our two dimensional array and can be viewed by examining source code on the demo page.

Level finished and loading new level

I used cookies to be able to load and save previous state for the user; after a level is finished I’m inserting a cookie specifying the level that needs to be loaded. LoadContent is reading the cookie and loading the right map. That way the user can close browser and come back to the same level later. Loading and saving of cookies is handled by cookie plugin that can be found here: http://plugins.jquery.com/project/cookie

I’ve also added “Congratulations” popup when user finished level with a button saying “Go to the next level”. Automatic map loading seemed to me not very user friendly.

My sokoban clone contains 40 classic levels so it’s very possible that user won’t be able to finish them all during one session.

Finally!

View Demo

I wanted to make this blog post as brief as possible. It turned out quite long but finally we are at the end of our journey. This is how our game looks; see the image below or visit View Demo.

I’ve skipped some code during this “Accelerated” tutorial but I left it non-minified so it can be viewed by looking at live demo source code.

finished game

What next?

Possible game improvements that are out of scope of this tutorial:

  • High score list ( after level is finished POST number of moves and pushes to the WebService and store them in Database, later display top 5 best scorers for every level )
  • Undo move option.
  • More secure end game conditions check. Currently it’s very easy to start with map level 40.
  • In case you want to give ability for the users to navigate between levels, don’t worry about previous point – implement level selection instead.
  • Ability to upload your own maps and share them with friends.

Hope you’ve enjoyed reading this post and it gave you a bit of insight into what game programming in HTML5 looks like.

 

Tags: ,

22 Responses to “Accelerated Game Programming with HTML5 and canvas”

  1. GB says:

    Thanks for the double-buffering code.

    It’s worth noting that if you are using clearRect() before drawing your _canvasBufferContext, you’ll also need to clear your _canvasContext – I assume this is because drawImage() is honoring the transparency of the canvasBuffer.

  2. Kevin Roast says:

    Nice tutorial. FYI double-buffering is not needed for canvas – there is no notable difference when using it or not. You will find that the other games out there using canvas don’t bother with it.

  3. Lucas says:

    Thanks for your comment!
    I’ve took a look at akihabara game programming library and it uses double buffering for games, but you are right some games don’t bother with it.
    It’s very interesting, now I’m tempted to write some app that will allow me to compare drawing performance with and without double buffering enabled.
    Of course in my example double buffering won’t do much difference but I wanted to at least show how it can be achieved.

  4. Double buffering proper may not be needed on a canvas, probably because it has something like it built-in. But rendering a lot of graphics takes time, potentially enough to slow your game to a crawl. JS is fast nowadays, but not that fast. So you can benefit from pre-rendering slow-changing parts of the image on a second canvas, and bit-copying them as a whole every frame with drawImage().

  5. Roger says:

    I tried your code and I can’t make it work. I don’t see any image. ImageRepository never loads the image. I copied all the files to my computer, including the images and nothing. Could you tell me what I could be doing wrong? Thanks

  6. Lucas says:

    It’s quite likely that it does not work because you don’t have Maps. Without maps JavaScript won’t have anything to render.
    Maps are returned from Windows Communications Foundation web service.
    You should be able to tweak the code to work without WCF web service.
    You can get the map for level 1 from “Map representation” section, then you just need to update URL in the “LoadMap” function inside “Map.js” file to point to your XML file containing level 1 map.
    Should work :)

  7. mikes says:

    I tried this with Google Chrome 11.0.696.60 and it seems to work fine.

  8. rtpHarry says:

    I tried this in FF4 and it doesn’t work correctly. When I press WSAD the browser tries to do a quick search so the cursor is then focused in the quick search entry box. I assume you are missing some code to cancel the event after you capture a keypress?

  9. Lucas says:

    Hi,
    I’ve tried that on my FF4 first time today (game was build before FF4 was released) but it seems to work correctly on my machine.
    Regardles of that, I’ve modified code a bit and I hope it will work correctly for you now.

    Regards,
    Lucas

  10. Jérémie says:

    Hi Lucas,
    I’m starting to dive into HTML5 and game development and I’ve stumbled upon your tutorial.

    First, thanks for sharing. Very nice to have all the code in order to understand.

    Second, i need some help to complete the tutorial. I’m trying to adapt it to an Android Phone and for that, I need to be able to dynamically draw the map so that it fits the screen (HTC Desire).
    Do you any tips on how to do that ?

    Thanks!

  11. Colton says:

    @Lucas, Great tutorial. I only found it cause I was looking for the double buffering part (I had too much flicker D=, but now I have less =D). I’ll definately be bookmarking this and looking at it later though. =D Thanks!

    @Jérémie, I actually have some code for you that worked for me at least. Never tried it on android though, just Google Chrome.
    _canvas.width = document.width;
    _canvas.height = document.height;
    /* The following is so the drawing code can draw within the right dimensions */
    canvasW = _canvas.width;
    canvasH = _canvas.height;

  12. Rösi says:

    Hi Lucas,

    thank you for posting this example. It helped me very much on my way to a working game.
    Especially the GameSkeleton.

    Your Demo Code is very readable and nice structered.
    Combined with the naming conventions i’am very happy with this code.
    Some other examples made me wonna kill myself.

  13. Max says:

    What about the canvas and touch events. Can those be supported in any way?

  14. Lucas says:

    @Max
    Please try:
    canvas.addEventListener( ‘touchstart’, onTouchStart, false );
    canvas.addEventListener( ‘touchmove’, onTouchMove, false );
    canvas.addEventListener( ‘touchend’, onTouchEnd, false );
    To check whether your game/website is running in environment with touch support you can use:
    var supportsTouch = “createTouch” in document;

    I hope that helps.

    Regards,
    Lucas

  15. jm says:

    To Lucas,
    Note1: You never really told Roger were to download the map files. Here’s the urls:
    “http://demo2.felinesoft.com/SokobanWCF/SokobanService.svc/GetMap/1″
    .
    thru
    .
    “http://demo2.felinesoft.com/SokobanWCF/SokobanService.svc/GetMap/40″
    Note the xml extension is not needed in Firefox, just use “File Save as” after the xml txt is displayed.
    ..oh and it would have been nice just to have all needed files zipped with all resources relatively (locally) referenced in the .js files.
    Note2: the test01.js file makes reference to a “../Content/Images/cos2a.gif” file
    I assume that’s referencing this url:
    “http://demo2.felinesoft.com/Sokoban/Content/Images/cos2a.gif”, but the file appears to be missing.

  16. Miglu says:

    At $.ajax({ I got this error: “$ is not defined”
    What is the problem?

  17. Michal Marianek says:

    Thanks for ths tutorial

  18. Mawuli says:

    Thanks for this tutorial. I am new to game programming, and will be grateful if you could restructure and zip the game files so it can be run locally by others too. I have downloaded all the files, but the map does not render.
    Thanks

  19. Pablo Farias says:

    Thanks for the great tutorial.

    For the ones who want to get into HTML5 game dev, I’ve created an online course called HTML5 Mobile Game Development for Beginners, aimed for people with basic JavaScript knowledge who want to get into this awesome field:

    http://www.udemy.com/html5-game-development

  20. James says:

    @Mawuli, I ran into this same issue as well. I resolved it by changing the LoadMap function in Maps.js to use a local copy of the map xml. Just change the URL in the AJAX call.

    @Lucas, Your tutorial has proven very helpful as an introduction to HTML5 game/canvas dev. Thank you.

  21. Tanuj says:

    I copied all the files and even changed the xml path in Map.js, but getting an error as “this._map is null”

  22. Keegan says:

    Why do you put an underscore in front of some variables and not in front of others. What is the structure here?