Tutorial: How to make a top-down shooter in JavaScript

Lesson 11) Health, damage and game over

Let’s add a health bar… and not the kind that serves smoothies

We’ve already given the player a health property, with the value 5. Let’s put this to use finally. First, change that value to 200. You’ll see why shortly. Then update drawHUD as follows:

function drawHUD() {
  c.font = '18pt Calibri';
  c.fillStyle = 'black';
  c.fillText("Health:", 10, 25);

  c.beginPath();
  if (Player1.health < 50) {
    c.strokeStyle= 'red';
  } else {
    c.strokeStyle= 'yellow';
  }
  c.moveTo(85,18);
  c.lineTo(85 + Player1.health, 18);
  c.lineWidth=15;
  c.stroke();

  c.fillStyle = 'black';
  c.fillText("Points:", 370, 25);
  c.fillText("Time remaining:", 585, 25);

  if (timeRemaining < 10) {
    c.fillStyle = "red"
  }
  c.fillText(Math.ceil(timeRemaining), 750, 25);

  c.fillStyle = 'yellow';
  c.fillText(Player1.points, 445, 25);
}

At the top we’ve written the word “Health” onto the canvas, and then started a new path. We draw a line, starting at position x=85, y=18, and finishing at x=85+Player1.health, y=18. This is why we made the health 200 = one pixel of the health bar for each health point. Then we set the line width to 15. The rest of the function is slightly different too, I’ve just shifted the time and points positions along a bit. Note the if statement that gives us a nice Street Fighter 2 style health bar – starts out yellow and turns red when health is low.

This should definitely make an impact

The rest is simple, because we’ve already done 90% of it. In the badGuyCollidesX/badGuyCollidesY functions we already check if the bad guy is about to impact the player.

Previously we just returned true, but let’s add a few more lines in there:

if (i.x + step < Player1.x + Player1.w &&
i.x + step + i.w > Player1.x &&
i.y < Player1.y + Player1.h &&
i.y + i.h > Player1.y && !i.hit) {
  Player1.health -= 40;
  if (Player1.health <= 0){
    gameOver = true;
    endMessage = " Game Over!";
  }
  i.hit = true;
  return true;
}

As well as checking for collision, we’re also checking that i.hit is false (that’s the !i.hit bit). If all of this is true, we take 40 off the player’s health, set “gameOver” to true if health has dropped to zero, and set a variable called endMessage to “Game Over!” (we’ll come to this shortly). Then we set i.hit to true.

So basically, if there’s a collision we take health off the player, and set i.hit to true. If we didn’t do this, we might register two hits on the player – one from the x axis, one from the y axis. So we set the bad guy’s “hit” property to true on a collision, then our program will ignore the y axis collision code, should there be one. Remember to do the same to the badGuyCollidesY.

At the end of badGuyMove, we can then check if that baddie’s hit property is true, and if so, splice it from the array:

function badGuysMove() {
  theBadGuys.forEach( function(i, j) {
    if (i.x > Player1.x && !badGuyCollidesX(i, j, -i.speed)) {i.x -= i.speed;}
    if (i.x < Player1.x && !badGuyCollidesX(i, j, i.speed)) {i.x += i.speed;}
    if (i.y > Player1.y && !badGuyCollidesY(i, j, -i.speed)) {i.y -= i.speed;}
    if (i.y < Player1.y && !badGuyCollidesY(i, j, i.speed)) {i.y += i.speed;}
    if (i.hit === true){ theBadGuys.splice(j, 1); }
  });
}

Game Over Routine

Hopefully you’ve added this extra code and got it working. You’ll probably have noticed a huge problem – the player’s health can go below zero. Is negative health even possible? Maybe that’s what the undead have? A topic for another day perhaps. Right now we need a way to end the game when the player’s health reaches zero.

Let’s start by defining some variables. I feel like we haven’t done that for a while, so this should bring back happy memories:

var gameOver = false;
var endMessage = "";

Just like old times eh?

Now let’s update mainDraw. For brevity I didn’t copy the while thing:

if (!gameOver){
  //... main function calls ...

    timeRemaining -= 0.02;
    if (timeRemaining < 0) {
      gameOver = true;
      endMessage = " You Survived!";
    }
  } // if !gameOver
  if (gameOver) {
    endStats();
  }
} //mainDraw

Right. So now we only run mainDraw if gameOver is false — in other words, if the fat lady has not yet sung. If the timer runs out, we set gameOver to true.

This is why we use a separate if statement afterwards. If we used an else, the timer could run out but the game would have to go through another loop to get to endStats(). In that time, a bad guy might collide and kill the player, which would make the player lose the game even though they survived for 30 seconds.

Within endStats, let’s add a few extra lines to display the endMessage to the player:

c.font = '80pt Calibri';
c.fillStyle = 'black';
c.fillText(endMessage, 70, 140);

Nice and simple, you’re familiar with writing text by now. I updated the rest of the text on the end screen, shifting it all down a bit to fit this new message in.

This game is over (rated?)

Back to our initial problem with the negative health bar. This is an easy fix now. When we deduct from the player’s health in badGuyCollidesX and badGuyCollidesY, we’ll just check whether the player’s health is zero or lower, and if so, we set gameOver to true:

if (Player1.health <= 0) {
  gameOver = true;
  endMessage = " Game Over!";
}

We also need to set gameOver back to false, and Player1.health back to 200, when the user presses the enter key to restart the game (in the “if ( keys[13] )” bit).

Let’s see what we’ve got:

This game is exceptionally difficult! My best score was 87, and it was just pure luck that few baddies spawned, and I got a few slow ones. Next, we’ll give the player a fighting chance.

Leave a Reply

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