How To Create A Game With JavaScript and HTML5 Canvas
In this step by step guide, I’ll show you how to create a game with pure JavaScript and HTML5 Canvas. I’ll give code examples along the way. The complete project files are in GitHub. You can see an example of the game we will be creating here. The object of the game is for the player to bring the gem back from the other side of the road. The user has to navigate the player through the bugs without getting hit. If you’re just starting out with object-oriented programming, this is a good introduction. The inheritance and abstraction are very simple. If you don’t have any experience at all with object-oriented programming, Udacity.com has a great introductory course that goes over the basics of Java and OOP. You may be familiar with OOP but not used to seeing it implemented in JavaScript. If so, this will be a good exercise for you too. You can download the project files from my GitHub repository here.
Step 1: Create HTML page
First, create the HTML page. The HTML is very simple, and it gives you an anchor point for the Canvas and the rest of the code. It will contain three JavaScript files and a stylesheet that we will create in the next steps:
Step 2: Define entities in app.js
Now we need to define our player, enemies, and prize. These are the entities that will be moving around on the screen. They’ll all go in app.js as their respective function objects. Inside these objects, we will add properties that define the characteristics of the entities. We need to define what they look like and how they move.
Enemies
Next, create a file called app.js. First, we’ll create the Enemy. In this game, the enemy is a bug, and there’s a lot of them. There are three possible “lanes” of stone they can start in. The hash() function returns a randomly selected y_position which corresponds to a randomly selected “lane.” Their speed is also randomly generated.
var Enemy = function() < // The image/sprite for our enemies if (start === true) < this.sprite = 'images/enemy-bug.png'; >this.x = (-300 + Math.random() * 101); var y_position; hash(); this.y = y_position; //generate random number which hashes to one of three //possible enemy start positions //top = 50 //middle = 140 //bottom = 230 function hash() < number = Math.floor(Math.random() * 101) * 4; if (number < 100) < y_position = 50; >else if (number > 100 && number < 300) < y_position = 140; >else if (number > 200) < y_position = 230; >return y_position; > this.speed = (Math.floor((Math.random() + 1) * 351)); >;
Then we’ll add the Enemy’s update and render functions. These will be called by updateEntities() in the game engine we will create later. We put the sprite onto the Canvas with ctx.drawImage().
// Update the enemy's position, required method for game // Parameter: dt, a time delta between ticks Enemy.prototype.update = function(dt) < this.x = this.x + this.speed * dt; >; // Draw the enemy on the screen, required method for game Enemy.prototype.render = function() < if (this.sprite) < ctx.drawImage(Resources.get(this.sprite), this.x, this.y); >>;
Player
Next, we’ll create the Player and its update and render functions. The Player is the entity that the user controls:
var Player = function() < this.sprite = 'images/char-boy.png'; this.x = 900; this.y = 400; >; Player.prototype.update = function(dt) < // player update code goes here >; Player.prototype.render = function() < //draw player ctx.drawImage(Resources.get(this.sprite), this.x, this.y); >;
The user will control the Player with the arrow keys. Later we will receive the input and pass it to this handleInput function. Now we need to define what the user input does to our Player:
Player.prototype.handleInput = function(keyCode) < //dictate player movement based on keyboard input if (keyCode == 'left' && this.x >0) < this.x = this.x - 100; >else if (keyCode == 'up' && this.y > 30) < this.y = this.y - 83; >else if (keyCode == 'right' && this.x < 556) < this.x = this.x + 100; >else if (keyCode == 'down' && this.y < 355) < this.y = this.y + 83; >else if (keyCode == 'enter' && start === false) < this.x = this.x - 700; start = true; >>;
Next, we add an event listener to get the user input from the arrow keys and pass it to handleInput():
// This listens for key presses and sends the keys to your // Player.handleInput() method. document.addEventListener('keyup', function(e) < var allowedKeys = < 37: 'left', 38: 'up', 39: 'right', 40: 'down', 13: 'enter' >; player.handleInput(allowedKeys[e.keyCode]); >);
Prize
Next, we make the gem or Prize object along with it’s update and render functions. We also check if the Player has successfully brought the Prize back from the other side of the road. If so, we put the win message onto the Canvas.
var Prize = function () < this.sprite = 'images/Gem Blue.png'; this.x = 530; this.y = 30; >; Prize.prototype.update = function() < //check if player has gotton to the gem if (follow === true) < //gem follows player player.follow(); >>; Prize.prototype.render = function() < ctx.drawImage(Resources.get(this.sprite), this.x, this.y, 50.5, 85.5); //check for player win if (this.y >300) < follow = false; //drop gem if (game_over === false) < this.x = this.x + 30; this.y = this.y +143; >ctx.textAlign = "center"; ctx.textBaseline = "Middle"; ctx.font = 'bold 50px Trebuchet MS, sans-serif'; ctx.fillStyle = '#FFD700'; ctx.fillText('You Win!', 353.5, 303); ctx.font = 'bold 50px Trebuchet MS, sans-serif'; ctx.strokeStyle = '#000000'; ctx.lineWidth = 3; ctx.strokeText('You Win!', 353.5, 303); game_over = true; > >;
Now we need to define a function as a property of the Player that we can use to stick the gem to the player once he gets to it. This will make it look like he’s carrying it.
Player.prototype.follow = function() < prize.x = this.x; prize.y = this.y; >;
// create global variables var start = false; var follow = false; var game_over = false;
// Now instantiate your objects. var player = new Player(); var prize = new Prize();
Since we have multiple enemies, we will create a function generate() to instantiate them and add them to an enemies array:
var allEnemies = []; //generate enemies generate = function() < for (var i = 0; i < 2; i++) < e = new Enemy; e.push; allEnemies.push(e); >>;
Next, we need to add two utility functions to check if our player collided with an Enemy or made it to the Prize:
// figure out which block entity is in function which_block(x, y) < var row; var col; if (x < 0) < row = -1; col = -1; >else < for (var i=0; irow = i; > for (i=0; i col = i; > > return [row, col]; > function checkCollisions() < //Check if player occupies the same tile as an enemy for (var i = 0; i < allEnemies.length; i++) < if (String(which_block(allEnemies[i].x, allEnemies[i].y)) === String(which_block(player.x, player.y))) < //reset player and enemies and drop gem if (follow === true) < follow = false; prize.x = player.x; prize.y = player.y; >allEnemies = []; generate(); player.x = 200; player.y = 400; > > //check if player occupies same block as gem if (String(which_block(player.x, player.y)) == String(which_block(prize.x, prize.y - 30))) < //gem follow player if (game_over === false) < follow = true; >> >
//reload page after two seconds function refresh() < setTimeout(function() , 2000); >
// draw instructions for game function title() < ctx.textAlign = "center"; ctx.textBaseline = "Middle"; ctx.lineWidth = 2; ctx.font = 'bold 30px Trebuchet MS, sans-serif'; ctx.fillStyle = '#FFD700'; ctx.fillText('Use the arrow keys to bring the gem back', 353.5, 166.65); ctx.fillText('from the other side of the road', 353.5, 210.65); ctx.font = 'bold 30px Trebuchet MS, sans-serif'; ctx.strokeStyle = '#000000'; ctx.strokeText('Use the arrow keys to bring the gem back', 353.5, 166.65); ctx.strokeText('from the other side of the road', 353.5, 210.65); ctx.lineWidth = 3; ctx.font = 'bold 50px Trebuchet MS, sans-serif'; ctx.fillText('Press ENTER to start', 707/2, 333.3); ctx.font = 'bold 50px Trebuchet MS, sans-serif'; ctx.strokeText('Press ENTER to start', 707/2, 333.3); >
Step 3: Create image loading utility
Create an image utility file called resource.js. I would recommend copying this file directly from the repository. It’s not very complicated, and there’s nothing too exciting going on here. It abstracts some image loading and checking from of the rest of the app. The important part is the code at the bottom that makes the functions globally available through the Resources object:
Step 4: Create the game engine
Once the game engine is initialized, it will do the animation via the main() function. Create a file called engine.js and inside it create the Engine object. This object is an anonymous function. It calls itself and passes in this which is the global context.
var Engine = (function(global) < >)(this);
var doc = global.document, win = global.window, canvas = doc.createElement('canvas'), ctx = canvas.getContext('2d'), lastTime; canvas.width = 707; canvas.height = 606; doc.body.appendChild(canvas);
Now we add main() as a property of the Engine function. This is the heart of the engine. At the end of the main() function, we use requestAnimationFrame(callback) and pass in main() as the callback. When the browser is ready, it will execute the callback passed into requestAnimationFrame(). This is what keeps the game keeps going frame after frame.
function main() < /* Get our time delta information which is required if your game * requires smooth animation. Because everyone's computer processes * instructions at different speeds we need a constant value that * would be the same for everyone (regardless of how fast their * computer is). */ var now = Date.now(), dt = (now - lastTime) / 1000.0; /* Call our update/render functions, pass along the time delta to * our update function since it may be used for smooth animation. */ update(dt); render(); /* Set our lastTime variable which is used to determine the time delta * for the next time this function is called. */ lastTime = now; //call refesh if player wins if (game_over === true) < refresh(); >/* Use the browser's requestAnimationFrame function to call this * function again as soon as the browser is able to draw another frame. */ win.requestAnimationFrame(main); >
/* This function is called by main (our game loop) and itself calls all * of the functions which may need to update entity's data. */ function update(dt) < updateEntities(dt); if (start === true) < if (allEnemies[allEnemies.length - 1].x >100) < generate(); >> checkCollisions(); prize.render(); > function updateEntities(dt) < prize.update(); allEnemies.forEach(function(enemy) < enemy.update(dt); >); player.update(); >
- Render() and renderEntities() – Call all functions to render entities on the Canvas as well as render the grid of images which forms the “ground” of the game.
function render() < //clear top and bottom of parts of the player that hang off the "grid" ctx.clearRect(0, 0, 707, 100); ctx.clearRect(0, 588, 707, 50); /* This array holds the relative URL to the image used * for that particular row of the game level. */ var rowImages = [ 'images/grass-block.png', // Top row is grass 'images/stone-block.png', // Row 1 of 3 of stone 'images/stone-block.png', // Row 2 of 3 of stone 'images/stone-block.png', // Row 3 of 3 of stone 'images/grass-block.png', // Row 1 of 2 of grass 'images/grass-block.png' // Row 2 of 2 of grass ], numRows = 6, numCols = 7, row, col; /* Loop through the number of rows and columns we've defined above * and, using the rowImages array, draw the correct image for that * portion of the "grid" */ var grid = []; for (row = 0; row < numRows; row++) < for (col = 0; col < numCols; col++) < //create a grid to reference with which_block() in app.js grid.push([row, col]); /* The drawImage function of the Canvas' context element * requires 3 parameters: the image to draw, the x coordinate * to start drawing and the y coordinate to start drawing. */ ctx.drawImage(Resources.get(rowImages[row]), col * 101, row * 83); >> renderEntities(); if (start === false) < title(); >> /* This render function calls renderEntities() on each game * tick. Its then calls the render functions defined * on the enemy and player entities within app.js */ function renderEntities() < /* Loop through all of the objects within the allEnemies array and call * the render function you have defined. */ allEnemies.forEach(function(enemy) < enemy.render(); >); if (start === true) < prize.render(); player.render(); >>
/* This function does some initial setup that should only occur once, * particularly setting the lastTime variable that is required for the * game loop. */ function init()
Next, we will load the resources. We will put the init() function in as the callback for when all the resources are loaded:
/* Go ahead and load all of the images we know we're going to need to * draw our game level. Then set init as the callback method, so that when * all of these images are properly loaded our game will start. */ Resources.load([ 'images/stone-block.png', 'images/water-block.png', 'images/grass-block.png', 'images/enemy-bug.png', 'images/char-boy.png', 'images/Gem Blue.png' ]); Resources.onReady(init);
Step 5: Create stylesheet
Lastly, create style.css. You can add whatever styles you want here to position your game how you want or to add a cool background. I’m just a little positioning so I’ve only added body
2 responses to “ How To Create A Game With JavaScript and HTML5 Canvas ”
I’m new to coding and javascript and this project does not work for me. After painstakingly following the instructions above I tried to run the game, nothing happens. Thought I made a mistake somewhere so I copied the github source files at the top of this page into my project thinking “well it will surely work now!” but I was wrong, nothing, blank page.. A game that doesn’t run when you finish the project isn’t very interesting to me. Not sure if you left out something that makes the app actually work but this feels like it was a huge waste of a few hours.