Tutorial: HTML 5 Space Shooter Part 3

Welcome to part 3 of the HTML 5 Space Shooter tutorial series. If you haven't already, I'd recommend checking out parts 1 and 2 before jumping in here. We're going to pick up where we left at the end of the last tutorial and implement enemies and collision detection. Let's get right to it!

The first thing we'll do is add enemies...well, maybe obstacles would be a better term in our case as our game will be about avoiding asteroids or blowing them up. The first task is to find a suitable graphic. You can use any image you like or even create your own; just make sure it is fairly small. For this tutorial I'm using a 64x64 pixel graphic which I found here. Save the image in the same directory as your html file; I simply named mine rock.png.

We're going to want to be able to have more than one rock on screen at a time so we'll declare a rocks variable at the top of our javascript block as below. Also, I'm going to impose an arbitrary limit on the number of rocks that can exist at a given time. To do this I'll use a constant, unchanging variable named ROCK_LIMIT.

var ROCK_LIMIT = 8;
var ROCK_SPEED = 8;
var rocks;

Notice that we're also creating a variable to control the speed of the rocks.

The next step is to instantiate our rocks variable; we'll do this in our init function. Go ahead and put it just after we initialize our stage variable.

stage = new Stage(canvas);
rocks = new Array();

Easy, right? Next up, let's write a function that will create a rock for us and add it to the display list.

function createRock() {
    if(randRange(0, 100) > 80 && rocks.length < ROCK_LIMIT) {
        var r = new Bitmap("rock.png");
        r.regX = r.image.width * 0.5;
        r.regY = r.image.height * 0.5;
        r.x = randRange(20, 630);
        r.y = -randRange(50, 100);
        rocks.push(r);				
        stage.addChild(r);
    }
}

The function above gives us about a 20% chance at creating a new rock, but only if we haven't hit our limit! Assuming our random number is greater than 80 and we're not yet at the limit, we create a new Bitmap object using our rock image. We then set the registration point to the center of the image and give the rock a random position on stage. Note that the y position is negative. This is because we want our rocks to come from the top of the screen and fall downward. Next we add the new rock to our rocks array and also add it to the display list.

The createRock function will make us rocks, but it won't move them. For that we'll create a new function named updateRocks. This will be similar to our updateBullets function we wrote in the last tutorial, but instead of bullets, we'll be moving rocks.

function updateRocks() {
    var loopLimit = rocks.length - 1;
    for(var i = loopLimit; i >= 0; --i) {
        rocks[i].y += ROCK_SPEED;
        if(rocks[i].y > 500) {					
            stage.removeChild(rocks[i]);
            rocks.splice(i, 1);
        }
    }
}

The updateRocks method loops through our array of rocks and updates the y position of each one. If the rock's y position is greater than 500, we remove the rock from the display and also take it out of our array; there's no need to keep track of it any more since it is no longer being displayed!

The only thing left to do now is to call these two new functions. We'll add the function calls to our tick method.

function tick() {
    createRock();  // new
    updateStarField();
    updateBullets();
    updateRocks();  // new
    checkMovement();
    stage.update();
}

If you open your html page in a supporting browser, you should now see asteroids falling from the top of the screen! Cool! At the moment; however, they don't pose much of a threat. They fly right by your ship causing no damage. Also, fire as you might, your bullets go straight through! Hmm, let's fix that by creating a collide method. First though, let's write a helper method named distance. This function will take in two objects and return the distance between them.

function distance(obj1, obj2) {
    var difx = obj2.x - obj1.x;
    var dify = obj2.y - obj1.y;
			
    return Math.sqrt( (difx*difx) + (dify*dify) );
}

Great! Now we can write our collide method. We're going to handle two kinds of collision in this function; player/rock collisions and rock/bullet collisions. For now the player/rock collision won't really do anything (we'll get to that later) but when we detect a rock/bullet collision we're going to remove both the rock and the bullet from the display.

function collide() {
  var numRocks = rocks.length - 1;
  var numBullets = bullets.length - 1;
  var curRock, curBullet;
				
  for(var i = numRocks; i >= 0; --i) {
    curRock = rocks[i];
    // check player/rock collision
    if(distance(curRock, ship) < 45) { 
      
    }

    // check bullet/rock collision	
    for(var j = numBullets; j >= 0; --j) {
      curBullet = bullets[j];
						
      if( distance(curRock, curBullet) < 32)  {
        stage.removeChild(curRock);
        stage.removeChild(curBullet);
        rocks.splice(i, 1);
        bullets.splice(j, 1);				
      }
    }
    numBullets = bullets.length - 1;
  }
}

Ok, this function is fairly long so let's break it down to see what's going on. First we store the number of rocks and bullets so that we don't have to look up the value every iteration of the loops. We also create two variables to store the current rock and bullet.

var numRocks = rocks.length - 1;
var numBullets = bullets.length - 1;
var curRock, curBullet;

Next we begin looping through our rocks and checking for collisions. The first collision check is to see if the current rock has hit the player. We do this by checking the distance between the current rock and the player. Remember that both the player object and rock have the registration point set in the center, so that is where the measurement is based. If the distance between them is less than half the size of the rock(32) plus half the size of the ship(25), a collision has occurred; however, I'm checking for a value of 45 to give the player a little padding.

for(var i = numRocks; i >= 0; --i) {
    curRock = rocks[i];
    // check player/rock collision
    if(distance(curRock, ship) < 45) { 

    }

As mentioned earlier, this doesn't have any effect on the game yet. After checking to see if the ship has collided with asteroids, we loop through the bullets array and check for collisions between the bullets and asteroids.

for(var j = numBullets; j >= 0; --j) {
    curBullet = bullets[j];
				
    if( distance(curRock, curBullet) < 32)  {
        stage.removeChild(curRock);
        stage.removeChild(curBullet);
        rocks.splice(i, 1);
        bullets.splice(j, 1);				
    }
}

Here we grab a bullet from the array and use our distance function to see if the bullet has collided with an asteroid. If the distance is less than half of the asteroid, a collision has occurred. In that case, we remove both the bullet and asteroid from the display and from their respective arrays.

The last thing we do in our collision loop is reset the value of numBullets. This is necessary because our inner loop which checks the asteroid/bullet collisions could potentially have changed the number of bullets in the bullets array.

numBullets = bullets.length - 1;

With the collision function in place, all we need to do is call it inside our tick function.

function tick() {
    collide();  // new
    createRock();
    updateStarField();
    updateBullets();
    updateRocks();
    checkMovement();
    stage.update();
}

If you open the html file in your browser now you should be able to shoot asteroids and have them disappear, along with the bullets that hit them. This is going pretty well, but we've still got quite a bit to do! We're going to add an explosion when an asteroid is hit and of course we also still need to end the game when the player gets hit by an asteroid. When that happens, we'll also need an explosion, so let's take care of getting that explosion in place first! Again, you can use any image you like. I grabbed an explosion here, brought it into photoshop, resized it to 64x64 and also cut out the background and saved it as explode.png.

As we've done for many objects before, we first must create a new variable to hold our explosions. Go ahead and add a variable at the top of the JS block and also initialize it inside the init function.

var explosions;

function init() {
    canvas = document.getElementById("canvas");
    stage = new Stage(canvas);

    explosions = new Array(); // new
    rocks   = new Array();
    bullets = new Array();

Now we'll write a function to create an explosion, aptly named createExplosion. This function will accept two parameters: an x position and a y position at which to place the explosion.

function createExplosion(xpos, ypos) {
    var exp = new Bitmap("explode.png");
    exp.regX = exp.image.width * 0.5;
    exp.regY = exp.image.height * 0.5;
    exp.x = xpos;
    exp.y = ypos;
    explosions.push(exp);
    stage.addChild(exp);
}

This function creates a new Bitmap object using the explode.png. Then it sets the registration point to the center of the image and also positions the image according to the function parameters. It also adds the newly created explosion to the explosion array and finally to the display list. With the creation method done, let's write the update function that will be responsible for updating explosions once they've been added to the display.

function updateExplosions() {
    var loopLimit = explosions.length - 1;
    for(var i = loopLimit; i >= 0; --i) {
        explosions[i].alpha -= 0.1;
        if(explosions[i].alpha <= 0) {
            stage.removeChild(explosions[i]);
            explosions.splice(i, 1);
        }
    }
}

The updateExplosions function above loops through all the current explosions and fades them out over time. When an explosion's alpha is less than or equal to 0, it is no longer visible so we can go ahead and remove it from the display and from the explosions array. As with our other update functions we need to add a call to this one in the tick function.

function tick() {
    collide();  
    createRock();
    updateStarField();
    updateBullets();
    updateRocks();
    checkMovement();
    updateExplosions(); // new
    stage.update();
}

Now we have functions for creating explosions and for updating them. The next obvious step is to go ahead and add some to the display! We'll start by adding them whenever a bullet hits an asteroid. To do this, let's look at the collide function we created earlier.

function collide() {
  var numRocks = rocks.length - 1;
  var numBullets = bullets.length - 1;
  var curRock, curBullet;
				
  for(var i = numRocks; i >= 0; --i) {
    curRock = rocks[i];
    // check player/rock collision
    if(distance(curRock, ship) < 45) { 
      
    }

    // check bullet/rock collision	
    for(var j = numBullets; j >= 0; --j) {
      curBullet = bullets[j];
						
      if( distance(curRock, curBullet) < 32)  {
        stage.removeChild(curRock);
        stage.removeChild(curBullet);
        createExplosion(curRock.x, curRock.y); // new
        rocks.splice(i, 1);
        bullets.splice(j, 1);				
      }
    }
    numBullets = bullets.length - 1;
  }
}

As you can see, we've added a call to createExplosion whenever we detect a collision between an asteroid and a bullet. We pass in the asteroid's current position and add the explosion at that spot. If you open the html file now you should be able to shoot asteroids and have them explode!

We're just about done now, but not quite! We still need to end the game whenever an asteroid hits our player. To take care of this, begin by adding a new variable to the top of the JS block; a boolean named gameOver. We will set this variable to false to begin with.

var gameOver = false;

Next, let's update the tick function to only run certain updates if the game is not over. Afterall, we don't want the player to be able to move and fire if the game has ended!

function tick() {
    if(gameOver != true) {
        collide();
        createRock();
        checkMovement();
    }
				
    updateStarField();
    updateBullets();
    updateRocks();
    updateExplosions();
    stage.update();
}

Notice that we've moved checkMovement up near the top of the function and wrapped that call, along with calls to collide and createRock, within an if statement. These calls only need to occur if the game has not ended. We leave the other calls outside of the if statement because it's fine to update the other items even if the game has ended. If we didn't make these calls, the entire screen would just freeze up and it would look odd to have explosions and bullets frozen in place.

The next thing we'll do here is create an endGame function. This method will do a few things; first it will add an explosion at the location of the player ship while also removing the ship from the display. Then it will set the gameOver variable to true so that the player can no longer move and fire. Lastly it will display a giant "Game Over" message to the screen.

function endGame() {
    createExplosion(ship.x, ship.y);
    stage.removeChild(ship);	
			
    gameOver = true;			
			
    var goverText = new Text("GAME OVER", "72pt bold Arial", "#cc0000");
    goverText.textAlign = "center";
    goverText.x = 320;
    goverText.y = 240;
    stage.addChild(goverText);
}

The only thing left to do is to actually call this function inside of our collide method when the player hits an asteroid.

function collide() {
    var numRocks = rocks.length - 1;
    var numBullets = bullets.length - 1;
    var curRock, curBullet;
				
    for(var i = numRocks; i >= 0; --i) {
        curRock = rocks[i];
        if(distance(curRock, ship) < 45) { // a distance of 45 gives a little cushion for the player
            endGame();
        }

    // rest of collide method 

Well, if you've made it this far then you now have a functioning space shooter game written in easeljs and HTML5; congratulations! For extra credit, try adding sound to the game and a scoring system! In my original version, I used Grant Skinner's soundjs library. If you get stuck, just take a look at the game right here and view the source code to see how things were done. The code isn't exactly the same as the code from this tutorial series, but it is very similar.

Thanks for reading; I hope you found this series useful! You can download the source for part 3 right here.

Part 1 Part 2

Bookmark and Share

Leave a Reply

Subscribe to RSS feed FGS5 Badge