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

Lesson 10) Stopping bad guys from overlapping

Lap it up… or not

If our blue baddies reach the player (which is inevitable at the moment, because some of them move faster than him) they just overlap. Effectively, they all pile on top of each other. While this would be an impressive physical feat, tactically I’d suggest it’d be a bit of an error. It’s more likely that the baddie at the front would fight the hero, while the ones behind either wait their turn to attack (like in Hollywood movies), or move around and swarm the hero from different angles (like in real life). Let’s simulate that in our game.

We already have a collision detection which checks every game loop whether or not the player and a coin are overlapping. To stop the bad guys piling up we do the same thing, but instead of checking the present location of each baddie, we check the location it intends to move into. If there would be a collision if he moved there, we don’t let him move. If there would not, we allow the movement.

To do this we need to add another condition to the badGuyMove function:

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;}
  });
}

Let’s take the top if statement as an example. Previously in this line, we checked whether the bad guy’s x coordinate is higher than the player’s. If it was, we deducted the bad guy’s speed from his x coordinate, effectively moving him towards the player.

However, now we also run the badGuyCollidesX function. This function, as I will explain below, returns a boolean value – either true or false. The exclamation mark in front of the function call means “Not” — so we’re looking for a false to be returned. Only then will we run the subsequent code and move the baddie.

Let’s look at this function:

function badGuyCollidesX (i, j, step) {
  for (k = theBadGuys.length - 1; k >= 0; k--){
    if (j != k && 
        i.x + step < theBadGuys[k].x + theBadGuys[k].w && 
        i.x + step + i.w > theBadGuys[k].x &&
        i.y < theBadGuys[k].y + theBadGuys[k].h && 
        i.y + i.h > theBadGuys[k].y) {
      return true;
    }
  }

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) {
return true;
}
return false;
}

We’ve passed three parameters to the function: i is the bad guy we want to move, j is his position in our theBadGuys array, and step is his speed property. Actually in this case, we’re checking if the bad guy can move to the left, so if you look at the previous code block you’ll see we actually passed -i.speed (so if his speed is 3, we’ve passed -3).

Then we start a for loop, we’re looping through each of the other bad guys to see if any of them are in his way:

for (k = theBadGuys.length - 1; k >= 0; k--){

}

You might be wondering why we start this off with “k = theBadGuys.length – 1”. It’s because the array starts at position 0. If there are 4 baddies on screen, theBadGuys.length will equal 4, but they will be in positions 0, 1, 2, and 3. We’re going to loop through theBadGuys, using k to reference the baddies within it, i.e., theBadGuys[k] will reference the baddie at position k in the array. So in this example if we just used theBadGuys.length, we’d end up referencing theBadGuys[4], which would cause an error as there’s nothing in that position.

As for the part within the loop, you’ve seen it before. It’s the same collision detection routine we used to detect whether the player has collected a coin. Only this time instead of checking if there is a collision at the x coordinate where the bad guy is currently standing, we’re checking if there is collision at his x coordinate + the step parameter. So if he’s at x=100 and his speed is 3, we’re checking if there is a collision at x=100 + -3 (remember we passed -i.speed in this example). In other words we’re checking for a collision at x=97.

If there is a collision at this new location, we return true.

If not, we move on to the next bit, which is the exact same routine but checking for a collision with the player. Again, we return true if there is one.

The further if statements in badGuysMove do the same thing for collisions along the y axis too. I checked the x and y axes separately so that baddies would still move along one axis, even if there was a potential collision on another. Look at the following example:

Because of the player’s position, both baddies will attempt to move to where the lighter-shaded blue box is. However, if we checked both the x and y positions that they intended to move to at the same time, our routine would detect a collision with the other baddie and neither would move – even though their path along the x axis is unobstructed. So by checking x and y separately, the y routine will detect a collision but the x will not — and they will move side-by-side towards the player.

Here’s how the new additions look in the game:

This is looking pretty good. The movement is working quite nicely. But the blue bad guys just swarm around the player and do nothing. It’s possible to completely ignore them and collect your coins as normal.

Let’s make them damage the player on impact.

Leave a Reply

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