Tutorial: HTML5 Gaming Basics

Posted on May 7, 2012 (one year ago). Seen 2,211 times. No comments. Permalink Feed
Photo Adrien Gueret
Software Engineer
Marakana, Inc.
Member since Jul 22, 2011
Location: London
Stream Posts: 9
Tagged as: HTML5 Tutorial

HTML5 defines a lot of new features. One of them is the <canvas> tag, which simply is an empty image.

Thanks to JavaScript, we can draw on this image. The <canvas> tag allows us to have dynamic pictures on our web sites.

In this tutorial, we’ll use this great property in order to develop a small video game!


Note <canvas> is a recent tag and only modern browsers support it. You can forget it if you want to use Internet Explorer before its ninth version.

Before starting

Basic HTML structure

Before starting to code, we must know how to draw on the <canvas> tag. First off, let’s consider this HTML code:

<!DOCTYPE html>
<html>
    <head>
        <title>HTML5 basic gaming</title>
        <meta charset="UTF-8" />
        <script src="game.js"></script>
    </head>
    <body style="background-color: #f3f3f3;">
        <p style="text-align: center;">
            <canvas id="game">
                <b>Hurm, your browser can't display the game...<br />
                Have you ever tried <a href="http://www.mozilla.com/fr/firefox/">Mozilla Firefox</a>,
                <a href="http://www.opera.com/">Opera</a> or <a href="http://www.google.com/chrome/">
                Google Chrome</a>?</b>
            </canvas>
        </p>
    </body>
</html>

Note If the above example is an empty web page, that means that your browser supports <canvas> tag. Otherwise, you should update it!

This code is a simple HTML5 page. The script we’ll develop will be saved in the file game.js.

The text you can see inside the <canvas> tag is displayed only if the browser doesn’t support it.

The 2D context

Now we’ll write code inside our game.js file. In order to draw on the <canvas> tag we have to retrieve it as such:

window.onload=function()
{
    var game = document.getElementById('game');
};

The canvas element has a method called getContext(). This method returns the drawing context of the <canvas> tag: this object will permit us to draw!

window.onload=function()
{
    var game = document.getElementById('game'),
    context = game.getContext('2d');
};

Note The parameter '2D' indicates that we want to draw 2D pictures. We can also specify a 3D context, but for now it is not managed well by browsers.

Drawing a rectangle

We will build a very simple game which will contain only rectangles. We will use the method fillRect() to draw these figures.

This method has four parameters: the first two are the abscissa and the ordinate of the rectangle, the last ones are its width and its height.

window.onload=function()
{
    var game = document.getElementById('game'),
    context = game.getContext('2d');

    //Draw a square 32px*32px in (320,320)
    context.fillRect(320,320,32,32);
};

Huh? There is nothing!

The problem is that we have drawn our square outside the <canvas>: the default dimensions of this tag are 300px*150px!

We can solve this by changing these dimensions:

window.onload=function()
{
    var game = document.getElementById('game'),
    context = game.getContext('2d');

    game.width = 640;
    game.height = 640;

    //Draw a square 32px*32px in (320,320)
    context.fillRect(320,320,32,32);
};

We can also add some colors thanks to fillStyle property: it’s a string that accepts any colors annotation, like CSS.

window.onload=function()
{
    var game = document.getElementById('game'),
    context = game.getContext('2d');

    game.width = 640;
    game.height = 640;

    //Set color to red
    context.fillStyle = 'red';
    //Draw a square 32px*32px in (320,320)
    context.fillRect(320,320,32,32);

    //Set color to green
    context.fillStyle = '#0f0';
    //Draw a rectangle 16px*64px in (120,220)
    context.fillRect(120,220,16,64);

    //Set color to blue
    context.fillStyle = 'rgb(0,0,255)';
    //Draw a rectangle 48px*24px in (220,120)
    context.fillRect(220,120,48,24);
};

Labs: White to Black

Now that we now how to draw rectangles with colors, try the following lab.

We want to display all the shades of gray inside our <canvas>.

To do that, you’ll use the context object we’ve just seen, its property fillStyle and its method fillRect().

  1. Remember that there are 256 different colors between white and black.

  2. The RGB annotation probably is the simplest way to do this exercise.

  3. You can also display the shades of red, green and blue.

You can see the desired result by clicking on this link.

The solution is in the file labs1-solution.js.

Init the game

Now that we know how to draw rectangles, we can think about our future game!

This game will be a puzzle game: the player will control a square. It will be able to move inside a small labyrinth. The player will have to walk on each square in order to go to the next level. The player won’t be able to move back on a square already walked on.

images/screen.png
Figure 1: Screen of the future game

In order to make the development of this game easier, we’ll use a JavaScript object:

window.onload=function()
{
    Game={};
};

This Game object will contain the definition of the game. First of all, we can set two properties: the <canvas> and its context!

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d')
    };
};

We can also specify the size of each square composing the labyrinth. If we want to have bigger or smaller graphics later, we’ll just have to update this value!

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32
    };
};

In order to run the game, an init() method will be very useful. For now, this method will only set the dimensions of the <canvas> and call another method: draw().

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'init': function()
        {
            this.canvas.width = 640;
            this.canvas.height = 640;
            this.draw();
        },
        'draw': function()
        {
            //Set color to red
            this.context.fillStyle = 'red';
            //Draw a square 32px*32px in (320,320)
            this.context.fillRect(320,320,32,32);

            //Set color to green
            this.context.fillStyle = '#0f0';
            //Draw a rectangle 16px*64px in (120,220)
            this.context.fillRect(120,220,16,64);

            //Set color to blue
            this.context.fillStyle = 'rgb(0,0,255)';
            //Draw a rectangle 48px*24px in (220,120)
            this.context.fillRect(220,120,48,24);
        }
    };

    //Let's run the game!
    Game.init();
};

Display our first level

It’s time to create our first level!

We’ll build a level inside a two-dimensional array.

A 0 will represent an empty place, 1 a wall, and 2 the player.

images/array2level.png
Figure 2: A JavaScript array transformed into a level

So, let’s start by adding a levels property to our Game object. It’s an array which will contain several two-dimensional arrays as levels!

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'levels':
        [
            [
                [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
                [1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1],
                [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
            ],
            [
                [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
                [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,1,0,0,2,0,0,0,1,0,0,0,1],
                [1,0,0,0,1,0,0,1,1,0,0,1,0,0,0,1],
                [1,0,0,0,1,1,1,1,1,1,1,1,0,0,0,1],
                [1,0,0,0,1,0,0,1,1,0,0,1,0,0,0,1],
                [1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1],
                [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
            ],
            [
                [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
                [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1],
                [1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1],
                [1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1],
                [1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1],
                [1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1],
                [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
            ],
            [
                [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
                [1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1],
                [1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,1],
                [1,0,0,1,1,1,0,1,0,0,0,0,0,0,0,1],
                [1,0,0,1,0,0,0,1,2,1,1,0,0,0,1,1],
                [1,0,0,0,0,1,0,0,1,0,0,0,0,0,0,1],
                [1,0,0,1,0,0,0,0,1,0,1,0,0,1,0,1],
                [1,1,0,0,0,1,1,0,0,0,1,0,0,0,0,1],
                [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
            ]
        ],
        'init': function()
        {
            //[...]
        },
        'draw': function()
        {
            //[...]
        }
    };

    //Let's run the game!
    Game.init();
};

During gameplay, how will we know the current level? We have to store it!

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'levels':
        [
            //[...]
        ],
        'currentLevel': [],
        'init': function()
        {
            //[...]
        },
        'draw': function()
        {
            //[...]
        }
    };

    //Let's run the game!
    Game.init();
};

We’ll begin by displaying the first level, so we’ll store this into a new currentLevel property, the first element of levels. As a two-dimensional array, we’ll push into currentLevel each array included in it. In order to store independent copies of arrays, we must call the slice() method, otherwise we’ll only store copies of references!

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'levels':
        [
            //[...]
        ],
        'currentLevel': [],
        'init': function()
        {
            for(var i=0, length=this.levels[0].length; i<length; i++)
            {
                this.currentLevel.push(this.levels[0][i].slice());
            }

            //We update the dimensions of the <canvas>
            this.canvas.height=this.currentLevel.length*this.tile_size;
            this.canvas.width=this.currentLevel[0].length*this.tile_size;

            this.draw();
        },
        'draw': function()
        {
            //[...]
        }
    };

    //Let's run the game!
    Game.init();
};

Go back to the draw() method!

Here, we’ll iterate through the elements of the current level and we’ll draw a rectangle for each of them. This rectangle will be black if we have a wall, gray in case of an empty place and red for the player.

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'levels':
        [
            //[...]
        ],
        'currentLevel': [],
        'init': function()
        {
            //[...]
            this.draw();
        },
        'draw': function()
        {
            var t=this.tile_size;
            for(var y=0,level=this.currentLevel,height=level.length; y<height; y++)
            {
                for(var x=0,width=level[0].length; x<width; x++)
                {
                    switch(level[y][x])
                    {
                        //Wall
                        case 1:
                            this.context.fillStyle='#000';
                        break;

                        //Player
                        case 2:
                            this.context.fillStyle='#f00';
                        break;

                        //Empty place
                        default:
                            this.context.fillStyle='gray';
                        break;
                    }

                    this.context.fillRect(x*t,y*t,t,t);
                }
            }
        }
    };

    //Let's run the game!
    Game.init();
};

Note Try to modify the value of Game.tile_size: the size of the game changes in consequences!

Move the player

We want to control the player, right? So we have to know its position.

We’ll create another property for our object Game. This new property is an object too, and it will store the position of the player:

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'player':
        {
            'x': 0,
            'y': 0
        }
        'levels':
        [
            //[...]
        ],
        'currentLevel': [],
        'init': function()
        {
            //[...]
        },
        'draw': function()
        {
            //[...]
        }
    };

    //Let's run the game!
    Game.init();
};

We have to get its position at the initialization of the game, so go back to init() method.

We’ll iterate through the current level until we find a 2 (=the player!):

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'player':
        {
            'x': 0,
            'y': 0
        },
        'levels':
        [
            ///[...]
        ],
        'currentLevel': [],
        'init': function()
        {
            for(var i=0, length=this.levels[0].length; i<length; i++)
            {
                this.currentLevel.push(this.levels[0][i].slice());
            }

            //We update the dimensions of the <canvas>
            this.canvas.height=this.currentLevel.length*this.tile_size;
            this.canvas.width=this.currentLevel[0].length*this.tile_size;

            main_loop:
            for(var y=0,level=this.currentLevel,height=level.length; y<height; y++)
            {
                for(var x=0,width=level[0].length; x<width; x++)
                {
                    if(level[y][x]==2)
                    {
                        this.player.x=x;
                        this.player.y=y;
                        break main_loop;
                    }
                }
            }

            this.draw();
        },
        'draw': function()
        {
            //[...]
        }
    };

    //Let's run the game!
    Game.init();
};

Now that we know where the player is, we can move it!

The easiest way is to use the keyboard. Thanks to the init() method, we’ll attach the onkeydown event to it!

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'player':
        {
            'x': 0,
            'y': 0
        },
        'levels':
        [
            //[...]
        ],
        'currentLevel': [],
        'init': function()
        {
            //[...]
            this.draw();

            var game = this;
            document.onkeydown=function(e)
            {
                if (!e) e=window.event; //IE fix
                var code=(e.which)?e.which:e.keyCode,
                x=game.player.x,
                y=game.player.y;

                switch(code)
                {
                    //Left
                    case 37:
                        x--;
                    break;

                    //Up
                    case 38:
                        y--;
                    break;

                    //Right
                    case 39:
                        x++;
                    break;

                    //Down
                    case 40:
                        y++;
                    break;
                }

                //If the wanted place is empty
                if(game.currentLevel[y][x]===0)
                {
                    //We set the previous place to valid
                    game.currentLevel[game.player.y][game.player.x]=3;
                    game.player.x=x;
                    game.player.y=y;
                    game.currentLevel[y][x]=2;

                    //Update the image
                    game.draw();
                }
            };
        },
        'draw': function()
        {
            //[...]
        }
    };

    //Let's run the game!
    Game.init();
};

To specify that a square has already been walked on, we change its value to 3. Don’t forget to treat this value in the draw() method!

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'player':
        {
            'x': 0,
            'y': 0
        },
        'levels':
        [
            //[...]
        ],
        'currentLevel': [],
        'init': function()
        {
            //[...]
        },
        'draw': function()
        {
            var t=this.tile_size;
            for(var y=0,level=this.currentLevel,height=level.length; y<height; y++)
            {
                for(var x=0,width=level[0].length; x<width; x++)
                {
                    switch(level[y][x])
                    {
                        //Wall
                        case 1:
                            this.context.fillStyle='#000';
                        break;

                        //Player
                        case 2:
                            this.context.fillStyle='#f00';
                        break;

                        //Valid square
                        case 3:
                            this.context.fillStyle='#fff';
                        break;

                        //Player
                        default:
                            this.context.fillStyle='gray';
                        break;
                    }

                    this.context.fillRect(x*t,y*t,t,t);
                }
            }
        }
    };

    //Let's run the game!
    Game.init();
};

Go to next level!

Our game contains severals levels but we can only play the first one! Furthermore, we can’t win, and that’s not very cool, is it?

For now, we have stored the current level into a variable: currentLevel. In order to go to the next level, we have to store the id of the current level as well.

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'player':
        {
            'x': 0,
            'y': 0
        },
        'levels':
        [
            //[...]
        ],
        'currentLevel': [],
        'id_level': -1,
        'init': function()
        {
            //[...]
        },
        'draw': function()
        {
            //[...]
        }
    };

    //Let's run the game!
    Game.init();
};

Now we can define a method which will permit us to go to the next level: goToNextLevel().

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'player':
        {
            'x': 0,
            'y': 0
        },
        'levels':
        [
            //[...]
        ],
        'currentLevel': [],
        'id_level': -1,
        'init': function()
        {
            //[...]
        },
        'goToNextLevel': function()
        {
            //If we finish the last level, we go back to the first one
            if(++this.id_level>=this.levels.length)
            {
                this.id_level=0;
            }

            this.currentLevel.length=0; //Clear the array

            for(var i=0, length=this.levels[this.id_level].length; i<length; i++)
            {
                this.currentLevel.push(this.levels[this.id_level][i].slice());
            }

            this.canvas.height=this.currentLevel.length*this.tile_size;
            this.canvas.width=this.currentLevel[0].length*this.tile_size;

            //Get the player position
            main_loop:
            for(var y=0,level=this.currentLevel,height=level.length; y<height; y++)
            {
                for(var x=0,width=level[0].length; x<width; x++)
                {
                    if(level[y][x]==2)
                    {
                        this.player.x=x;
                        this.player.y=y;
                        break main_loop;
                    }
                }
            }

            this.draw();
        },
        'draw': function()
        {
            //[...]
        }
    };

    //Let's run the game!
    Game.init();
};

As you can see, it’s a redundant code with the init() method. In consequences, we can update this last:

window.onload=function()
{
    Game=
    {
        'canvas': document.getElementById('game'),
        'context': document.getElementById('game').getContext('2d'),
        'tile_size': 32,
        'player':
        {
            'x': 0,
            'y': 0
        },
        'levels':
        [
            //[...]
        ],
        'currentLevel': [],
        'id_level': -1,
        'init': function()
        {
            var game=this;
            game.goToNextLevel();

            document.onkeydown=function(e)
            {
                //[...]
            };
        },
        'goToNextLevel': function()
        {
            //[...]
        },
        'draw': function()
        {
            //[...]
        }
    };

    //Let's run the game!
    Game.init();
};

In the onkeydown event, we can add a "restart" feature: if the player presses the spacebar we reload the current level.

window.onload=function()
{
    //[...]
    document.onkeydown=function(e)
    {
        var game=this;
        game.goToNextLevel();

        document.onkeydown=function(e)
        {
            if (!e) e=window.event; //IE fix
            var code=(e.which)?e.which:e.keyCode,
            x=game.player.x,
            y=game.player.y;

            switch(code)
            {
                //Left
                case 37:
                    x--;
                break;

                //Up
                case 38:
                    y--;
                break;

                //Right
                case 39:
                    x++;
                break;

                //Down
                case 40:
                    y++;
                break;

                //Spacebar
                case 32:
                    game.id_level--;
                    game.goToNextLevel();
                return false;
            }

            //[...]
        };
    };
    //[...]
};

When do we need to change the level? Only when the player has walked on each square. So, after a move, we have to check all the elements of the level: if there is no empty place, that means that the player successfully finished the level!

window.onload=function()
{
    //[...]
    document.onkeydown=function(e)
    {
        //[...]

        //If the wanted place is empty
        if(game.currentLevel[y][x]===0)
        {
            //We set the previous place to valid
            game.currentLevel[game.player.y][game.player.x]=3;
            game.player.x=x;
            game.player.y=y;
            game.currentLevel[y][x]=2;

            //Update the image
            game.draw();

            var end=true;

            main_loop:
            for(var y=0,level=game.currentLevel,height=level.length; y<height; y++)
            {
                for(var x=0,width=level[0].length; x<width; x++)
                {
                    if(level[y][x]===0)
                    {
                        end=false;
                        break main_loop;
                    }
                }
            }

            if(end)
            {
                game.goToNextLevel();
            }
        }
    };
    //[...]
};

Improve the graphics

Our game is fully functional, but it isn’t very beautiful…

Even if we just use rectangles, we can add some nice effects. The first one is a checkerboard.

So, go back to the draw() method. We can alternate the color of each square thanks to the modulo:

//[...]
'draw': function()
{
    var t=this.tile_size;
    for(var y=0,level=this.currentLevel,height=level.length; y<height; y++)
    {
        for(var x=0,width=level[0].length; x<width; x++)
        {
            switch(level[y][x])
            {
                //Wall
                case 1:
                    this.context.fillStyle='#000';
                break;

                //Player
                case 2:
                    this.context.fillStyle='#f00';
                break;

                //Valide square
                case 3:
                    if((x+y)%2==0)
                    {
                        this.context.fillStyle='#eeefa3';
                    }
                    else
                    {
                        this.context.fillStyle='#e3e063';
                    }
                break;

                //Empty place
                default:
                    if((x+y)%2==0)
                    {
                        this.context.fillStyle='#d4cff6';
                    }
                    else
                    {
                        this.context.fillStyle='#c8c2f4';
                    }
                break;
            }

            this.context.fillRect(x*t,y*t,t,t);
        }
    }
}
//[...]

It is better, isn’t it?

We can also add a perspective effect. Simply by increasing the height of the walls and by adding a lighter rectangle at their bottom, we can create a very nice "3/4 perspective":

images/perspective.png
Figure 3: We can easily add a perspective effect
//[...]
'draw': function()
{
    var t=this.tile_size;
    for(var y=0,level=this.currentLevel,height=level.length; y<height; y++)
    {
        for(var x=0,width=level[0].length; x<width; x++)
        {
            switch(level[y][x])
            {
                //Wall
                case 1:
                    this.context.fillStyle='#000';
                    this.context.fillRect(x*t,t*(y-1/4),t,t);
                    this.context.fillStyle='#7f7f7f';
                    this.context.fillRect(x*t,t*(y+1-1/4),t,t/4);
                continue;

                //Player
                case 2:
                    this.context.fillStyle='#931e1a';
                    this.context.fillRect(x*t,t*(y-1/4),t,t);
                    this.context.fillStyle='#d42525';
                    this.context.fillRect(x*t,t*(y+1-1/4),t,t/4);
                continue;

                //Valide square
                case 3:
                    if((x+y)%2==0)
                    {
                        this.context.fillStyle='#eeefa3';
                    }
                    else
                    {
                        this.context.fillStyle='#e3e063';
                    }
                break;

                //Empty place
                default:
                    if((x+y)%2==0)
                    {
                        this.context.fillStyle='#d4cff6';
                    }
                    else
                    {
                        this.context.fillStyle='#c8c2f4';
                    }
                break;
            }

            this.context.fillRect(x*t,y*t,t,t);
        }
    }
}
//[...]

Conclusion

This completes our first game with the powerful <canvas> tag. Of course, we’ve only scratched the surface.

Fortunately, <canvas> can draw figures other than just rectangles: lines, curves, ellipses…even external images!

This tag is probably one of the most powerful features offered by HTML5, and I hope this short introduction makes you want to learn more!

Download the resources


Comments

Be the first one to post a comment!