Learn Canvas and JavaScript Animation by Creating a Vertical Shooter (COLLISION DETECTION)- Part 6

Building the next 5 stages

Obviously, there are some significant aspects of our game missing. At present there are no bullet or plane collisions, there are no score updates and the game never ends. The next 5 stages will fix these issues and others.

 

Stage 6: Collision Detection

We will be using what is called the bounding box test to check for collisions between the bullet and the enemy planes. Basically, this means that for each bullet we will check if its center coordinates move inside the perimeter bounds of one of the enemies.

Our calculation will look at the X and Y coordinate of the enemy, (the left and top coordinates), and then use the height and width to calculate the bottom and right bounds.

So on the horizontal plane if the bullet.center is greater than (>) enemy.left and less than (<) enemy.right and on the vertical plane if bullet.centre is greater than (>) enemy.top and less than (<) enemy.bottom then the coordinates of the bullet are inside the enemy bounding box.

We will use this same method for detecting collisions between our player’s plane and the enemy ships.

Our collision detection code must be run after each item has been moved. We will therefore create two functions for checking our two different collisions, though it might be possible to simplify this to one function with some creativity.

The first, and easiest collision detection function will check for bullets. The function is passed two objects to check; the first is the enemy object and the second is the bullet. If the center coordinates of our bullet exist inside the enemy bounding box our function will:

  1. move the enemy off the top of screen (y=-200)
  2. set the bullet to inactive
  3. pick a new random x position for the enemy

Add this function to the end of our code:

function checkEnemyCollision(object1, object2) {
  if ((object2.x + object2.width / 2 > object1.x) && (object2.x + object2.width / 2 < object1.x + object1.width) && (object2.y + object2.height / 2 > object1.y) && (object2.y + object2.height / 2 < object1.y + object1.height)) {
    object1.y = -200;
    object2.active = false;
    object1.x =30+Math.random()*(canvasWidth-object1.width-30);
  }
}

 

The function above can to be called from our bullet.update function[1] so that each time the bullet moves it also checks to see if it has hit one of our enemies. See the footnote below for an alternative location to call our function from.

 

Add these lines to our bullet.update() function:

this.update = function () {
  this.y = this.y + this.speed;
  ctx.drawImage(this.image,this.x,this.y,this.width, this.height);

  if (this.y < -10) {
    this.active = false;
  }

  checkEnemyCollision(enemy1, this); 
  checkEnemyCollision(enemy2, this);
  checkEnemyCollision(enemy3, this); 
};

Run your code now and check that when the bullets hit our enemy that they disappear.

Collision Detection between our player and enemy planes

The second collision check we need to code for is when our player touches an enemy plane. While the if condition will be almost exactly the same as above, our code will be a bit more complicated because we want to indicate that there has been a collision[2] by making our player flash and also making sure it does not register multiple collisions while it is flashing (player should be invincible during flashing).

 

The collision detection function should be added to the end of our JavaScript code:

function checkPlayerCollision(playerObject, enemyObject) {

  if ((playerObject.xcenter > enemyObject.x) && (playerObject.xcenter < enemyObject.x + enemyObject.width) && (playerObject.ycenter > enemyObject.y) && (playerObject.ycenter < enemyObject.y + enemyObject.height)) {
    enemyObject.resetLocation();
    playerObject.flashPlayer();
  }
}

 

Notice that it uses 2 functions called resetLocation() and flashPlayer(). Let’s create them now.

The resetLocation() function is used to move the enemy back to the top of the screen once destroyed so it can start moving down again. This code needs to be added to our enemy function; add it at the end of the function before the last parentheses.

function enemy(file, x, y, width, height, wait) {
  this.wait = wait;
  this.x = x;
  this.y = y;
  this.yspeed = 0;
  this.timeout = false;
  this.xspeed = 0;
  this.width = width;
  this.height = height;
  this.image = new Image();
  this.image.onload = function () {
    loadProgress = loadProgress + 1;
    loadingUpdate();
  };
  this.image.src = "images/" + file;
  this.update = function () {
    this.y = this.y + this.yspeed;
    this.x = this.x + this.xspeed;
    if ((this.y > canvasHeight) && (!this.timeout)) {
      var self = this;
      setTimeout(function () {
        self.y = -100 - self.height;
        self.x = 30 + Math.random() * (canvasWidth - self.width);
        self.timeout = false;
      }, self.wait);
      this.timeout = true;
    } else {
      ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
    }
  }; 

  this.resetLocation = function () {
    this.x = Math.random() * canvasWidth -80;
    this.y = -80;
  };
}

 

The second function is the flashPlayer() function and this should be added to our playerPlane function. What it does is:

  1. Sets the plane to invincible
  2. Creates some timers that toggle between our playerFlash = true or false. The more you create the longer the flash lasts.
  3. The final timer sets invincible back to false.

Add lines 8 to 33 into our playerPlane function, just before the closing brackets:

function playerPlane(file, x, y, width, height) {
  this.health = 3;
   ...
  this.update = function () {
   ...
  };

  this.flashPlayer = function () {
    player.playerInvulnerable = true;
    player.playerFlash = true;
    setTimeout(function () {
      player.playerFlash = false;
    }, 100);
    setTimeout(function () {
      player.playerFlash = true;
    }, 200);
    setTimeout(function () {
      player.playerFlash = false;
    }, 300);
    setTimeout(function () {
      player.playerFlash = true;
    }, 400);
    setTimeout(function () {
      player.playerFlash = false;
    }, 500);
    setTimeout(function () {
       player.playerFlash = true;
    }, 600);
    setTimeout(function () {
      player.playerFlash = false;
      player.playerInvulnerable = false;
    }, 700);
  }; // end of flashPlayer function
}

 
Finally we need to call the checkPlayerCollision() function from somewhere. The best place to check for this is in the enemy.update() method so that whenever we move our enemy we also check if it collides with our player. In the enemy.update() method add the lines. These lines first check to make sure that our player hasn’t already crashed into another enemy, therefore stopping the situation where a player could hit two enemies at the same time:

this.update = function () {
    this.y = this.y + this.yspeed;
    …
    if (!player.playerInvulnerable) {
      checkPlayerCollision(player, this);
    }
 };

 
Run your code now and test to make sure our player can collide with enemy planes.
 

Better collisions for the player

Since our player’s sprite is much larger than the bullet we will need to make our bounding box a bit bigger to make sure it looks correct. Presently when you play the game the player can “clip wings” with the enemy, as shown in the diagram below. Because the center coordinate of the player is still outside the bounding box as shown the collision won’t be registered:

Fixing the issue should be as simple as changing our if condition. See if you can change it before we move to the next part of our game, the Score and Health.

Performance tip
This method of clearing the whole screen and then redrawing it can be “expensive” in terms of CPU and graphics processing, particularly if there is only a small section to be updated each run loop.
Sometimes it might be quicker to only clear and redraw a small part of the screen, leaving the rest untouched. The reverse can also be true, if there are many small updates it might be more efficient to update the whole screen each time rather than do many small updates.

Move on to stage 7 –>
Jump to: [Vertical Shooter Post: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 ]


 

[1] A better method if we had infinite enemies would be to check bullet collisions from our enemy.update() method. We would then loop through each active bullet and check.

[2] A later addition will be to add an explosion sound and an explosion animation.