Using HTML5 canvas and JavaScript to detect collisions between vehicles in a game

This topic explains how to detect collisions between your vehicle and other objects on the screen.

  • Canvas Code Sample
    • Canvas Code Sample Discussion
  • Related topics

This topic includes a stand-alone annotated code sample that shows how to use HTML5 Canvas and JavaScript to detect if the spaceship collides with a red asteroid or the blue home base. The code in this sample builds on the sample in the Moving Your Vehicle topic. This sample demonstrates how to use Canvas to keep track of every pixel on the screen. Because it operates in immediate mode, Canvas has the ability to define the position and color value of each pixel. Because Canvas allows you to read and write each pixel, you can create detailed animations that run fast and efficiently. This code sample shows you how to assign specific color values to pixels to create red asteroids and a blue home base. Then it explains how to detect collisions by taking snapshots of the screen to scan for colored objects that the spaceship must avoid.

This code sample covers the following tasks that demonstrate the basic principles of using Canvas to detect the color of different objects:

  • Drawing asteroids and a home base with specific colors for later detection
  • Taking snapshots of what is in front of the spaceship
  • Scanning snapshots for colored objects and displaying messages

At the end of the code sample is discussion material that explains more about the design and structure of these tasks and how they work.

Canvas Code Sample

<!DOCTYPE html>
<html>
  
  <head>
    <script type="text/javascript">
      // Global variables
      var shipX = 0; // X position of ship
      var shipY = 0; // Y position of ship
      var canvas; // canvas
      var ctx; // context
      var back = new Image(); // storage for new background piece
      var oldBack = new Image(); // storage for old background piece
      var ship = new Image(); // ship
      var shipX = 0; // current ship position X
      var shipY = 0; // current ship position Y
      var oldShipX = 0; // old ship position Y
      var oldShipY = 0; // old ship position Y
      // This function is called on page load.


      function canvasSpaceGame() {

        // Get the canvas element.
        canvas = document.getElementById("myCanvas");

        // Make sure you got it.
        if (canvas.getContext)

        // If you have it, create a canvas user interface element.
        {
          // Specify 2d canvas type.
          ctx = canvas.getContext("2d");

          // Paint it black.
          ctx.fillStyle = "black";
          ctx.rect(0, 0, 300, 300);
          ctx.fill();

          // Save the initial background.
          back = ctx.getImageData(0, 0, 30, 30);

          // Paint the starfield.
          stars();

          // Draw space ship.
          makeShip();

          // Draw asteroids.
          drawAsteroids();
        }

        // Play the game until the until the game is over.
        gameLoop = setInterval(doGameLoop, 16);

        // Add keyboard listener.
        window.addEventListener('keydown', whatKey, true);

      }

      // Paint a random starfield.


      function stars() {

        // Draw 50 stars.
        for (i = 0; i <= 50; i++) {
          // Get random positions for stars.
          var x = Math.floor(Math.random() * 299);
          var y = Math.floor(Math.random() * 299);

          // Make the stars white
          ctx.fillStyle = "#EEEEEE";

          // Paint the star but not if too close to ship.
          if (x > 40 && y > 40) {

            // Draw an individual star.
            ctx.beginPath();
            ctx.arc(x, y, 3, 0, Math.PI * 2, true);
            ctx.closePath();
            ctx.fill();
          } else--i;
        }
        // Save black background.
        oldBack = ctx.getImageData(0, 0, 30, 30);

      }

      function makeShip() {

        // Draw saucer bottom.
        ctx.beginPath();
        ctx.moveTo(28.4, 16.9);
        ctx.bezierCurveTo(28.4, 19.7, 22.9, 22.0, 16.0, 22.0);
        ctx.bezierCurveTo(9.1, 22.0, 3.6, 19.7, 3.6, 16.9);
        ctx.bezierCurveTo(3.6, 14.1, 9.1, 11.8, 16.0, 11.8);
        ctx.bezierCurveTo(22.9, 11.8, 28.4, 14.1, 28.4, 16.9);
        ctx.closePath();
        ctx.fillStyle = "rgb(222, 103, 0)";
        ctx.fill();

        // Draw saucer top.
        ctx.beginPath();
        ctx.moveTo(22.3, 12.0);
        ctx.bezierCurveTo(22.3, 13.3, 19.4, 14.3, 15.9, 14.3);
        ctx.bezierCurveTo(12.4, 14.3, 9.6, 13.3, 9.6, 12.0);
        ctx.bezierCurveTo(9.6, 10.8, 12.4, 9.7, 15.9, 9.7);
        ctx.bezierCurveTo(19.4, 9.7, 22.3, 10.8, 22.3, 12.0);
        ctx.closePath();
        ctx.fillStyle = "rgb(51, 190, 0)";
        ctx.fill();

        // Save ship data.
        ship = ctx.getImageData(0, 0, 30, 30);

        // Erase it for now.
        ctx.putImageData(oldBack, 0, 0);
      }

      function doGameLoop() {

        // Put old background down to erase shipe.
        ctx.putImageData(oldBack, oldShipX, oldShipY);

        // Put ship in new position.
        ctx.putImageData(ship, shipX, shipY);
      }

      // Get key press.


      function whatKey(evt) {

        // Flag to put variables back if we hit an edge of the board.
        var flag = 0;

        // Get where the ship was before key process.
        oldShipX = shipX;
        oldShipY = shipY;
        oldBack = back;

        switch (evt.keyCode) {

          // Left arrow.
        case 37:
          shipX = shipX - 30;
          if (shipX < 0) {
            // If at edge, reset ship position and set flag.
            shipX = 0;
            flag = 1;
          }
          break;

          // Right arrow.
        case 39:
          shipX = shipX + 30;
          if (shipX > 270) {
            // If at edge, reset ship position and set flag.
            shipX = 270;
            flag = 1;
          }
          break;

          // Down arrow
        case 40:
          shipY = shipY + 30;
          if (shipY > 270) {
            // If at edge, reset ship position and set flag.
            shipY = 270;
            flag = 1;
          }
          break;

          // Up arrow 
        case 38:
          shipY = shipY - 30;
          if (shipY < 0) {
            // If at edge, reset ship position and set flag.
            shipY = 0;
            flag = 1;
          }
          break;

        default:

          flag = 1;
          alert("Please only use the arrow keys.");
        }

        // If flag is set, the ship did not move.
        // Put everything back the way it was.
        if (flag) {
          shipX = oldShipX;
          shipY = oldShipY;
          back = oldBack;
        } else {
          // Otherwise, get background where the ship will go
          // So you can redraw background when the ship
          // moves again.
          back = ctx.getImageData(shipX, shipY, 30, 30);
        }

        collideTest();
      }

      function collideTest() {

        // Collision detection. Get a clip from the screen.
        var clipWidth = 20;
        var clipDepth = 20;
        var clipLength = clipWidth * clipDepth;
        // alert(clipLength);
        var clipOffset = 5;
        var whatColor = ctx.getImageData(shipX + clipOffset, shipY + clipOffset, clipWidth, clipDepth);

        // Loop through the clip and see if you find red or blue. 
        for (var i = 0; i < clipLength * 4; i += 4) {
          if (whatColor.data[i] == 255) {
            alert("red");
            break;
          }
          // Second element is green but we don't care. 
          if (whatColor.data[i + 2] == 255) {
            alert("blue");
            break;
          }
          // Fourth element is alpha and we don't care. 
        }
      }

      function drawAsteroids() {

        // Draw asteroids.
        for (i = 0; i <= 20; i++) {
          // Get random positions for asteroids.
          var a = Math.floor(Math.random() * 299);
          var b = Math.floor(Math.random() * 299);

          // Make the asteroids red
          ctx.fillStyle = "#FF0000";

          // Keep the asteroids far enough away from
          // the beginning or end.
          if (a > 40 && b > 40 && a < 270 && b < 270) {

            // Draw an individual asteroid.
            ctx.beginPath();
            ctx.arc(a, b, 10, 0, Math.PI * 2, true);
            ctx.closePath();
            ctx.fill();
          } else--i;
        }

        // Draw blue base.
        ctx.fillStyle = "#0000FF";
        ctx.beginPath();
        ctx.rect(270, 270, 30, 30);
        ctx.closePath();
        ctx.fill();
      }
    </script>
  </head>
  
  <body onload="canvasSpaceGame()">
    <h1>
      Canvas Space Game
    </h1>
    <canvas id="myCanvas" width="300" height="300">
    </canvas>
  </body>

</html>

Canvas Code Sample Discussion

Note  The code in this sample builds upon the code in the previous sample.

 

This section explains the design and structure of this code sample. It will tell you about the different parts and how they fit together. The Canvas sample uses a standard HTML5 header, <!doctype html>, so that browsers can distinguish it as part of the HTML5 specification.

This code is divided into two major parts:

  • Body Code
  • Script Code

Body Code

The body code is the same as in the Moving Your Vehicle topic.

Script Code

The script code contains several blocks of code from the Moving Your Vehicle topic. However, it modifies almost every block and adds several new ones. The script code of this sample consists of the following:

  • canvasSpaceGame function
  • stars function
  • makeShip function
  • whatKey function
  • collideTest function
  • drawAsteroids function

Global variables are called when the page loads. The canvasSpaceGame function is called from the body tag onload attribute. The rest of the functions are called from the canvasSpaceGame function.

canvasSpaceGame Function

This is nearly the same as the the second task of this scenario, "Moving your vehicle." The only modification is to add a call to the drawAsteroids function, which draws the asteroids and the home base on the screen.

stars Function

This is almost the same as the stars function in the second task of this scenario, "Moving your vehicle." The only difference is that the star color is given a different color value. In the stars function from the second task of this scenario, "Moving your vehicle,, the star color was “white”. (The four-byte value is 100% red, blue, green, and alpha, or “#FFFFFF”.) But in this task, the stars are given the color value of “#EEEEEE”, which is almost white (the four byte values are 94% red, green, blue, and alpha). This looks the same as white because it displays only a hint of color. It is necessary to display a hint of color when scanning the screen for collision detection, because using pure white causes problems. When you are scanning for collisions and looking for an object that is 100% red, you might get a false positive for a white star, because a “white” star has 100% red in it.

Working with precise percentages of red, green, blue, and alpha will allow you to do very accurate and subtle collision detection. This is very important because almost all arcade games require collision detection in their game play. Another benefit of using precise colors is you can keep the snapshot size small and perform quick scans for color values, making for even faster collision detection.

makeShip Function

This is the same as the makeShip function in the second task of this scenario, "Moving your vehicle." The only difference is that a 30x30 pixel snapshot of the ship is saved to the ship image for later use.

whatKey Function

This function has nearly the same code as the whatKey function in the second task of this scenario, "Moving your vehicle." The only difference is that a call to collideTest is added after all the key events are processed. This is because every time a key is pressed, a collision is possible, so you should see what happened. Until the ship moves, you do not need to waste cycles detecting collisions, because in this game, nothing else moves.

collideTest Function

This new function determines if the ship has collided with an asteroid or the home base. It does this in two steps. The first step is to take a snapshot of the screen at the point where the ship is about to move. This location has already been calculated by the whatKey function. The snapshot data is a simple linear array of four-byte color values, starting at the upper left-hand corner and continuing from left to right in rows, and continuing each row until the lower right-hand corner. The size of the array is 4 (for the 4-byte color value) times the length times the width. For a 20 x 20 snapshot, the size would be 4 x 20 x 20 or 1,600.

The second step is to scan through the snapshot data, four bytes at a time. You are looking to see if a 100% red value (255) is in the first byte. If you find it, an asteroid is present. You are also looking to see if a 100% blue value (255) is in the third byte. If it is, you have landed on the home base. We don’t care about the second and fourth bytes in this example, because you’re not looking for green or alpha. If a 100% red value is in the first byte, an alert box is displayed telling you that you have encountered “red”. Similarly, if a 100% blue value is in the third byte, an alert box tells you that you have hit “blue.” In the next task of this scenario, you learn how hitting red or blue ends the game, but in this task of the scenario, you can continue after you close the alert box.

This simple task of taking a snapshot and scanning the data for specific bytes of color can be very effective for detecting collisions. Large snapshots might not be efficient, but careful design of your art can make it fast and easy to detect objects on the screen. Scanning a 1,600 byte array every time you press the key is very quick.

Canvas Pixel Colors

In order to read or write colored pixels on the screen, each pixel must be assigned a color value. This value consists of four parts: red, green, blue, and alpha. For example, a white pixel is made up of 100% red, 100% green, 100% blue, and 100% alpha. (Alpha refers to the amount of transparency a color displays.) Each canvas pixel color value is stored as a four-byte array. The four bytes correspond to the percent of red, green, blue, and alpha in each pixel. (Bytes range from 0 to 255 for decimal numbers and from #0 to #FF in hexadecimal.) For more informations about the names and numeric values of specific colors, see https://msdn.microsoft.com/en-us/library/ms531197(VS.85).aspx.

Web designers typically use color names (“red”) or hexadecimal values (“#FF0000”) to define colors. Canvas uses those notation systems but also uses the rgb function to indicate the first three bytes of color, or the rgba function if you need to define the alpha value. If the alpha is not defined, it is assumed to be 100%. For example, red would be rgb(255,0,0). Be aware that the rgb function was used to define colors when you drew the original ship earlier in this topic using the makeShip function.

The code in this sample shows you how Canvas uses immediate mode to read pixel colors to detect collisions between objects. Canvas does this by reading a block of pixels on the screen and determining which parts of the block contain a particular color value. For example, if you want your game program to know if a red asteroid is near your ship, you can take a snapshot of the screen and scan it to determine if it contains red. If it does, your program knows that an asteroid is present. As a result, the program is now prepared to react to the player’s action. If the player bombs the asteroid, it blows up. If the player crashes into the asteroid, the player’s spaceship blows up.

drawAsteroids Function

This is a new function. It is very similar to the stars function in the second task of this scenario, "Moving your vehicle." The drawAsteroids function uses the same logic as the stars function but makes the size of each asteroid larger in the Canvas arc method and makes the fillStyle “#FF0000”, which is pure red. Defining the color ensures that the collision detection routine, when it looks for 255 in the first byte of the color value in the collideTest function, will always find “red”. The drawing loop makes sure that the asteroids are not too close to the starting location of the ship or the home base. If the asteroids are drawn too close to either, the loop does not draw an asteroid, the count is increased, and the loop cycles an extra time.

The drawAsteroids function also draws the blue base. It uses the color value of “#0000FF”, which is pure “blue.” The base is a simple rectangle.

How to Use Canvas to Create a Space Game