Safari HTML5 Canvas Guide

Creating Games

Creating games involves all the subjects covered in this document up to this point: drawing shapes, images, and text; animation; adding touch and mouse controls; and triggering sounds. In addition, for many games you need to detect collisions, which may involve reading the canvas pixels.

Collision Detection

There are several ways to detect collisions between objects in a game.

You can compare the x and y coordinates of the various elements—this is best for detecting collisions with walls or between rectangular objects.

If at least one of the objects is a shape, you can use the isPointInPath(x,y) method to see if a given point is inside the path—this is best for detecting collisions between missiles and shapes.

You can examine the canvas bitmap, to see if a target color is anywhere inside a given area—this is best when the target is a unique color.

Space Arcade Game

The example in Listing 16-1 is the skeleton of an arcade game, as illustrated in Figure 16-1. It has a scrolling background, a ship that responds to touch or mouse control, missiles that fire when the mouse is clicked or the screen is tapped, and targets that follow a path. The game detects collisions between missiles and targets, and maintains a score that advances when a target is hit.

All the elements of a side-scrolling arcade game are present. With a little work, you can modify the skeleton to support any number of arcade shoot-em-ups.

Figure 16-1 Space arcade game

Listing 16-1 Space arcade game

<html>

<head>

<title>Space Arcade Game</title>

<meta name="viewport" content="width=600" />

<script type="text/javascript">

var can, ctx, back, xBack = 0, xIncr = 1,

imgWidth = 1498, shipX = 10, shipY = 140,

target = [1, 1, 1, 1, 1],

targetX = [], targetY = [], targetSpeed = 1.5,

phase = 0.1, targets = 4, bulletX, bulletY, score = 0;

function init() {

back = document.getElementById("back");

can = document.getElementById("can");

ctx = can.getContext("2d");

ctx.font = "14pt Helvetica";

newTargets();

animate();

can.addEventListener("mousemove",move, false);

can.addEventListener("touchmove",tMove, false);

can.addEventListener("mouseup",newBullet, false);

}

function newTargets() {

for (i = 0; i < targets; i++) {

target[i] = 1;

targetX[i] = can.width + 10 + i * 50;

targetY[i] = i * Math.PI / 2;

}

}

// There is a maximum of 1 bullet

// If user shoots again, old bullet is gone

// If bulletX = 0, there is no bullet

// User clicked mouse or lifted finger

function newBullet(e) {

bulletX = 35;

bulletY = shipY + 10;

}

function animate() {

model();

drawBack();

drawShip();

drawBullet();

drawTarget();

drawScore();

setTimeout(animate, 15);

}

function model() {

// if there is a bullet, advance it

if (bulletX)

bulletX = bulletX + 2;

// if the bullet goes off the right edge, zero it

if (bulletX > can.width)

bulletX = 0;

// move targets

for (i = 0; i < targets; i++) {

targetX[i] -= targetSpeed;

targetY[i] += phase;

}

// if last target off left edge of screen,

// generate new set

if (targetX[targets - 1] < 0)

newTargets();

}

function drawBack() {

// pan background

xBack -= xIncr;

ctx.drawImage(back, xBack, 0);

// draw new copy at right edge of old copy

ctx.drawImage(back, xBack + imgWidth, 0);

// if background scrolled off screen, reset

if (xBack <= -1 * imgWidth)

xBack += imgWidth;

}

function drawTarget() {

ctx.strokeStyle = "red";

// for each target:

for (i = 0; i < targets; i++) {

// if target not yet hit:

if (target[i]) {

// draw a circle

ctx.beginPath();

var tY = 150 + 25 * Math.sin(targetY[i]);

ctx.arc(targetX[i], tY, 10, 0, 2 * Math.PI);

ctx.closePath();

ctx.stroke();

// if bullet inside circle, target is hit

if (bulletX && ctx.isPointInPath(bulletX,bulletY)) {

target[i] = 0;

score = score + 10;

}

}

}

}

function drawShip() {

ctx.fillStyle = "white";

ctx.beginPath();

ctx.moveTo(shipX, shipY);

ctx.lineTo(shipX + 30, shipY + 10);

ctx.lineTo(shipX, shipY + 20);

ctx.closePath();

ctx.fill();

}

function drawBullet() {

if (bulletX)

ctx.fillRect(bulletX, bulletY, 2, 1);

}

function drawScore() {

var sc = "Score: " + score;

ctx.fillText(sc, 10, 25);

}

// move ship in response to mouse

function move(e)

{

if (!e)

e = event;

shipY = e.pageY;

return false;

}

// move ship in response to touch

function tMove(e)

{

if (!e)

e = event;

shipY = e.touches[0].pageY;

return false;

}

</script>

</head>

<body onload="init()" style="background-color:black">

<canvas id="can" height="300" width="500"

style="position:absolute;top:0;left:0">

</canvas>

<img id="back" style="display:none" src="starback.png" />

</body>

</html>

Loony Lander Game

The example in Listing 16-2 is a complete game, including sound effects, simulating a Lunar Excursion Module (LEM) landing on the moon. This game uses PNG images—scaled and rotated—as sprites over a fixed background. The game has two custom controls, a button and a slider, created using CSS-styled div elements, to control thrust and rotation. The controls respond to touch or mouse input. A very simple physics model adds gravity to the LEM at regular intervals. The game is illustrated in Figure 16-2.