Animated Sprites in Canvas

Animating Canvas Sprites

It's not difficult to convert the sprites we're using to animated sprites. All we have to do is make sure our tileset has several frames for the sprites we want to animate, and make some small changes to our code. To begin with, we'll add support for animated map tiles, and test this by (badly) animating the water sprite.

View example
tileset

We're going to modify the tileTypes array, but just the water entry. Our new water code will look like this:


	4 : { colour:"#678fd9", floor:floorTypes.water,	sprite:[
			{x:160,y:0,w:40,h:40,d:200}, {x:200,y:0,w:40,h:40,d:200},
			{x:160,y:40,w:40,h:40,d:200}, {x:200,y:40,w:40,h:40,d:200},
			{x:160,y:40,w:40,h:40,d:200}, {x:200,y:0,w:40,h:40,d:200}
	]}

In our sprite property we've added 5 more sets of information, whos x,y properties correspond to our new water frames positions on our tilesheets, and w,h properties correspond to the tileW, tileH values we have set.

Each entry also has a new d property, which we use to set the duration that frame will be displayed for in milliseconds before the next frame is drawn.

Processing sprite data

An additional bit of processing in the windows onload function will be added to make the tileTypes sprite property even more useful for our code. First, loop over the tileTypes array:


	for(x in tileTypes)
	{

We'll check the sprite property of each, and if we've provided more than one frame worth of data we'll flag the sprite as animated; otherwise we'll flag it as not animated:


		tileTypes[x]['animated'] = tileTypes[x].sprite.length > 1 ? true : false;

Now, if this sprite is animated, we have more work to do. Firstly, we need to create a temporary variable t, which will be a time counter for the duration of each frame:


		if(tileTypes[x].animated)
		{
			var t = 0;

We'll also loop over every frame we have data for in the sprite array.


			for(s in tileTypes[x].sprite)
			{

For each frame, we'll set the start time of the sprite to the current accumulated value of the t variable:


				tileTypes[x].sprite[s]['start'] = t;

We can now add the duration of this frame (d) to the value of t, and set this accumulated value as the end property of this frame:


				t+= tileTypes[x].sprite[s].d;
				tileTypes[x].sprite[s]['end'] = t;

We're done with editing information for the frames of this tileTypes sprite, so we'll close the frame loop, after which we'll set a spriteDuration property for the tileType we're currently processing as the accumulated final value of the t variable, and then close the processing loops.


			}

			tileTypes[x]['spriteDuration'] = t;
		}
	}

A helper function for frame finding

Now that we have all this information for the frames of our sprite, we can create a helper function to find the current frame data of a sprite. our new getFrame function will take 4 arguments; the sprite, which is an array of frames, the duration, which is the spriteDuration property, ie: the complete duration of the sprite if all frames are counted, the time, which is the current game time in milliseconds, and animated, a boolean flag which states whether or not the sprite is animated.


function getFrame(sprite, duration, time, animated)
{

First, we'll check the animated flag to see if we have multiple frames. If not, we just return the first frame (index 0):


	if(!animated) { return sprite[0]; }

Next, we perform a modulus operation on the time argument; the value of time is divided by the total sprite duration, and the time variable is updated to the remainder of this sum. This allows sprites to "loop".


	time = time % duration;

Finally, we loop over the frames of the sprite. If the end time of the sprite occurs at or after the time value we're looking for for the current frame, we return this frame. This will find us the correct frame, so we can then close this function.


	for(x in sprite)
	{
		if(sprite[x].end>=time) { return sprite[x]; }
	}
}

Updating rendering

Finally, as we're only adding full support for animated tile sprites in this example, we only need to modify the sprite drawing code inside the y,x nested loops in the drawGame function for tile rendering.

After we have assigned the current x,y tileType to our temporary tile variable inside these loops, we can then create a temporary sprite variable, and use our getFrame helper function to find the current frame:


			var sprite = getFrame(tile.sprite, tile.spriteDuration,
				currentFrameTime, tile.animated);

We're passing the method the sprite, duration, and animated arguments from the tile properties, and the time argument from the current game time (currentFrameTime).

The method returns us the appropriate set of x,y, w,h values from the sprite array, which we can then use the render to the canvas with minor changes to the drawImage code:


			ctx.drawImage(tileset,
				sprite.x, sprite.y, sprite.w, sprite.h,
				viewport.offset[0] + (x*tileW), viewport.offset[1] + (y*tileH),
				tileW, tileH);

If you now load up your HTML document you should have a (terrible) animation effect on the maps water sprites!

Page loaded in 0.011 second(s).