Open source html5 canvas browser game written in Javascript

In this post you will find all the source code which I have used to create one of my html5 game which is now live on chrome web store. I have decided to open up my source code because I want to help those new html5 game creators to get started with their own html5 game, if you like this game code then please help me to share it with your friend! Before we get started let us take a look at the game concept first.

The game concept:

There are two ships, one at the front and another at the back. The front ship will intercept the incoming rock first and then the second ship will intercept that same rock again to destroy it. Every time both ships have destroyed a rock the player will get 1 point, there are 10 levels in this game.

Complete 10 levels to win the game
Complete 10 levels to win the game

How to move the ship:

Use the left right arrow key on your keyboard to move the ship, when the front ship move to the right the back ship will move to the left which will indeed increase the difficulty of the game because you need to avoid the back ship from getting hit by the incoming rock which has not yet been intercepted by the front ship!

Things to avoid and to do:

If you intercept the rock with the back ship before that rock gets intercepted by the front ship then it is game over.

You will need to intercept the rock once with both ships in order to win a point.

What are the graphics you will need for this game:

Create a 550 x 550 menu
Create a 550 x 550 main menu

This page will show up each time you have completed a level.

thick mark
94 x 75 check mark

This is the check mark used to tick on the main menu page.

87x47 play button
87×47 play button

This play button on the menu page is use to start the next level

550x550 front page
550×550 front page

The front page of the game will show up each time you load a new game

150x81 play button
150×81 play button

This play button is used on the front page as well as on the winning and the game over page of the game

Winning page
550×550 winning page

This page will show up when you have won the game

gameover page
550×550 gameover page

This page will show up when it is gameover

1059x582
1059×582 main sprite sheet

The main sprite sheet consists of three item 1) 550 x 550 background image 2) 32 x 32 ship 3) 26 x 26 rock. You will need to level the top of the rock with the top of the ship and both of them shall sit on top of the background image.

I will leave the graphic creation part to you which you can easily create them in Inkscape, if you like you can download those game graphic and then open it up on your own computer.

Besides the above graphics, you will also need to prepare an icon (16×16.ico) to show up on the tab of your browser.

On top of that, you will also need to create these audio files:

1) The sound which will be played at the time the menu page is loading. (gave.mp3)
2) The sound which will be played at the time you score a point. (eat.mp3)
3) The game over’s sound! (hits.mp3)

The mp3 file works on almost all major web browsers so I will use it instead of using the other audio file format.

After you have all those materials ready let get into the coding part.

How big is our canvas:

There are many methods that you can use to show an image on the web browser with javascript but the most common one is through the html5 canvas. In this example we will use canvas to display our gaming character and the size of the canvas will be 550 x 550 pixel. In order to use the canvas all you need to do is to insert below tag within the html5 page where you want that canvas to appear.

I have assumed you have knowledge on how to create an html5 page like below. Also don’t forget to link the 16×16.ico file which you have created earlier within the link tag.

<!doctype html>
<!--
 * Copyright (c) 2016 gamingdirectional.com. All rights reserved. 
-->
<html>
	<head>
		<meta charset="UTF-8">
		<link rel="shortcut icon" href="./16x16png.ico"> //link to the icon file
		<title>Rock Sweeper</title> //title of the game
		<script src="jquery.js"></script> //include the jquery file
		<script src="collide.js"></script> //include the collision detection file
		<script src="main.js"></script> //include the main javascript file
		<link rel="stylesheet" type="text/css" href="main.css"> //include the css style sheet
	</head>
	<body>
		<audio id="menuSound" preload="auto">
				<source src="./eat.mp3"  type="audio/mpeg">
		</audio>
		<audio id="eatSound" preload="auto">
				<source src="./hits.mp3"  type="audio/mpeg">
		</audio>
		<audio id="overSound" preload="auto">
				<source src="./gave.mp3"  type="audio/mpeg">
		</audio>
		
 		<div class="canvas">
   			<canvas id="canvas" width="550" height="550">
    				Your browser doesn't support the HTML5 element canvas.
   			</canvas>
                        <div class="score">
   			</div>
 		</div>
 		
 		
 		
	</body>
</html>

Let see what we have on the above html5 file (index.html)

1) We will import a few files into this html5 file within the head tag section.
2) We will include the three sound files that we have created with the audio tag (I assume you are familiar with html5’s audio tag and how to use it, if you are not then here is a great tutorial online which will show you how :
http://webdesign.tutsplus.com/tutorials/create-a-customized-html5-audio-player–webdesign-7081
3) Within the canvas class division tag there are the canvas and the score tag. The score tag is use to display the score at the top corner of the canvas.

We need to specify the height and width within the canvas tag which is 550px in this example plus an id for that canvas so we can call it later on.

<canvas id="canvas" width="550" height="550">
   Your browser doesn't support the HTML5 element canvas.
</canvas>

I also have assumed you understand css style sheet so here is the style sheet (main.css) for the game page. Basically I have set the background to black so the gamer can concentrate on the game only. I also push the score tag to the top from it’s original position. Besides that, we also need to specify the width and height of this canvas class division tag to match the width and height of the canvas.

* {
 margin:0 0;
}
html {
 background-color:#000000;
}
.canvas {
 width:550px;
 height:550px;
 display:block;
 margin:1px auto auto auto;
 background-color:#000000;
} 
.score {
 position: relative;
 top:-548px;
 left:10px;
 font-size: 17px;
 line-height: 20px;
 font-weight: bold;
 color:white;
}

Below is the main.js file and the collide.js file. First of all I just want to emphasize to you that this is not a javascript tutorial for a beginner, if you want to learn the basic of javascript then here is the place which will get you started in javascript programming. http://www.w3schools.com/js/.

This post is for those of you who have already known javascript programming and would like to learn how to write a game program in javascript. I will leave you to enjoy reading the remaining javascript program, if you need more clarifications on a certain part of the program then do let me know and I will help you out!

This main.js file is where the entire game engine lies:

// Copyright (c) 2016 gamingdirectional.com. All rights reserved. 

jQuery(document).ready(function() {

	var theCanvas = document.getElementById("canvas");
	var context = theCanvas.getContext("2d");
        var canvasLeft = theCanvas.offsetLeft;
        var canvasTop = theCanvas.offsetTop;
        var play = false;

	var scoreboard = jQuery(".score");
	var sprites = [];
	
	var keyPressList = [];
	var winLabel;
	var loseLabel; 
	var loadLabel;
	var loadid;
	
	var LEVEL = 1;
	var WINGAMED = 10;

	//Key press and code
	var keypress = false;
	var KEYRIGHT = 39;
	var KEYLEFT = 37;
	var KEYSTRIKE = 32;

	//load splash
	var logoImg = new Image();
   	logoImg.src = "./title.png";
        var button = new Image();
        button.src = "./play_on.png";
   	
	var stoneswippers;
	var smallship;
	var cloud;
	
	var rocks = [];
	var rock;
	var rotation = Math.PI/180;
	var rock_vertical_distance = 300;
	var rock_spin_left = true;
 
	var LOADING = 0;
 	var BUILD_GAME_OBJ = 1;
 	var PLAYING = 2;
 	var OVER = 3;
 	var GAMED = 4;
 	var WIN = 5;
 	var gameState = LOADING;
 
	var assetsToLoad = [];
 	var assetsLoaded = 0;
	var score = 0;

	//The common sprite sheet
 
	var spriteObject = {
 	
		sourceX : 0,
      	
		sourceY : 0,
      	
		sourceWidth : 0,
      	
		sourceHeight : 0,
	
		vx : 0,
        
		vy : 0,
	
		X : 0,
        
		Y : 0,
        
		width : 0,
        
		height : 0,
        
		visible : true,
	
		rotate : false,
	
		rock_vertical_distance : 0,
	
		rock_radian : 0,
 
	};


	//Load the tilesheet image
 	var image = new Image();
 	image.addEventListener("load", loadHandler, false);
 	image.src = "./maintile.png";
 	assetsToLoad.push(image);
 	
 	//Load win screen
 	var winLabel = new Image();
 	winLabel.src = "./menu.png";
	
	//Load lose screen
 	var loseLabel = new Image();
 	loseLabel.src = "./gameover.png";
	
	//Load Gamed label
	var gamedLabel = new Image();
	gamedLabel.src = "./win.png";
	
	//Load play button
	var play_next = new Image();
	play_next.src = "./next.png";
	
	//Load thick
 	var thick = new Image();
 	thick.src = "./thick.png";
	
	var thick_location_array = [[345,35], [345,84], [345, 133], [345, 182], [345, 231], [345,280],[345,329],[345,378],[345,427],[345,476]];
 	
	//Load sound
	var eat = document.querySelector("#eatSound");
	var gave = document.querySelector("#overSound");
	var menu = document.querySelector("#menuSound");
	eat.addEventListener("canplaythrough", loadHandler, false);
 	eat.load();
 	assetsToLoad.push(eat);
 	gave.addEventListener("canplaythrough", loadHandler, false);
 	gave.load();
 	assetsToLoad.push(gave); 
	menu.addEventListener("canplaythrough", loadHandler, false);
 	menu.load();
 	assetsToLoad.push(menu); 
	

	function loadHandler() {
		assetsLoaded++;
		if(assetsLoaded === assetsToLoad.length) {
			//Remove the load handler
			image.removeEventListener("load", loadHandler, false);
			eat.removeEventListener("load", loadHandler, false);
			gave.removeEventListener("load", loadHandler, false);
			menu.removeEventListener("load", loadHandler, false);
			play = true;
		} 
	}

	//load the game splash screen
	loadid = requestAnimationFrame(gameStateTitle);
	
	// Add event listener for `click` events.
	theCanvas.addEventListener('click', function(event) {
    	  var x = event.pageX - canvasLeft,
          y = event.pageY - canvasTop;
          if(x > 190 && x <= 340 && y > 240 && y <= 321) {
			if(play == true) {
				cancelAnimationFrame(loadid);
  				gameState=BUILD_GAME_OBJ;
  				requestAnimationFrame(updateGameStatus);
				play = false;
			} else if(gameState == OVER) {
				sprites = [];
				rocks = [];
				score = 0;
				rock_vertical_distance = 300;
				rock_spin_left = true;
				keyPressList = [];
				gameState = BUILD_GAME_OBJ;
			} else if(gameState == GAMED) {
				sprites = [];
				rocks = [];
				score = 0;
				rock_vertical_distance = 300;
				rock_spin_left = true;
				keyPressList = [];
				gameState = BUILD_GAME_OBJ;
				LEVEL = 1;
			}
		} else if(x > 450 && x <= 537 && y > 483 && y <= 530) {
			if(gameState == WIN) {
				sprites = [];
				rocks = [];
				score = 0;
				rock_vertical_distance = 300;
				rock_spin_left = true;
				keyPressList = [];
				gameState = BUILD_GAME_OBJ;
				LEVEL += 1;
			}
		} 
        }, false);

 	function gameStateTitle(timestamp) {
 		context.clearRect(0, 0, theCanvas.width, theCanvas.height);
		context.drawImage(logoImg, 0, 0, logoImg.width, logoImg.height);
		if(play == true)
			context.drawImage(button, 190, 240, button.width, button.height);
		loadid = requestAnimationFrame(gameStateTitle);
	} 

	//run check game status 
	function updateGame() {
		updateKeycode();
		updateCloud();
		updateShip();
		updateStoneswippers();
		updateRock();
	}
	
	function updateGameStatus(timestamp) {
  		checkGameStatus();	
    	        requestAnimationFrame(updateGameStatus);
	}

	function updateKeycode(){
	
		if(keyPressList[KEYLEFT] == true && (gameState != WIN || gameState != OVER)) {
			stoneswippers.vx = -1;
			stoneswippers.X += stoneswippers.vx;	
			smallship.vx = 1;
			smallship.X += smallship.vx; 
		} else if(keyPressList[KEYLEFT] == false && (gameState != WIN || gameState != OVER)){
			stoneswippers.vx = 0;
			smallship.vx = 0;
		}
		
		if(keyPressList[KEYRIGHT] == true && (gameState != WIN || gameState != OVER)){
			stoneswippers.vx = 1;	
			stoneswippers.X += stoneswippers.vx;
			smallship.vx = -1;
			smallship.X += smallship.vx; 
		} else if(keyPressList[KEYRIGHT] == false && (gameState != WIN || gameState != OVER)){
			stoneswippers.vx = 0;
			smallship.vx = 0;
		}
	
	}

	function updateCloud() {
		if(cloud.Y >= theCanvas.height) {
			cloud.Y = -326;
		} else {
			cloud.vy = 1;
			cloud.Y += cloud.vy; 
		}
	}

	function updateShip() {
		if(smallship.X + smallship.width > theCanvas.width) {
			smallship.X = theCanvas.width - smallship.width;
		} else if(smallship.X < 0) {
			smallship.X = 0; 
		}
	}

	function updateStoneswippers() {
		if(stoneswippers.X + stoneswippers.width > theCanvas.width) {
			stoneswippers.X = theCanvas.width - stoneswippers.width;
		} else if(stoneswippers.X < 0) {
			stoneswippers.X = 0; 
		}
	}

	function updateRock() {
		for(var i = rocks.length-1; i >= 0; i--) {
			rock = rocks[i];
			rock.contact = true;
			if(rock.Y >= theCanvas.height) {
				rocks.splice(i, 1);
			} else {
				rock.vy = 1;
				rock.Y += rock.vy; 
			}
		}
		if(rocks.length < 500)
			createMoreRocks();
	}

	function createMoreRocks(){

	        rock_vertical_distance += 50;
		rock = Object.create(spriteObject);

		rock.vy = 0;
					
		rock.sourceX = 32;
					
		rock.sourceY = 0;
	
		rock.sourceWidth = 26;
	
		rock.sourceHeight = 26;
	
		rock.width = 26;
	
		rock.height = 26;
	
		rock.rotate = true;
		rock.hit = false;
		rock.rock_radian = 0;
		rock.size = Math.random();
		if(rock.size < 0.7);
			rock.size = 0.7;
		
		rock.X = (theCanvas.width) * Math.random();
		if(rock.X <= 0)
			rock.X = 30;
				
		rock.Y = -rock_vertical_distance;
		rock.rock_vertical_distance = -rock_vertical_distance;
			
		if(rock_spin_left) {
			rock.rock_spin_left = true;
			rock_spin_left = false;
		} else if(!rock_spin_left) {
			rock.rock_spin_left = false;
			rock_spin_left = true;
		}	
		rock.rock_radian = 0;
		rock.visible = true;
		sprites.push(rock);
		rocks.push(rock);
	}

	//Add keyboard listeners
 	window.addEventListener("keydown", function(event) {
 	
		switch(event.keyCode) {
	
			case KEYLEFT:
			keyPressList[event.keyCode] = true;
			break;
	
			case KEYRIGHT:
			keyPressList[event.keyCode] = true;
			break;
	
		}
  
	}, false);

  

	window.addEventListener("keyup", function(event) {
  	
		switch(event.keyCode) {
			case KEYLEFT:
			keyPressList[event.keyCode] = false;
			break;
		
			case KEYRIGHT:
			keyPressList[event.keyCode] = false;			
			break;
			
		}
 
	}, false);

	window.addEventListener("keydown", function(e) {
    	   // space and arrow keys prevent default
    	   if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
        	e.preventDefault();
    	   }
	}, false);

	function checkGameStatus() {
 	

		switch(gameState) {

			case BUILD_GAME_OBJ:
				buildGameObject();
				gameState = PLAYING;
				break;

			case PLAYING:
				updateGame();
				checkCollide();
				render();
				break;

			case OVER:
				updateKeycode();
				render();
				break;

			case WIN:
				updateKeycode();
				render();
				break;
			
			case GAMED:
				updateKeycode();
				render();
				break;
			
 	
		}
 	}

	function buildGameObject() {
	
		//create ground
		var ground = Object.create(spriteObject);			
		ground.sourceX = 0;
		ground.sourceY = 32;
		ground.X = 0;
		ground.Y = 0;
		ground.visible = true;
		ground.sourceWidth = 550;
		ground.sourceHeight = 550;
		ground.width = 550;
		ground.height = 550;
		sprites.push(ground);
	
		//create stoneswippers object
		stoneswippers = Object.create(spriteObject);
		stoneswippers.vx = 0;
		stoneswippers.sourceX = 0;
		stoneswippers.sourceY = 0;
		stoneswippers.sourceWidth = 32;
		stoneswippers.sourceHeight = 32;
		stoneswippers.width = 32;
		stoneswippers.height = 32;
		stoneswippers.X = theCanvas.width/2 - stoneswippers.width/2;
		stoneswippers.Y = theCanvas.height - 130;
		stoneswippers.visible = true;
		sprites.push(stoneswippers);
	
		//create the small ship
		smallship = Object.create(spriteObject);
		smallship.vx = 0;
		smallship.sourceX = 0;
		smallship.sourceY = 0;
		smallship.sourceWidth = 32;
		smallship.sourceHeight = 32;
		smallship.width = 32;
		smallship.height = 32;
		smallship.X = theCanvas.width/2 - smallship.width/2 - stoneswippers.width;
		smallship.Y = theCanvas.height - 70;
		smallship.visible = true;
		sprites.push(smallship);
	
		//create first 3 rock object
		var rock_count = 0;
		for(var i = 0; i < 3; i++) {
		
			rock = Object.create(spriteObject);
			rock.vy = 0;		
			rock.sourceX = 32;
			rock.sourceY = 0;
			rock.sourceWidth = 26;
			rock.sourceHeight = 26;
			rock.width = 26;
			rock.height = 26;
			rock.rotate = true;
			rock.hit = false;
			rock.size = Math.random();
			if(rock.size < 0.7)
				rock.size = 0.7;
			if(rock_count == 0){
				rock.X = theCanvas.width/2 - rock.width/2;
			} else if(rock_count == 1){
				rock.X = 50;
			} else {
				rock.X = 450;
			}
			rock.Y = -rock_vertical_distance;
			rock.rock_vertical_distance = -rock_vertical_distance;
			rock_count++;
		
			if(rock_spin_left) {
				rock.rock_spin_left = true;
				rock_spin_left = false;
			} else if(!rock_spin_left) {
				rock.rock_spin_left = false;
				rock_spin_left = true;
			}
					
			rock.visible = true;			
			sprites.push(rock);
			rocks.push(rock);
		}
	
		//create cloud
		cloud = Object.create(spriteObject);
		cloud.sourceX = 550;
		cloud.sourceY = 32;
		cloud.visible = true;
		cloud.vy = 0;
		cloud.sourceWidth = 509;
		cloud.sourceHeight = 326;
		cloud.width = 509;
		cloud.height = 326;
		cloud.X = theCanvas.width/2 - cloud.width/2;
		cloud.Y = -326;
		sprites.push(cloud);
		
	}

	function render() {
	
		context.clearRect(0, 0, theCanvas.width, theCanvas.height);
		
		if(gameState == GAMED) {
			context.drawImage (gamedLabel, 0, 0, gamedLabel.width, gamedLabel.height,0, 0,   gamedLabel.width, gamedLabel.height);
			context.drawImage(button, 190, 240, button.width, button.height);
		}
		
		else if(gameState == OVER) {
			context.drawImage (loseLabel, 0, 0, loseLabel.width, loseLabel.height,0, 0, loseLabel.width, loseLabel.height);
			context.drawImage(button, 190, 240, button.width, button.height);
		}
		
		else if(gameState == WIN) {
			context.drawImage (winLabel, 0, 0, winLabel.width, winLabel.height,0, 0, winLabel.width, winLabel.height);
			for(var i = 0; i < LEVEL; i++) {
				context.drawImage (thick, 0, 0, thick.width, thick.height, thick_location_array[i][0], thick_location_array[i][1], thick.width, thick.height);
			}
			context.drawImage (play_next, 0, 0, play_next.width, play_next.height,450, 483, play_next.width, play_next.height);
		} else {
	
			for(var i = 0; i < sprites.length; i++) {
		
				var sprite = sprites[i];
		
				if(sprite.visible != true) {
					continue;
				}
				if(sprite.rotate == true) {
					context.save(); 
			
					if(sprite.rock_spin_left) {
						sprite.rock_radian+=7;
					} else if(!sprite.rock_spin_left) {
						sprite.rock_radian+=-7;
					}
			
					sprite.width = sprite.sourceWidth*sprite.size;
					sprite.height = sprite.sourceHeight*sprite.size;
					context.translate(sprite.X + sprite.width/2, sprite.Y + sprite.height/2); 
			
					// rotate around this point
					context.rotate(sprite.rock_radian*rotation); 
 
					// then draw the image back and up
					context.drawImage(image, sprite.sourceX, sprite.sourceY, sprite.sourceWidth, sprite.sourceHeight,-sprite.width/2, -sprite.height/2, sprite.width, sprite.height); 
 
					// and restore the co-ordinate system to its default top left origin and no rotation
					context.restore();
			
				} else if(sprite.rotate == false) {
					context.drawImage (image, sprite.sourceX, sprite.sourceY, sprite.sourceWidth, sprite.sourceHeight,sprite.X, sprite.Y, sprite.width, sprite.height);
				}
			}
		}	
	}

	function checkCollide() {
 	
		for(var i = rocks.length-1; i >= 0; i--) {
			rock = rocks[i];
			if(checkShipRockCollide(stoneswippers, rock)) {
					//rocks.splice(i, 1);
					//rock.visible = false;
					rock.hit = true;
					if(rock.X > 270) {
						rock.X -= 1;
					} else {
						rock.X += 1;
					}
					//score++;
					//scoreboard.text("Stone Down : " + score);
			} 
		}	
 
		for(var i = rocks.length-1; i >= 0; i--) {
			rock = rocks[i];
			if(checkShipRockCollide(smallship, rock) && rock.hit) {
				eat.play();
				rocks.splice(i, 1);
				rock.visible = false;
				score++;
				scoreboard.text("Stone Down : " + score);
				if(score >= LEVEL * 10 && LEVEL == WINGAMED) {
					scoreboard.text('');
					gameState = GAMED;
					menu.play();
					break;
				} else if(score >= LEVEL * 10) {
					scoreboard.text('');
					gameState = WIN;
					menu.play();
					break;
				}
			} else if(checkShipRockCollide(smallship, rock)) {
				gameState = OVER;
				gave.play();
				scoreboard.text('');
				rock.visible = false;
				smallship.visible = false;
				rocks.splice(i, 1);
				menu.play();
				break;
			} 
		}	
		
	}
	
});

The collide.js file is use to detect the contact between the rock and the ship:

function checkShipRockCollide(ship, rock) {
	
	var vectorX = (ship.X + ship.width/2) - (rock.X + rock.width/2);
	var vectorY = (ship.Y + ship.height/2) - (rock.Y + rock.height/2);
	var magnitude = Math.sqrt(vectorX * vectorX + vectorY * vectorY);
	
	var totalRadius = rock.width/2 + ship.width/2;
	
	var hit = magnitude < totalRadius;
		
	//Check for a collision on the X axis
	if(hit) {
		return true;
	} else {
		return false;
	}    
}

Besides those two files above you will also need to download the latest jQuery file from this link : https://jquery.com/download/ and rename the file to jquery.js

Now let us play the game on this page http://gamingdirectional.com/application/RockSweeper/index.html and see those scripts in action.