O'Reilly logo

HTML5 Canvas by Jeff Fulton, Steve Fulton

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Advanced Cell-Based Animation

In the previous example, we simply flipped back and forth between two tiles on our tile sheet. Next, we are going to create a method that uses a tile sheet to play through a series of images. First, let’s look at the new tile sheet, created by using tiles from SpriteLib. Figure 4-7 shows the example sprite sheet, tanks_sheet.png; we will refer back to this figure throughout the chapter.

Example tile sheet

Figure 4-7. Example tile sheet

As you can see, it contains a number of 32×32 tiles that can be used in a game. We will not create an entire game in this chapter, but we will examine how to use these tiles to create a game screen. In Chapter 9, we will create a simple maze-chase game using some of these tiles.

Examining the Tile Sheet

The tile sheet is formatted into a series of tiles starting at the top left. As with a two-dimensional array, the numbering starts at 0—we call this 0 relative. Moving from left to right and down, each tile will be referenced by a single number index (as opposed to a multidimensional index). The gray square in the top left is tile 0, while the tank at the end of the first row (the rightmost tank) is tile 7. Moving down to the next row, the first tank on the far left of the second row is tile 8, and so on until the final tile on row 3 (the fourth row down when we start numbering at 0) is tile 31. We have four rows with eight columns each, making 32 tiles with indexes numbered 0 to 31.

Creating an Animation Array

Next, we are going to create an array to hold the tiles for the animation. There are two tanks on the tile sheet: one is green and one is blue. Tiles 1‒8 are a series that—when played in succession—will make it appear as though the green tank’s tracks are moving.

Note

Remember, the tile sheet starts at tile 0, but we want start with the first tank image at tile number 1.

We will store the tile ids we want to play for the tank in an array:

var animationFrames = [1,2,3,4,5,6,7,8];

We will use a counter to keep track of the current index of this array:

var frameIndex = 0;

Choosing the Tile to Display

We will use the frameIndex of the animationFrames array to calculate the 32×32 source rectangle from our tile sheet that we will copy to the canvas. First, we need to find the x and y locations of the top-left corner for the tile we want to copy. To do this, we will create local variables in our drawScreen() function on each iteration (frame) to calculate the position on the tile sheet. The sourceX variable will contain the top-left corner x position, and the sourceY variable will contain the top-left corner y position.

Here is pseudocode for the sourceX calculation:

sourceX = integer(current_frame_index modulo 
the_number_columns_in_the_tilesheet) * tile_width

The modulo (%) operator gives us the remainder of the division calculation. The actual code we will use for this calculation looks like this:

var sourceX = Math.floor(animationFrames[frameIndex] % 8) *32;

The calculation for the sourceY value is similar, except we divide rather than use the modulo operation:

sourceY = integer(current_frame_index divided by 
the_number_columns_in_the_tilesheet) *tile_height

Here is the actual code we will use for this calculation:

var sourceY = Math.floor(animationFrames[frameIndex] / 8) *32;

Looping Through the Tiles

We will update the frameIndex value on each frame tick. When frameIndex becomes greater than 7, we will set it back to 0:

frameIndex++;
     if (frameIndex == animationFrames.length) {
     frameIndex = 0;
     }

The animationFrames.length value is 8. When the frameIndex is equal to 8, we must set it back to 0 to start reading the array values over again, which creates an infinite animation loop.

Drawing the Tile

We will use drawImage() to place the new tile on the screen on each iteration:

context.drawImage(tileSheet, sourceX, sourceY,32,32,50,50,32,32);

Here, we are passing the calculated sourceX and sourceY values into the drawImage() function. We then pass in the width (32), the height (32), and the location (50,50) to draw the image on the canvas. Example 4-5 shows the full code.

Example 4-5. Advanced sprite animation

var tileSheet = new Image();
tileSheet.addEventListener('load', eventShipLoaded , false);

tileSheet.src = "tanks_sheet.png";

var animationFrames = [1,2,3,4,5,6,7,8];
var frameIndex = 0;

function eventShipLoaded() {
  startUp();
}

function drawScreen() {

  //draw a background so we can see the Canvas edges
  context.fillStyle = "#aaaaaa";
  context.fillRect(0,0,500,500);

  var sourceX = Math.floor(animationFrames[frameIndex] % 8) *32;
  var sourceY = Math.floor(animationFrames[frameIndex] / 8) *32;

  context.drawImage(tileSheet, sourceX, sourceY,32,32,50,50,32,32);

  frameIndex++;
  if (frameIndex ==animationFrames.length) {
     frameIndex=0;
  }

}

function startUp(){

  setInterval(drawScreen, 100 );
}

When we run the example, we will see the eight tile cell frames for the tank run in order and then repeat—the only problem is that the tank isn’t going anywhere. Let’s solve that little dilemma next and drive the tank up the screen.

Moving the Image Across the Canvas

Now that we have the tank tracks animating, let’s “move” the tank. By animating the tank tracks and applying a simple movement vector to the tank’s position, we can achieve the simulation of animated movement.

To do this, we first need to create variables to hold the current x and y positions of the tank. These represent the top-left corner where the tile from our sheet will be drawn to the canvas. In the previous examples, this number was set at 50 for each, so let’s use that value here as well:

var x = 50;
var y = 50;

We also need a movement vector value for each axis. These are commonly known as deltaX (dx) and deltaY (dy). They represent the “delta” or “change” in the x or y axis position on each iteration. Our tank is currently facing in the “up” position, so we will use -1 for the dy and 0 for the dx:

var dx = 0;
var dy = -1;

The result is that on each frame tick, our tank will move one pixel up on the y-axis and zero pixels on the x-axis.

Inside drawScreen() (which is called on each frame tick), we will add the dx and dy values to the x and y values, and then apply them to the drawImage() function:

y = y+dy;
x = x+dx;
context.drawImage(tileSheet, sourceX, sourceY,32,32,x,y,32,32);

Rather than use the hardcoded 50,50 for the location of the drawImage() call on the canvas, we have replaced it with the current x,y position. Let’s examine the entire code in Example 4-6.

Example 4-6. Sprite animation and movement

var tileSheet = new Image();
tileSheet.addEventListener('load', eventShipLoaded , false);
tileSheet.src = "tanks_sheet.png";

var animationFrames = [1,2,3,4,5,6,7,8];
var frameIndex = 0;
var dx = 0;
var dy = -1;
var x = 50;
var y = 50;

function eventShipLoaded() {
  startUp();
}

function drawScreen() {

   y = y+dy;
   x = x+dx;

  //draw a background so we can see the Canvas edges
  context.fillStyle = "#aaaaaa";
  context.fillRect(0,0,500,500);

  var sourceX = Math.floor(animationFrames[frameIndex] % 8) *32;
  var sourceY = Math.floor(animationFrames[frameIndex] / 8) *32;

  context.drawImage(tileSheet, sourceX, sourceY,32,32,x,y,32,32);

  frameIndex++;
  if (frameIndex==animationFrames.length) {
     frameIndex=0;
  }

}

function startUp(){

  setInterval(drawScreen, 100 );
}

By running this example, we see the tank move slowly up the canvas while its tracks play through the eight separate tiles of animation.

Our tile sheet only has images of the tank facing in the up position. If we want to have the tank move in other directions, we can do one of two things. The first option is to create more tiles on the tile sheet to represent the left, right, and down positions. However, this method requires much more work and creates a larger source image for the tile sheet. We are going to solve this problem in another way, which we will examine next.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required