Using HTML5 canvas and JavaScript to remove obstacles in a game and track scoring
This topic includes a stand-alone annotated code sample that shows how to use HTML5 Canvas and JavaScript to remove obstacles from your vehicle and track scoring. The sample code builds on the code in the Using HTML5 canvas and JavaScript to detect collisions between vehicles in a game topic.
- Canvas code sample
- Canvas code sample discussion
- Body code
- Script code
- Visual debugging
- How to install the game
- How to play the game
- Starting to play
- Scoring
- Ending the game
- Related topics
This sample demonstrates how canvas creates these fast animation actions by changing individual pixels directly on the screen. This technique can be used effectively to move or modify objects during game play. This code sample demonstrates how to modify pixels to launch green bombs from the spaceship to destroy red asteroids. When the bombs hit the asteroids, the asteroids break up into pieces, and the spaceship can safely fly through to home base.
This code sample covers the following tasks that demonstrate the basic principles of using Canvas to remove obstacles and keep score:
- Capturing keystrokes to launch bombs
- Modifying pixels to move objects and simulate explosions
- Creating a second canvas screen to display the score
- Tracking actions to calculate scores
At the end of the code sample is discussion material that explains more about the design and structure of these tasks and how they work. Also included is: information on how to debug your code, steps for installing the finished game program, and instructions on how to play this fast action retro style Canvas outer space game.
Canvas code sample
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
// Global variables
var shipX = 0; // X position of ship
var shipY = 0; // Y position of ship
var canvas; // canvas
var ctx; // context
var back = new Image(); // storage for new background piece
var oldBack = new Image(); // storage for old background piece
var ship = new Image(); // ship
var bomb = new Image(); // neutralizer ray field
var shipX = 0; // current ship position X
var shipY = 0; // current ship position Y
var oldShipX = 0; // old ship position Y
var oldShipY = 0; // old ship position Y
var direction = "R"; // direction of ship movement
var score = 0; // score
// This function is called on page load.
function canvasSpaceGame() {
// Get the main canvas element.
canvas = document.getElementById("myCanvas");
// Get the score canvas element.
canvas2 = document.getElementById("myScore");
// Initialize the score element.
if (canvas2.getContext)
// If you have it, create score element.
{
// Specify score 2d canvas type.
ctx2 = canvas2.getContext("2d");
}
// Initialize main element.
if (canvas.getContext)
// If you have it, create a canvas user interface element.
{
// Specify main 2d canvas type.
ctx = canvas.getContext("2d");
// Paint it black.
ctx.fillStyle = "black";
ctx.rect(0, 0, 300, 300);
ctx.fill();
// Save the initial background.
back = ctx.getImageData(0, 0, 30, 30);
// Paint the starfield.
stars();
// Draw space ship.
makeShip();
// Draw asteroids.
drawAsteroids();
}
// Play the game until the until the game is over.
gameLoop = setInterval(doGameLoop, 16);
// Add keyboard listener.
window.addEventListener('keydown', whatKey, true);
}
// Paint a random star field.
function stars() {
// Draw 50 stars.
for (i = 0; i <= 50; i++) {
// Get random positions for stars.
var x = Math.floor(Math.random() * 299);
var y = Math.floor(Math.random() * 299);
// Make the stars white
ctx.fillStyle = "#EEEEEE";
// Paint the star but not if too close to ship.
if (x > 40 && y > 40) {
// Draw an individual star.
ctx.beginPath();
ctx.arc(x, y, 3, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
} else--i;
}
// Save black background.
oldBack = ctx.getImageData(0, 0, 30, 30);
}
function makeShip() {
// Draw saucer bottom.
ctx.beginPath();
ctx.moveTo(28.4, 16.9);
ctx.bezierCurveTo(28.4, 19.7, 22.9, 22.0, 16.0, 22.0);
ctx.bezierCurveTo(9.1, 22.0, 3.6, 19.7, 3.6, 16.9);
ctx.bezierCurveTo(3.6, 14.1, 9.1, 11.8, 16.0, 11.8);
ctx.bezierCurveTo(22.9, 11.8, 28.4, 14.1, 28.4, 16.9);
ctx.closePath();
ctx.fillStyle = "rgb(222, 103, 0)";
ctx.fill();
// Draw saucer top.
ctx.beginPath();
ctx.moveTo(22.3, 12.0);
ctx.bezierCurveTo(22.3, 13.3, 19.4, 14.3, 15.9, 14.3);
ctx.bezierCurveTo(12.4, 14.3, 9.6, 13.3, 9.6, 12.0);
ctx.bezierCurveTo(9.6, 10.8, 12.4, 9.7, 15.9, 9.7);
ctx.bezierCurveTo(19.4, 9.7, 22.3, 10.8, 22.3, 12.0);
ctx.closePath();
ctx.fillStyle = "rgb(51, 190, 0)";
ctx.fill();
// Save ship data.
ship = ctx.getImageData(0, 0, 30, 30);
// Erase it for now.
ctx.putImageData(oldBack, 0, 0);
}
function doGameLoop() {
// Put old background down to erase shipe.
ctx.putImageData(oldBack, oldShipX, oldShipY);
// Put ship in new position.
ctx.putImageData(ship, shipX, shipY);
}
// Get key press.
function whatKey(evt) {
// Flag to put variables back if we hit an edge of the board.
var flag = 0;
// Get where the ship was before key process.
oldShipX = shipX;
oldShipY = shipY;
oldBack = back;
switch (evt.keyCode) {
// Left arrow.
case 37:
shipX = shipX - 30;
if (shipX < 0) {
// If at edge, reset ship position and set flag.
shipX = 0;
flag = 1;
}
direction = "L";
break;
// Right arrow.
case 39:
shipX = shipX + 30;
if (shipX > 270) {
// If at edge, reset ship position and set flag.
shipX = 270;
flag = 1;
}
direction = "R";
break;
// Down arrow
case 40:
shipY = shipY + 30;
if (shipY > 270) {
// If at edge, reset ship position and set flag.
shipY = 270;
flag = 1;
}
direction = "D";
break;
// Up arrow
case 38:
shipY = shipY - 30;
if (shipY < 0) {
// If at edge, reset ship position and set flag.
shipY = 0;
flag = 1;
}
direction = "U";
break;
// A key for drawing neutralizer field
case 65:
// Using this increases your score.
score = score + 20;
// The ship isn't moving.
flag = 1;
// Draw the neutralizing ray which will let you pass.
neutralize();
break;
// If any other keys were presssed
default:
flag = 1; // Don't move the ship.
alert("Please only use the arrow keys.");
}
// If flag is set, the ship did not move.
// Put everything back the way it was.
// Reduce score since the ship did not move.
if (flag) {
shipX = oldShipX;
shipY = oldShipY;
back = oldBack;
score = score - 1;
} else {
// Otherwise, get background where the ship will go
// So you can redraw background when the ship
// moves again.
back = ctx.getImageData(shipX, shipY, 30, 30);
}
// Increase score.
score = score + 1;
// Draw score on scoreboard.
ctx2.clearRect(0, 0, 300, 300);
ctx2.font = "20 point Ariel";
ctx2.fillText("Score", 20, 15);
ctx2.fillText(score, 100, 15);
// Did we collide?
collideTest();
}
function collideTest() {
// Collision detection. Get a clip from the screen.
// See what the ship would move over.
var clipWidth = 20;
var clipDepth = 20;
var clipLength = clipWidth * clipDepth;
var clipOffset = 5;
var whatColor = ctx.getImageData(shipX + clipOffset, shipY + clipOffset, clipWidth, clipDepth);
// Loop through the clip and see if you find red or blue.
for (var i = 0; i < clipLength * 4; i += 4) {
if (whatColor.data[i] == 255) {
direction = "P";
break;
}
// Second element is green but we don't care.
if (whatColor.data[i + 2] == 255) {
direction = "B";
break;
}
// Fourth element is alpha and we don't care.
}
// Did we hit something?
if (direction == "P") bang();
if (direction == "B") youWin();
}
function bang() {
// You lose.
alert("Game over! You hit an asteroid.");
// Stop game.
clearTimeout(gameLoop);
window.removeEventListener('keydown', whatKey, true);
}
function youWin() {
// You win.
alert("Game over! You made it to home base.");
// Stop game.
clearTimeout(gameLoop);
window.removeEventListener('keydown', whatKey, true);
}
function drawAsteroids() {
// Draw asteroids.
for (i = 0; i <= 20; i++) {
// Get random positions for asteroids.
var a = Math.floor(Math.random() * 299);
var b = Math.floor(Math.random() * 299);
// Make the asteroids red
ctx.fillStyle = "#FF0000";
// Keep the asteroids far enough away from
// the beginning or end.
// Draw an individual asteroid.
ctx.beginPath();
ctx.arc(a, b, 10, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
}
// Draw green for neutralizer.
ctx.fillStyle = "#00FF00";
ctx.beginPath();
ctx.rect(270, 270, 30, 30);
ctx.closePath();
ctx.fill();
// Save it for later.
bomb = ctx.getImageData(270, 270, 30, 30);
// Draw blue base.
ctx.fillStyle = "#0000FF";
ctx.beginPath();
ctx.rect(270, 270, 30, 30);
ctx.closePath();
ctx.fill();
// Make some room at beginning.
ctx.putImageData(back, 0, 30);
ctx.putImageData(back, 30, 0);
ctx.putImageData(back, 30, 30);
// Make some room at end.
ctx.putImageData(back, 240, 240);
ctx.putImageData(back, 270, 240);
ctx.putImageData(back, 240, 270);
}
// Create neutralizer field.
function neutralize() {
// Which way was the ship going?
// Put down a neuralizer field that way.
switch (direction) {
case "D":
ctx.putImageData(bomb, shipX, shipY + 30);
break;
case "U":
ctx.putImageData(bomb, shipX, shipY - 30);
break;
case "L":
ctx.putImageData(bomb, shipX - 30, shipY);
break;
case "R":
ctx.putImageData(bomb, shipX + 30, shipY);
break;
default:
}
}
</script>
</head>
<body onload="canvasSpaceGame()">
<h1>
Canvas Space Game
</h1>
<canvas id="myCanvas" width="300" height="300">
</canvas>
<canvas id="myScore" width="300" height="300">
</canvas>
</body>
</html>
Canvas code sample discussion
Note The code in this sample builds upon the code in the previous sample.
This section explains the design and structure of this code sample. It will tell you about the different parts and how they fit together. The Canvas sample uses a standard HTML5 header, <!doctype html>, so that browsers can distinguish it as part of the HTML5 specification.
This code is divided into two major parts:
- Body Code
- Script Code
Body code
Most of the body code is the same as the body code in the Detecting Collisions topic. However, a second Canvas tag is added that will be used to create a second canvas for displaying the score.
Script code
The script code starts out with the same script code as in the Detecting Collisions topic. However, it modifies almost every block and adds several new ones. The script code of this sample consists of the following:
- Global variables
- canvasSpaceGame function
- makeShip function
- whatKey function
- collideTest function
- drawAsteroids function
- bang function
- youWIn function
- neutralize function
Global variables are called when the page loads. The canvasSpaceGame function is called from the body tag onload attribute. The rest of the functions are called from the canvasSpaceGame function.
Global variables
Two new global variables are added:
- Direction – this keeps track of what direction the ship is moving. When the ship collides with an object, this variable is also used to determine how the game ends.
- Score – this keeps track of the score. Each time the ship moves, the score is incremented by one. If you use a bomb to destroy an asteroid, 20 points are added to your score. The goal of the game is to fly through the asteroid field to get to the home base with the lowest possible score. This can be accomplished by making careful choices of whether to move away around the asteroids or launch bombs at them.
canvasSpaceGame function
This function is nearly the same as the canvasSpaceGame function in the third task of this scenario, "Detecting Collisions." A second canvas is added to this function so that you can display the score without modifying the first canvas. Also, a call to the drawAsteroids function is made to draw the asteroids on the screen and create the home base.
whatKey function
This function starts out with the same code as the whatKey function in the in the third task of this scenario, "Detecting Collisions," but several new things are added to facilitate scoring and launching bombs.
If the up, down, left, or right keys are pressed to move the spaceship, the direction of the ship is saved so you can use it to determine where the bomb will land. If the “A” key is pressed, this will trigger the bomb and add 20 points to the score. The flag will be set to indicate that the ship did not move and the neutralize function will be called to draw the bomb on the screen.
The whatKey function also handles scoring. If the ship moves, one point is added. The score is displayed on the second canvas screen by first clearing the screen, setting the font, drawing the word “Score,” and then displaying a numerical score. If the ship did not move, no points are added to the score.
collideTest function
This function has been modified from the collideTest function in the third task of this scenario, "Detecting Collisions" to determine what to do if a collision takes place. If the ship collides with an asteroid, the direction variable is changed to “P” and will be processed later. This different use of the direction variable won’t be a problem because the ship no longer can move and therefore does not have a direction.
If the ship collides with the home base, the direction variable changes to “B” and will be processed later. After the snapshot is processed, a test is made to see if there was a collision. The bang function is called if the ship hit an asteroid. The youWin function is called if the ship docked at home base.
bang function
This function is called when the ship hits an asteroid. An alert is displayed to indicate that the ship was destroyed and the game is over. You must do two additional actions to stop the code from running:
- Stop the game loop by calling clearTimeout with the original gameLoop variable you created when you set up the game loop. Otherwise the game loop will continue to run.
- You must also stop the keyboard event listener by calling removeEventListener. If you do not do this, keys will continue to be processed and actions will continue to take place.
youWin function
This function is called when the ship docks at home base. An alert tells the player that they have won the game. You must also stop the game loop and keyboard event listener in the same way that the bang function does.
drawAsteroids function
This is slightly modified from the drawAsteroid function in the third task of this scenario, "Detecting Collisions." One change is to draw a green rectangle (representing the bomb debris) before you draw the home base and save it for later use. The other change is to draw black images around the initial ship location and home base, using the back image variable. This will make the game a little easier by not having too many asteroids crowding the starting position or home base.
neutralize function
This new function simply takes the direction value and uses a switch statement to decide where to draw the bomb image. The location of the bomb is determined by the direction variable. Because the green bomb image does not contain any red or blue, your ship will be able to move through the bomb debris without a collision. This shows how easy it is to use Canvas to modify your game behavior by manipulating the pixel colors of the objects on the screen.
Visual debugging
When using Canvas for collision detection, it can be helpful to see the snapshot of what you are colliding with. You cannot see what you are colliding with because the spaceship is covering it up. You can see what the collision detection snapshot looks like by adding this line to the collideTest function right after the whatColor image is calculated.
ctx2.putImageData(whatColor,50,100);
You can display the snapshot image on a separate canvas (ctx2) so that it does not interfere with the main canvas where the game is taking place. This is a valuable technique when working with Canvas games so that you can see what images have been created or are being used. In addition, you could also use a second canvas screen to write numerical variable data so you can easily track intermediate values for debugging.
How to install the game
This game runs in Windows Internet Explorer 9. It will not work in earlier versions of Windows Internet Explorer, but might run in other browsers that support HTML5 Canvas.
Complete the following steps to install the game:
- Copy the game code to a text file and save the file with an .html file extension.
- Load and run the file in Internet Explorer 9.
- When you run the file, make sure you are running in IE9 Browser Mode and IE9 standards Document Mode.
- You may be asked if you want to allow blocked content. Click the Allow blocked content button to finish loading the game.
How to play the game
When you have loaded the game into the browser, you will know it is running when you see a black background that has white stars, several red asteroids, a spaceship in the upper left corner, and a blue home base square on the lower-right corner. The object of the game is to fly your spaceship safely to home base through the starfield of exploding asteroids. If you hit an asteroid, your ship will be destroyed. You must avoid the asteroids or blow them up with green bombs before you crash into them.
Starting to play
To begin playing the game, press one of the four arrow keys on your keyboard. The ship moves in that direction. If you drive your ship into a red asteroid, the ship is destroyed and the game will be over and you will lose. If an asteroid is in your way and you want to remove it, press the "A" key to launch a green bomb. The bomb will blow up the asteroid and you can fly through the green dust to safety. The direction the bomb launches is based on your last move; for example, if you move left, the bomb will launch one space to the left of your ship. If you try to move your ship off the screen, the game won't let you.
Scoring
During game play, your score is continuously displayed in a separate canvas to the right of the game canvas. Scoring is based on how many times you move your ship and how many bombs you launch. Each move of the ship adds one point and each bomb launch adds twenty points. You want to get the lowest possible score before you reach home base. Use bombs sparingly and choose your path wisely.
Ending the game
When you finish the game, you can shut it down it by simply closing the browser. If you want to play again, refresh your browser to start a new game. Each time you play, the asteroids and stars will appear in new random positions that will provide you with a fresh challenge.