How To Make An HTML5 Canvas Game
HTML5 introduced the canvas element. The canvas element presents us with a blank canvas. We can use this canvas to draw and paint anything we want. JavaScript provides us with the tools we need to dynamically animate and draw to the canvas. It not only provides drawing and an animation system, it also handles user interactions. In this tutorial we will be making a basic HTML5 Canvas framework using pure JavaScript, that can be used to make a real game. A very simple game is created at the end of the tutorial to demonstrate the strengths of the HTML5 Canvas in combination with JavaScript.
Click here to go directly to the end of this article and play the game.
HTML5 Canvas Basic Game Framework
Let’s create a basic game framework around the canvas element. We need an HTML5 file and a JavaScript file. The HTML5 file should include a canvas element and a reference to the JavaScript file. The JavaScript file contains our code to draw stuff to the canvas element.
Here is the HTML5 file index.html:
1 2 3 4 5 6 7 8 9 10 11 | <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>HTML5 Canvas Basic Game Framework</title> <script type="text/javascript" src="game.js"></script> </head> <body> <canvas id="viewport" width="640" height="480"></canvas> </body> </html> |
As you can see, the JavaScript file game.js is included in the head of the html file. A canvas element is defined with the name “viewport” that has a width of 640 pixels and a height of 480 pixels. In our game.js we need to find the canvas element using its name, so we can draw on it. The framework we are creating should support a rendering loop and player interactions with the mouse. For the rendering loop, we will be using Window.requestAnimationFrame(). Mouse interactions are enabled by adding mouse event listeners.
Here is the JavaScript file game.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | // The function gets called when the window is fully loaded window.onload = function() { // Get the canvas and context var canvas = document.getElementById("viewport"); var context = canvas.getContext("2d"); // Timing and frames per second var lastframe = 0; var fpstime = 0; var framecount = 0; var fps = 0; // Initialize the game function init() { // Add mouse events canvas.addEventListener("mousemove", onMouseMove); canvas.addEventListener("mousedown", onMouseDown); canvas.addEventListener("mouseup", onMouseUp); canvas.addEventListener("mouseout", onMouseOut); // Enter main loop main(0); } // Main loop function main(tframe) { // Request animation frames window.requestAnimationFrame(main); // Update and render the game update(tframe); render(); } // Update the game state function update(tframe) { var dt = (tframe - lastframe) / 1000; lastframe = tframe; // Update the fps counter updateFps(dt); } function updateFps(dt) { if (fpstime > 0.25) { // Calculate fps fps = Math.round(framecount / fpstime); // Reset time and framecount fpstime = 0; framecount = 0; } // Increase time and framecount fpstime += dt; framecount++; } // Render the game function render() { // Draw the frame drawFrame(); } // Draw a frame with a border function drawFrame() { // Draw background and a border context.fillStyle = "#d0d0d0"; context.fillRect(0, 0, canvas.width, canvas.height); context.fillStyle = "#e8eaec"; context.fillRect(1, 1, canvas.width-2, canvas.height-2); // Draw header context.fillStyle = "#303030"; context.fillRect(0, 0, canvas.width, 65); // Draw title context.fillStyle = "#ffffff"; context.font = "24px Verdana"; context.fillText("HTML5 Canvas Basic Framework - Rembound.com", 10, 30); // Display fps context.fillStyle = "#ffffff"; context.font = "12px Verdana"; context.fillText("Fps: " + fps, 13, 50); } // Mouse event handlers function onMouseMove(e) {} function onMouseDown(e) {} function onMouseUp(e) {} function onMouseOut(e) {} // Get the mouse position function getMousePos(canvas, e) { var rect = canvas.getBoundingClientRect(); return { x: Math.round((e.clientX - rect.left)/(rect.right - rect.left)*canvas.width), y: Math.round((e.clientY - rect.top)/(rect.bottom - rect.top)*canvas.height) }; } // Call init to start the game init(); }; |
The code above draws a simple frame with a border, a title and a frames per second counter. This is what the code generates:
A Game With A Bouncing Square
Now that we have a framework, let’s create a simple game with it. We will be creating a game that has a bouncing square on the screen. When the player clicks on it, the score on the square increases, the direction in which the square is traveling changes and the speed of the square increases.
First, we define some objects and properties. The level defines the area in which the square can bounce around. The square itself has a position, dimensions and movement properties. Finally, there is a score.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // Level properties var level = { x: 1, y: 65, width: canvas.width - 2, height: canvas.height - 66 }; // Square var square = { x: 0, y: 0, width: 0, height: 0, xdir: 0, ydir: 0, speed: 0 } // Score var score = 0; |
We need to initialize the objects and properties in the init() function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // Initialize the game function init() { // Add mouse events canvas.addEventListener("mousemove", onMouseMove); canvas.addEventListener("mousedown", onMouseDown); canvas.addEventListener("mouseup", onMouseUp); canvas.addEventListener("mouseout", onMouseOut); // Initialize the square square.width = 100; square.height = 100; square.x = level.x + (level.width - square.width) / 2; square.y = level.y + (level.height - square.height) / 2; square.xdir = 1; square.ydir = 1; square.speed = 200; // Initialize the score score = 0; // Enter main loop main(0); } |
The objects need to be updated, so let’s modify the update() function. The square needs to be moved and collisions with the edges of the level should be detected and resolved.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // Update the game state function update(tframe) { var dt = (tframe - lastframe) / 1000; lastframe = tframe; // Update the fps counter updateFps(dt); // Move the square, time-based square.x += dt * square.speed * square.xdir; square.y += dt * square.speed * square.ydir; // Handle left and right collisions with the level if (square.x <= level.x) { // Left edge square.xdir = 1; square.x = level.x; } else if (square.x + square.width >= level.x + level.width) { // Right edge square.xdir = -1; square.x = level.x + level.width - square.width; } // Handle top and bottom collisions with the level if (square.y <= level.y) { // Top edge square.ydir = 1; square.y = level.y; } else if (square.y + square.height >= level.y + level.height) { // Bottom edge square.ydir = -1; square.y = level.y + level.height - square.height; } } |
We need to draw the square and the score. This needs to be done in the render() function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Render the game function render() { // Draw the frame drawFrame(); // Draw the square context.fillStyle = "#ff8080"; context.fillRect(square.x, square.y, square.width, square.height); // Draw score inside the square context.fillStyle = "#ffffff"; context.font = "38px Verdana"; var textdim = context.measureText(score); context.fillText(score, square.x+(square.width-textdim.width)/2, square.y+65); } |
The title of the demo should be modified in the drawFrame() function. The final step is to add mouse interactions. Let’s add our code to the onMouseDown() function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | function onMouseDown(e) { // Get the mouse position var pos = getMousePos(canvas, e); // Check if we clicked the square if (pos.x >= square.x && pos.x < square.x + square.width && pos.y >= square.y && pos.y < square.y + square.height) { // Increase the score score += 1; // Increase the speed of the square by 10 percent square.speed *= 1.1; // Give the square a random position square.x = Math.floor(Math.random()*(level.x+level.width-square.width)); square.y = Math.floor(Math.random()*(level.y+level.height-square.height)); // Give the square a random direction square.xdir = Math.floor(Math.random() * 2) * 2 - 1; square.ydir = Math.floor(Math.random() * 2) * 2 - 1; } } |
Bouncing Square Example
Here is the resulting game, made with the basic framework and some modifications. Click on the square to increase your score and to advance to the next square. The full source code is available on GitHub.