Build a vertical scrolling shooter game with HTML5 canvas – Part 4

March 16th, 2011

Last time we made it so that our laser would hit an enemy ship and they would be removed and a new enemy would be added to the game. This time we’re going to add a score and set up hit detection on the player’s ship because it would be a pretty boring game if the player can’t die.

The first thing we need to add is a new variable. Add this to the end of the list of variables at the top:

1
score = 0

Now we need to create a function that will display the score on the canvas. Creating and displaying text on the canvas is pretty simple, in fact it’s a lot like drawing a shape. We need to set three things, the font, the style and the content and position. Let’s build the function that will display our score:

1
2
3
4
5
6
function scoreTotal() {
  ctx.font = 'bold 18px Arial';
  ctx.fillStyle = '#fff';
  ctx.fillText('Score: ', 490, 30);
  ctx.fillText(score, 550, 30);
}

With ctx.font, we are setting the weight, size and font of our text. Then with ctx.fillStyle, we set the color to white. Next, we use fillText to actually draw the text on the canvas. For fillText, we need to set three parameters, ctx.fillText(content, x position, y position). In the first instance of fillText, we pass the content as ‘Score’ and the x position to 490px and the y position to 30px. For the second instance, we use the variable score to set the content and the score will update on the canvas for us. And, of course, none of this will work until we actually call the function. So add this to the gameLoop function:

1
scoreTotal();

If you test it now, there should be ‘Score: 0’ in the top right corner. Except when an enemy is shot, the score doesn’t increase yet. We just need to add one line to the hitTest function. After the line enemies.splice(j, 1), add this:

1
score += 10;

Now, every time that an enemy is destroyed and removed, the score will be increased by 10. Since we have the score working and it wasn’t that hard to set up, we’ll create the hit detection for the player’s ship. We’ll start with this:

1
2
3
4
5
6
7
function shipCollision() {
  var ship_xw = ship_x + ship_w,
      ship_yh = ship_y + ship_h;
  for (var i = 0; i < enemies.length; i++) {

  }
}

The first thing we do is create a couple of variables called ship_xw and ship_yh. These two variables find us the top right and bottom right corners of the ship by adding the x position to the width of the ship and the y position to the height. This function is pretty much the same as hit detection for the laser, we’re just checking more, because the ship has four side and the enemies have four sides and any one of them could touch, so we need to check for each. The for loop checks our ship against every enemy in the enemies array. Now the code that checks to see if the ship is touching any of the enemies:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function shipCollision() {
  var ship_xw = ship_x + ship_w,
      ship_yh = ship_y + ship_h;
  for (var i = 0; i < enemies.length; i++) {
    if (ship_x > enemies[i][0] && ship_x < enemies[i][0] + enemy_w && ship_y > enemies[i][1] && ship_y < enemies[i][1] + enemy_h) {

    }
    if (ship_xw < enemies[i][0] + enemy_w && ship_xw > enemies[i][0] && ship_y > enemies[i][1] && ship_y < enemies[i][1] + enemy_h) {

    }
    if (ship_yh > enemies[i][1] && ship_yh < enemies[i][1] + enemy_h && ship_x > enemies[i][0] && ship_x < enemies[i][0] + enemy_w) {

    }
    if (ship_yh > enemies[i][1] && ship_yh < enemies[i][1] + enemy_h && ship_xw < enemies[i][0] + enemy_w && ship_xw > enemies[i][0]) {

    }
  }
}

Each of these four if statements checks to see if any part of the ship is within one of the enemies. I’m not going to go through each one, they essentially say, if the ship’s x position is greater than the enemy’s x position but less than it’s x position plus it’s width and it’s y position is greater than the enemy’s y position but less than the enemy’s y position plus it’s height, then it’s a hit. And this is repeated for the other three sides of the ship.

This is all great and everything, but it won’t amount to much unless we actually have something happen when there’s a collision between the ship and an enemy. So we need to create a new variable that we’ll add to the list at the top:

1
alive = true

Pretty self explanatory, if alive equals true, then our ship is alive, now back in the shipCollision function, we just need to update it to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function shipCollision() {
  var ship_xw = ship_x + ship_w,
      ship_yh = ship_y + ship_h;
  for (var i = 0; i < enemies.length; i++) {
    if (ship_x > enemies[i][0] && ship_x < enemies[i][0] + enemy_w && ship_y > enemies[i][1] && ship_y < enemies[i][1] + enemy_h) {
      alive = false;
    }
    if (ship_xw < enemies[i][0] + enemy_w && ship_xw > enemies[i][0] && ship_y > enemies[i][1] && ship_y < enemies[i][1] + enemy_h) {
      alive = false;
    }
    if (ship_yh > enemies[i][1] && ship_yh < enemies[i][1] + enemy_h && ship_x > enemies[i][0] && ship_x < enemies[i][0] + enemy_w) {
      alive = false;
    }
    if (ship_yh > enemies[i][1] && ship_yh < enemies[i][1] + enemy_h && ship_xw < enemies[i][0] + enemy_w && ship_xw > enemies[i][0]) {
      alive = false;
    }
  }
}

Now, whenever the ship hits an enemy, alive will be set to false. Of course, this needs to be added to the gameLoop so that the function actually runs:

1
shipCollision();

We still have to change one thing, right now, if alive is changed to false, it doesn’t matter because it’s not actually affecting anything. To make shipCollision actually do something, the gameLoop function needs to be updated to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
function gameLoop() {
  clearCanvas();
  if (alive) {
    hitTest();
    shipCollision();
    moveLaser();
    moveEnemies();
    drawEnemies();
    drawShip();
    drawLaser();  
  }
  scoreTotal();
}

Now the game will only run if alive is true and if it’s false, we get a black screen with just the score showing. That’s kind of boring, so for now we can at least tell the player the game is over by adding this to the scoreTotal function:

1
2
3
if (!alive) {
  ctx.fillText('Game Over!', 245, height / 2);
}

When alive is false, the words game over will appear at the center of the canvas. And with that, we continue to add functionality to our game. Next time, we’ll add lives and give the player the option to play again after they’ve lost all their lives. One more thing, as I learn more about building games using JavaScript and the canvas tag, I’ve discovered one other change we should make to the game. Up until now, we’ve been using setInterval to fire off our code, but I’ve learned that it’s not always the best option, mainly because, as in our case, we’ve set the game to fire every 25 milliseconds. This can lead to a problem if the browser can’t run all the code in those 25 milliseconds. It seems the smart way to go, is to use setTimeout, which fires won’t fire until all the code has run. Instead of having:

1
setInterval(gameLoop, 25);

inside the init function, we’ll add this to the gameLoop function:

1
game = setTimeout(gameLoop, 1000 / 30);

Setting it to 1000/30 amounts to running the game at thirty frames a second, which is what I’ve always set for my Flash games. You can check out the game so far right here.

Here’s the entire code:

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Game with score</title>
<style>
body {
  padding:0;
  margin:0;
  background:#666;
}
canvas {
  display:block;
  margin:30px auto 0;
  border:1px dashed #ccc;
  background:#000;
}
</style>
<script>
var canvas,
    ctx,
    width = 600,
    height = 600,
    enemyTotal = 5,
    enemies = [],
    enemy_x = 50,
    enemy_y = -45,
    enemy_w = 50,
    enemy_h = 38,
    speed = 3,
    enemy,
    rightKey = false,
    leftKey = false,
    upKey = false,
    downKey = false,
    ship,
    ship_x = (width / 2) - 25, ship_y = height - 75, ship_w = 50, ship_h = 57,
    laserTotal = 2,
    lasers = [],
    score = 0,
    alive = true;

for (var i = 0; i < enemyTotal; i++) {
 enemies.push([enemy_x, enemy_y, enemy_w, enemy_h, speed]);
 enemy_x += enemy_w + 60;
}

function clearCanvas() {
 ctx.clearRect(0,0,width,height);
}

function drawEnemies() {
 for (var i = 0; i < enemies.length; i++) {
   ctx.drawImage(enemy, enemies[i][0], enemies[i][1]);
 }
}

function drawShip() {
 if (rightKey) ship_x += 5;
 else if (leftKey) ship_x -= 5;
 if (upKey) ship_y -= 5;
 else if (downKey) ship_y += 5;
 if (ship_x <= 0) ship_x = 0;
 if ((ship_x + ship_w) >= width) ship_x = width - ship_w;
  if (ship_y <= 0) ship_y = 0;
 if ((ship_y + ship_h) >= height) ship_y = height - ship_h;
  ctx.drawImage(ship, ship_x, ship_y);
}

function moveEnemies() {
  for (var i = 0; i < enemies.length; i++) {
   if (enemies[i][1] < height) {
     enemies[i][1] += enemies[i][4];
   } else if (enemies[i][1] > height - 1) {
      enemies[i][1] = -45;
    }
  }
}

function drawLaser() {
  if (lasers.length)
    for (var i = 0; i < lasers.length; i++) {
     ctx.fillStyle = '#f00';
     ctx.fillRect(lasers[i][0],lasers[i][1],lasers[i][2],lasers[i][3])
   }
}
function moveLaser() {
 for (var i = 0; i < lasers.length; i++) {
   if (lasers[i][1] > -11) {
      lasers[i][1] -= 10;
    } else if (lasers[i][1] < -10) {
     lasers.splice(i, 1);
   }
 }
}

function hitTest() {
 var remove = false;
 for (var i = 0; i < lasers.length; i++) {
   for (var j = 0; j < enemies.length; j++) {
     if (lasers[i][1] <= (enemies[j][1] + enemies[j][3]) && lasers[i][0] >= enemies[j][0] && lasers[i][0] <= (enemies[j][0] + enemies[j][2])) {
       remove = true;
        enemies.splice(j, 1);
        score += 10;
        enemies.push([(Math.random() * 500) + 50, -45, enemy_w, enemy_h, speed]);
      }
    }
    if (remove == true) {
      lasers.splice(i, 1);
      remove = false;
    }
  }
}

function shipCollision() {
  var ship_xw = ship_x + ship_w,
      ship_yh = ship_y + ship_h;
  for (var i = 0; i < enemies.length; i++) {
   if (ship_x > enemies[i][0] && ship_x < enemies[i][0] + enemy_w && ship_y > enemies[i][1] && ship_y < enemies[i][1] + enemy_h) {
     alive = false;
    }
    if (ship_xw < enemies[i][0] + enemy_w && ship_xw > enemies[i][0] && ship_y > enemies[i][1] && ship_y < enemies[i][1] + enemy_h) {
     alive = false;
    }
    if (ship_yh > enemies[i][1] && ship_yh < enemies[i][1] + enemy_h && ship_x > enemies[i][0] && ship_x < enemies[i][0] + enemy_w) {
     alive = false;
    }
    if (ship_yh > enemies[i][1] && ship_yh < enemies[i][1] + enemy_h && ship_xw < enemies[i][0] + enemy_w && ship_xw > enemies[i][0]) {
     alive = false;
    }
  }
}

function scoreTotal() {
  ctx.font = 'bold 18px Arial';
  ctx.fillStyle = '#fff';
  ctx.fillText('Score: ', 490, 30);
  ctx.fillText(score, 550, 30);
  if (!alive) {
    ctx.fillText('Game Over!', 245, height / 2);
  }
}

function init() {
  canvas = document.getElementById('canvas');
  ctx = canvas.getContext('2d');
  enemy = new Image();
  enemy.src = '8bit_enemy.png';
  ship = new Image();
  ship.src = 'ship.png';
  //setInterval(gameLoop, 25);
  document.addEventListener('keydown', keyDown, false);
  document.addEventListener('keyup', keyUp, false);
  gameLoop();
}
function gameLoop() {
  clearCanvas();
  if (alive) {
    hitTest();
    shipCollision();
    moveLaser();
    moveEnemies();
    drawEnemies();
    drawShip();
    drawLaser();  
  }
  scoreTotal();
  game = setTimeout(gameLoop, 1000 / 30);
}

function keyDown(e) {
  if (e.keyCode == 39) rightKey = true;
  else if (e.keyCode == 37) leftKey = true;
  if (e.keyCode == 38) upKey = true;
  else if (e.keyCode == 40) downKey = true;
  if (e.keyCode == 88 && lasers.length <= laserTotal) lasers.push([ship_x + 25, ship_y - 20, 4, 20]);
}

function keyUp(e) {
  if (e.keyCode == 39) rightKey = false;
  else if (e.keyCode == 37) leftKey = false;
  if (e.keyCode == 38) upKey = false;
  else if (e.keyCode == 40) downKey = false;
}

window.onload = init;
</script>
</head>

<body>
  <canvas id="canvas" width="600" height="600"></canvas>
</body>
</html>

Leave a Reply

Your email address will not be published. Required fields are marked *