Cover by Jeff Fulton, Steve Fulton

Safari, the world’s most comprehensive technology and business learning platform.

Find the exact information you need to solve a problem on the fly, or go deeper to master the technologies and skills you need to succeed

Start Free Trial

No credit card required

O'Reilly logo

Creating a Dynamic Tile Sheet at Runtime

In Chapter 4, we briefly examined two principles we can use to help eliminate the need to precreate rotations of objects in tile sheets. Creating these types of tile sheets can be cumbersome and use up valuable time that’s better spent elsewhere in the project.

The idea will be to take a single image of a game object (e.g., the first tile in the medium rock tile sheet), create a “dynamic tile sheet” at runtime, and store it in an array rather than using the prerendered image rotation tiles.

To accomplish this, we need to make use of a second canvas, as well as the getImageData() and putImageData() Canvas functions. If you recall from Chapter 4, getImageData() will throw a security error if the HTML page using it is not on a web server.

Currently, only the Safari browser will not throw this error if the file is used on a local filesystem. For this reason, we have separated this functionality from the Geo Blaster Extended game and will simply demonstrate how it could be used instead of replacing all the tile sheets in the game with this type of prerendering.

We will start by creating two <canvas> elements on our HTML page:

<body>
<div>
<canvas id="canvas" width="256" height="256" style="position: absolute; top: 
50px; left: 50px;">
 Your browser does not support HTML5 Canvas.
</canvas>

<canvas id="canvas2" width="32" height="32"  style="position: absolute; top: 
 256px; left: 50px;">
 Your browser does not support HTML5 Canvas.
</canvas>
</div>
</body>

The first <canvas>, named canvas, will represent our hypothetical game screen, which will be used to display the precached dynamic tile sheet animation.

The second <canvas>, named canvas2, will be used as a drawing surface to create the dynamic tile frames for our tile sheet.

We will need to separate context instances in the JavaScript, one for each <canvas>:

var theCanvas = document.getElementById("canvas");
var context = theCanvas.getContext("2d");
var theCanvas2 = document.getElementById("canvas2");
var context2= theCanvas2.getContext("2d");

We will use the mediumrocks.png file (Figure 9-9) from the Geo Blaster Extended game as our source for the dynamic tile sheet. Don’t let this confuse you. We are not going to use all five tiles on this tile sheet—only the first tile.

The mediumrocks.png tile sheet

Figure 9-9. The mediumrocks.png tile sheet

In Geo Blaster Extended, we used all five tiles to create a simulated rotation animation. Here, we will only use the first tile. We will draw this first tile and rotate it on theCanvas2 by 10 degrees, and then copy the current imageData pixels from this canvas to an array of imageData instances, called rotationImageArray.

We will then repeat this process by rotating theCanvas2 by 10 more degrees and in a loop until we have 36 individual frames of imageData representing the rotation animation for our medium rock in an array:

var rotationImageArray = [];
var animationFrame = 0;
var tileSheet = new Image();
tileSheet.addEventListener('load', eventSheetLoaded , false);
tileSheet.src = "mediumrocks.png";

The rotationImageArray variable will hold the generated imageData instances, which we will create by using a rotation transformation on theCanvas2.

The animationFrame is used when redisplaying the rotation animation frames in rotationImageArray back to the first theCanvas to demo the animation.

When the tileSheet is loaded, the eventSheetLoaded() function is called, which in turn calls the startup() function. The startup() function will use a loop to create the 36 frames of animation:

function startUp(){

   for (var ctr=0;ctr<360;ctr+=10){
     context2.fillStyle = "#ffffff";
     context2.fillRect(0,0,32,32);
     context2.save();
     context2.setTransform(1,0,0,1,0,0)
     var angleInRadians = ctr * Math.PI / 180;
     context2.translate(16, 16);
     context2.rotate(angleInRadians);
     context2.drawImage(tileSheet, 0, 0,32,32,-16,-16,32,32);
     context2.restore();
     var imagedata = context2.getImageData(0, 0, 32, 32)
     rotationImageArray.push(imagedata);
   }
   setInterval(drawScreen, 100 );
}

This loop first clears theCanvas2 with a white color, and then saves it to the stack. We then translate to the center of our object and rotate the canvas by the current ctr value (an increment of 10). Next, we draw the first tile from mediumrocks.png and save the result into a new local imageData instance using the getImageData() function.

Note

This is the place where the security error will be thrown if the domain of the image and the domain of the HTML file are not the same. On a local machine (not running on a local web server, but from the filesystem), this error will be thrown on all browsers but Safari (currently).

Finally, the new imageData is pushed into the rotationImageArray. When the loop is complete, we set up an interval to run and call the drawScreen() function every 100 milliseconds.

To display the animation on the first canvas, we use this timer loop interval and call putImageData() to draw each frame in succession, creating the simulation of animation. As with the tile sheet, we didn’t have to use 36 frames of animation, we could use just five. Naturally, the animation is much smoother with more frames. But this process shows how easy it is to create simple transformation animations “on the fly” rather than precreating them in image files:

function drawScreen() {

      //context.fillStyle = "#ffffff";
      //context.fillRect(50,50,32,32);
      context.putImageData(rotationImageArray[animationFrame],50,50);
      animationFrame++;
      if (animationFrame ==rotationImageArray.length){
         animationFrame=0;
      }
}

Example 9-2 shows the entire code.

Example 9-2. A dynamic tile sheet example

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH9EX2: Canvas Copy</title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded() {
   canvasApp();
}

function canvasSupport () {
    return Modernizr.canvas;
}

function canvasApp(){

   if (!canvasSupport()) {
          return;     }else{
      var theCanvas = document.getElementById("canvas");
      var context = theCanvas.getContext("2d");

      var theCanvas2 = document.getElementById("canvas2");
      var context2= theCanvas2.getContext("2d");
   }
   var rotationImageArray = []
   var tileSheet = new Image();
   var animationFrame = 0;
   tileSheet.addEventListener('load', eventSheetLoaded , false);
   tileSheet.src = "mediumrocks.png";
   function eventSheetLoaded() {
      startUp();
   }

   function startUp(){
      //context.drawImage(tileSheet, 0, 0);
      //context2.drawImage(theCanvas, 0, 0,32,32,0,0,32,32);

      for (var ctr=0;ctr<360;ctr+=10){
         context2.fillStyle="#ffffff";
         context2.fillRect(0,0,32,32);

         context2.save();
         context2.setTransform(1,0,0,1,0,0)
         var angleInRadians = ctr * Math.PI / 180;
         context2.translate(16, 16)
         context2.rotate(angleInRadians);
         context2.drawImage(tileSheet, 0, 0,32,32,-16,-16,32,32);
         context2.restore();

         var imagedata = context2.getImageData(0, 0, 32, 32);

         rotationImageArray.push(imagedata);
      }
      setInterval(drawScreen, 100 );
   }

   function drawScreen() {
      //context.fillStyle="#ffffff";
      //context.fillRect(50,50,32,32);
      context.putImageData(rotationImageArray[animationFrame],50,50);
      animationFrame++;
      if (animationFrame ==rotationImageArray.length){
         animationFrame = 0;
      }
   }

}

</script>
</head>
<body>
<div>
<canvas id="canvas" width="256" height="256" style="position: absolute; top: 
 50px; left: 50px;">
 Your browser does not support the HTML 5 Canvas.
</canvas>

<canvas id="canvas2" width="32" height="32" style="position: absolute; top: 
 256px; left: 50px;">
 Your browser does not support HTML5 Canvas.
</canvas>

</div>
</body>
</html>

In the rest of the chapter, we will look at creating a simple tile-based game using some of the techniques first discussed in Chapter 4.

Find the exact information you need to solve a problem on the fly, or go deeper to master the technologies and skills you need to succeed

Start Free Trial

No credit card required