Sprite Animations: Vampire Kitty Lives

Tweet

I’ve always loved web games; they’re just fun to make, easy to code (mostly), and there’s something really nice about how accessible a game is when the user just has to click a link to start playing.

Ajax and moving dom elements around made for some fun, but limited in what kind of experience you could create. For game developers, things are changing, and quickly. HTML5 is introducing a bunch of new options for game development purely in the browser, and the browser vendors are competing hard to be the best platform for the new standards.

So from a game developer’s perspective everything is going in the right direction: 2D  and 3D hardware-acceleration, high-performance javascript engines, integrated debuggers and profilers, and, probably most importantly, browser vendors who are actively racing to be the best for serious game development.

So the tools are becoming usable, the browsers capable, and the vendors are listening, we can just go make awesome games right? Well, mostly.

HTML5/Javascript game development is still early, and there’s pitfalls to avoid, as well as choices to make on which technology to deploy.

In this article I’ll run through some of the choices to be made developing 2D games, and hopefully give you some ideas for developing your own games using HTML5.

The Basics

First question you’ll have to answer is whether to use the HTML5 Canvas tag for drawing images (a scene-graph), or by manipulating DOM elements.

To do 2D games using the DOM, you basically adjust element styles dynamically in order to move it around the page. Whilst there are some cases where DOM manipulation is good, I’m going to focus on using the HTML5 canvas for graphics since it’s the most flexible for games in a modern browser.

If you’re worried about compatible for older browsers and canvas check out excanvas (http://excanvas.sourceforge.net/).

Page Setup

To get going you’ll need to create an HTML page that contains the canvas tag:

<!doctype html>
<html>
<head>
  <title></title>
</head>
<body style='position: absolute; padding:0; margin:0; height: 100%; width:100%'>

<canvas id="gameCanvas"></canvas>

</body>
</html>

If you load this up, you’ll be rewarded with, well, nothing much. That’s because whilst we have a canvas tag, we haven’t drawn anything on it. Let’s add some simple canvas calls to draw some boxes.

<head>
  <title></title>
  <script type='text/javascript'>
    var canvas = null;
    function onload() {
      canvas = document.getElementById('gameCanvas');
      var ctx = canvas.getContext("2d");
      ctx.fillStyle = '#000000';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.fillStyle = '#333333';
      ctx.fillRect(canvas.width / 3, canvas.height / 3,
                   canvas.width / 3, canvas.height / 3);
    }
  </script>
</head>
<body onload='onload()' ...

In this example I’ve added an onload event binding to the body tag, and then implemented the function to grab the canvas element and draw some boxes. Simple enough so far.

The boxes are nice, but you’ll notice the canvas doesn’t take up the complete area of the browser window. To accommodate that we can set it’s size by adding a width and height style to the canvas tag. I prefer to keep things dynamic by adjusting the size based on the size of the document element the canvas is contained within.

var canvas = null;
function onload() {
  canvas = document.getElementById('gameCanvas');
  canvas.width = canvas.parentNode.clientWidth;
  canvas.height = canvas.parentNode.clientHeight;
  ...

Reload and you’ll see the canvas taking up the entire screen. Sweet.

Taking things a little further, let’s handle resizing of the canvas if the browser window is resized by the user.

var canvas = null;
function onload() {
  canvas = document.getElementById('gameCanvas');
  resize();
}
function resize() {
  canvas.width = canvas.parentNode.clientWidth;
  canvas.height = canvas.parentNode.clientHeight;
  var ctx = canvas.getContext("2d");
  ctx.fillStyle = '#000000';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = '#333333';
  ctx.fillRect(canvas.width/3, canvas.height/3, canvas.width/3, canvas.height/3);
}

And add the onresize call to the body tag.

  <body onresize='resize()' ...

Now if you resize the browser the rectangles will follow along nicely.

Loading Graphics

Most games are going to need animated sprites, so let’s add some graphics.

First up you’ll need get to an image resource. Since we’re going to be drawing it from within javascript, I find it makes sense to declare the image there and then set its src attribute to be the url of the image you want to load. Please download this image file, which is adapted from SpriteLib GPL: simba.png

var img = null;
function onload() {
    ...
    img = new Image();
    img.src = 'simba.png';
}

You can then draw the image by adding this to the resize method:

  ctx.drawImage(img, canvas.width/2 - (img.width/2), canvas.height/2 - (img.height/2));

If you then reload the page, in most cases, you’ll see an image appear. I say most cases, because it depends on how fast your machine is, and whether the browser has cached the image already. That’s because the resize method is being called in between when you’ve started loading the image (setting its src attribute) and when the browser has it ready to go. With one or two images you might get away with it, but as soon as your game expands you’ll need to wait till all the images are loaded before taking action. To wait, add a notification listener to the image so you’ll get a callback when the image is ready. I’ve had to rearrange things a little to make it all work, so here’s the complete updated code:

var canvas = null;
var img = null;
var ctx = null;
var imageReady = false;
function onload() {
  canvas = document.getElementById('gameCanvas');
  ctx = canvas.getContext("2d");
  img = new Image();
  img.src = 'images/simba.png';
  img.onload = loaded();
  resize();
}
function loaded() {
  imageReady = true;
  redraw();
}
function resize() {
  canvas.width = canvas.parentNode.clientWidth;
  canvas.height = canvas.parentNode.clientHeight;
  redraw();
}
function redraw() {
  ctx.fillStyle = '#000000';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  if (imageReady)
      ctx.drawImage(img, canvas.width/2 - (img.width/2),
                         canvas.height/2 - (img.height/2));
}

And the results should be:

This image shows 6 running frames of a little vampire kitty (well, that’s what I think it looks like). To animate the sprite we need to draw each of the frames one at a time.

Sprite Animation

You can draw a single frame using the source parameters of the drawImage call. In effect, only drawing a constrained portion of the source image. So to draw only the first frame use the expanded version of drawImage that let’s you specify a rectangle in the source image. Since our cat animation is made up from 6 frames each 96 x 96 pixels in size, we can do:

ctx.drawImage(img, 0, 0, 96, 54, canvas.width/2 - 48, canvas.height/2 - 48, 96, 54);

The key thing here is the starting 0, 0, 96, 54. That limits the image being drawn to just the first frame of our cat animation. I’ve also adjust the centering to be based on a single frame as well (the 48s) rather than the entire image size containing all six frames.

Now the fun bit. To make the animation work we need to track which frame to draw, then as time progresses advance the frame number. To do this we’ll need to go from a static page to one that is cycling on a timed basis.

Let’s start by doing things the old fashioned way. Add an interval timer with a cycle time equivalent to 60 frames per second (1000ms divided by 60). To make sure we only start cycling the animation after the image has loaded, put the call in the loaded function:

function loaded() {
    imageReady = true;
    setTimeout( update, 1000 / 60 );
}

Adding an update function can then step forward the frame, and call for the redraw:

var frame = 0;

function update() {
    redraw();
    frame++;
    if (frame >= 6) frame = 0;
    setTimeout( update, 1000 / 60 );
}

After the draw and frame has been advance the timeout is set again.

Next, modify the draw image to move the source window according to which frame we want to draw (the key piece being the source X position being set to frame multiplied by the size of the frame (in this case frame * 96):

function redraw() {
    ctx.fillStyle = '#000000';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    if (imageReady)
        ctx.drawImage(img, frame*96, 0, 96, 54,
                      canvas.width/2 - 48, canvas.height/2 - 48, 96, 54);
}

And the result:

Our evil undead-vampire-kitty lives! At super-cat speeds even.

Now we have our animation going, we’ll make some improvements in the second part of this article, the day after tomorrow.

This article originally appeared on BuildNewGames.com, a collaboration by the teams at Bocoup and Internet Explorer.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://bricklandblog.blogspot.de/ 1UnitedPower

    A view suggestions:
    It’s not the canvas-tag, but the canvas-element that brings the magic.

    Don’t use setTimeout or setInterval. Thats really bad practice for game-development. You should use requestAnimationFrame instead.

    You should’nt bind your event-listeners through html-attributes for the same reasons you should not use eval. Furthermore i’d recommend to prefer addEventListener(“load”[...]); over document.onload.

    Since your code needs to run after the DOM is loaded you should use “DOMContentLoaded”-event instead of “load”-event. The load event is triggered after all resources have been loaded, but we don’t need to take care of all stylesheets and images beeing downloaded completly.

    Furthermore you should check your code once more. I don’t think “img.onload = loaded();” will behave in the way you expected. Imho. this article needs a complete revision.

  • http://www.xoogu.com/ Dave

    Any reason why you write
    var canvas = null;
    var img = null;
    var ctx = null;

    instead of
    var canvas, img, ctx;

    Just personal preference?