Level-Up Coding

Level 8: You can make sprites move and collide

We have come pretty far. You have sprites on the screen that move around, and you can control them using user input. But to make a complete game, you need to add some more code to manage the gameplay of your game, and deal with collisions between sprites.


Spawning shots

Spawning shots is a common thing to do in games. This might happen when the player presses the spacebar, for example. Before writing the code to spawn a shot, you need to figure out where the shot will start, and what its starting velocity will be.

For the example with the ship that we have been using, shots should go straight up, The vx component of the velocity should be 0, and the vy component should be a negative number that depends on how fast the shot should go.

The position of the shot is a little harder, since it should start just above the center of the ship. You can see this set up in the diagram below:

First let’s calculate t.x, the x component of the shot’s position. t.x is one half the width of the shot to the left of the ship’s center, which gives us the equation:
t.x = s.x + 0.5 * s.w - 0.5 * t.w;
The y component of the shot t.y is the above the ship by the height of the shot:
t.y = s.y - t.h;


Managing shots with the gameState variable

You can handle shots with the gameState the same way as you did enemies, using an array to hold them. The code to spawn a shot could be in the onKeyDown function, might look something like the following:

    // Spawn a shot when the spacebar is pressed
    if (event.keyCode == 32)
    {
        var s = gameState.ship;
        var t = new Sprite("shotImage", 0, 0, 0, -10);
        t.x = s.x + 0.5 * s.w - 0.5 * t.w;
        t.y = s.y - t.h;
        gameState.shots.push(t);
    }

Of course, a shot in a video game should probably have a limited life. At some point they should be deleted from the game. There are a several ways you can do this. One is to check to see if the shots will go off the canvas, and delete them if they will. Another way is to give the shots a timer variable. Each time a shot is moved, you also subtract one from the timer variable, and when it reaches 0 you delete the shot.


Sprite collision bounds

One thing that you need to do in games is to calculate collisions between sprites (such as shots and enemies). An easy way to do this is to act like the sprites are just rectangles. Then you check for overlap between the rectangles.

Remember that a sprite has an image with a bunch other object properties. Four of those properties are the (x,y) coordinates of the top left corner of the sprite image, and the width and height of the sprite (w,h). Using these together, you can calculate the bottom right corner of the image as (x+w, y+h). Together, the top left corner and bottom right corner are called the bounds of the sprite.

Most of the time, sprites don’t really cover all of the image area. Some of the image is transparent, so it’s better to imagine that there is a border around the edge that’s not part of the sprite object. The width of this border is different for each sprite, and we can store it as an extra property of the sprite, called the border.

Reducing the bounds of the sprite by the border width gives you the collision bounds. To get the collision bounds of the sprite, you just add the border width b to the top left corner coordinates (x,y), and subtract it from the bottom right corner coordinates (x+w, y+h), as shown in the following diagram:

In code, we can write a function that will get the collision bounds in an object with four properties: xmin, ymin, xmax, ymax.

    var getCollisionBounds = function(sprite)
    {
        var bounds = {};
        var border = sprite.border;
        bounds.xmin = sprite.x + border;
        bounds.ymin = sprite.y + border;
        bounds.xmax = sprite.x + sprite.w - border;
        bounds.ymax = sprite.y + sprite.w - border;
        return bounds;
    }

Collisions between sprites

Starting with the collision bounds for two sprites, it’s not too hard to figure out if the sprites collide. You just need to check whether the sprites overlap in the x and y directions with three steps:

  1. If one sprite is completely to the left or right of the other, the sprites do not collide.

  2. If one sprite is completely above or below the other, the sprites do not collide.

  3. Otherwise, the two sprites do collide.

This is shown graphically in the figure below:

The function spritesCollide implements the three steps and returns true if the two sprites overlap, and false if they do not:

    var spritesCollide = function(spriteA, spriteB)
    {
        // collision bounds for spriteA and spriteB
        var a = getCollisionBounds(spriteA); 
        var b = getCollisionBounds(spriteB);

        // spriteB is to the right or left of spriteA
        if (b.xmin > a.xmax) return false;
        if (b.xmax < a.xmin) return false;

        // spriteB is above or below spriteA
        if (b.ymax < a.ymin) return false;
        if (b.ymin > a.ymax) return false;

        // The two sprites collide if spriteA is not
        // left of, right of, above, or below spriteB
        return true;
    }

Collisions between One shots and All the enemies

The spritesCollide function that was just defined can test whether one shot hits one enemy, but each shot has to be tested against all of the enemies. To do this, we use a loop. The collideShot function tests the shot and index s against all of the enemies. If there are any collisions, it deletes the enemy and the shot:

    // collide one shot with all of the enemies
    var collideShot(var s)
    {
        var shot = gameState.shots[s];
        var enemies = gameState.enemies;
        for (var i=0; i < enemies.length; i++)
        {
            if (spritesCollide(shot, enemies[s]))
            {
                gameState.shots.splice(s, 1); // delete shot
                enemies.splice(i, 1);         // delete enemy
                return;
            }
        }
    }

Collisions between All the shots and All the enemies

Now, to test whether any shot hits any enemy, we have to call collideShot for every shot! In code you can write a loop to do this in the animateGame function:

    for (var s = gameState.shots.length-1; s >= 0; s--)
    {
        collideShot(s);
    }

Hitting the edge of the canvas

You can figure out if a sprite has hit the edge of the canvas, once again by using the collision bounds. There will be four checks:

  1. On the left side, check to see if xmin < 0.
  2. On the right side, check to see if xmax > canvas.width.
  3. On the top, check to see if ymin < 0.
  4. On the bottom, check to see if ymax > canvas.height

The function below returns true if a sprite is completely on the canvas, and false if it is not:

    var spriteOnCanvas = function(sprite)
    {
        var cw = canvas.width;
        var ch = canvas.height;
        s = getCollisionBounds(sprite);

        // check left and right
        if (s.xmin < 0) return false;
        if (s.xmax > canvas.width) return false;

        // check top and bottom
        if (s.ymin < 0) return false;
        if (s.ymax > canvas.height) return false;

        // we have checked left, right, top, and bottom
        // so the sprite must be on the canvas.
        return true;
    }

Bouncing off the edge of the canvas

To make a sprite bounce off the edge of the canvas, set the sign of vx or vy to positive or negative based on the which side was hit. The function doBounce does this:

    var doBounce = function(sprite)
    {
        var a = getCollisionBounds(sprite); 

        // bounce off left and right edge of canvas
        if (a.xmin < 0) 
        {
            sprite.vx = Math.abs(sprite.vx);
        }
        if (a.xmax > canvas.width) 
        {
            sprite.vx = -Math.abs(sprite.vx);
        }

        // bounce off top and bottom edge of canvas
        if (a.ymin < 0) 
        {
            sprite.vy = Math.abs(sprite.vy);
        }
        if (a.ymax > canvas.height) 
        {
            sprite.vy = -Math.abs(sprite.vy);
        }
    }

Explosion sprites

Your game will work if you just make ships disappear when they are hit with the shot, but it would be a lot more exciting if they exploded instead. One way to do this is to replace enemies with explosion sprites when they are destroyed. An explosion sprite is just a picture of a fireball that is about the same size as the enemy. You can put a timer on the explosion sprite, and remove it after a second or two so that the explosion disappears.

Usually in a video game, explosion sprites do not interact with anything, so you can put them in a separate array that is not part of enemies.


Terms


First exercise - shots

For this exercise, you will start with the completed code from the previous level, and add code to spawn shots when the user presses the spacebar.

  1. Copy your completed code from level 7 that has the moving ships, as well as the ship and enemy images.
  2. Save the file shotImage.png to the same folder where you saved the level 7 code. The image is included below.
  3. If you have not already done so, add code to make the ship move based on keyboard input.
  4. Add an empty array called shots to the gameState object.
  5. Add code to spawn a shot when the user presses the spacebar.

Second exercise - colliding shots with enemies

  1. Copy the getCollisionBounds code into your program.
  2. Copy the spriteCollision code into your program.
  3. In animateGame, add code that will remove enemy sprites if the shot hits them.

Bonus level