Have you ever thought about writing a game? If you have, you’ve probably wondered how to render animations for your game characters. In this post, I’ll show you how you can use JavaScript to do time-based sprite animations, drawing them on canvas – vital if you want to do a game. You can also apply the same techniques I’ll show in other languages, such as ActionScript.
What is “time-based” animation?
We all know what animation means, but what does time-based add?
Let’s imagine we have a game. Our game runs at 60 frames per second (fps) on a powerful PC, and 20 fps on a slow PC.
Animation that isn’t time-based is frame-based. In this case, a frame means a “cycle” in the game: In each frame you typically calculate things like movement or AI, and draw things on the screen.
Let’s say we have a walk animation which changes every frame. This means that on the fast PC the animation is much faster than on the slow PC, since the framerate is much higher.
Time-based animation calculates how long each frame in your game takes, and uses this to calculate how long an animation should take. By using time instead of frames, we can achieve exactly the same speed on both the fast and slow PCs.
There is also a term “time-based movement”. This applies the same principle to movement of characters and other things on the screen so that everything moves at the same speed no matter what frame rate.
The infrastructure
There is some infrastructure we need before we can actually start animating things.
- We need a timer which can calculate how long each frame takes
- We need a sprite sheet object, which we can use to keep track of different sprites in a big bitmap
- Lastly, we need an animation object which handles choosing the correct sprite from a spritesheet and such.
In addition to these, in a game you would also have things like animation and spritesheet loaders that load spritesheet and animation definitions from files so you won’t have to modify your code to change an animation. However, these are outside the scope of this post – if you’d like to see a post about loading this kind of things from files, leave a comment.
First, let’s look at making the class which will take care of timing the frames.
Creating a frame timer
The basic idea of a frame timer is that it should be called once every frame, usually after the processing is complete. At that point, it compares how much time has passed since the previous call. We can then use this value on the next frame to make a good guess of how long it’ll take this time.
To determine the time on each frame, we’ll use the Date object.
var FrameTimer = function() { this._lastTick = (new Date()).getTime(); }; FrameTimer.prototype = { getSeconds: function() { var seconds = this._frameSpacing / 1000; if(isNaN(seconds)) { return 0; } return seconds; }, tick: function() { var currentTick = (new Date()).getTime(); this._frameSpacing = currentTick - this._lastTick; this._lastTick = currentTick; } }; |
This simple looking class simply tracks time spent during two “ticks”. With this we track the actual time spent between two frames, so that we can keep the animation’s speed constant in real time instead of application speed.
Using this object is simple:
var timer = new FrameTimer(); //Tick once to initialize the time timer.tick(); //Now do processing while(true) { //do things based on time work(timer.getSeconds()); timer.tick(); } |
Sprite sheet and animation objects
To make our life a bit easier in the code, let’s abstract the code which calculates the exact location of a specific sprite on a spritesheet. Let’s also write a class which abstracts the animation and handling which frame should be displayed.
Since we’ll need the spritesheet class for the animation class, let’s look at that one first.
The idea is that we can create a spritesheet object that helps us in drawing sprites from a big sheet. As you know, to draw a specific sprite from a sheet, we need to know the location of it on the sheet – locating a specific sprite will be the main job of the spritesheet object, so that we won’t have to think of it all the time.
var SpriteSheet = function(data) { this.load(data); }; SpriteSheet.prototype = { _sprites: [], _width: 0, _height: 0, load: function(data) { this._height = data.height; this._width = data.width; this._sprites = data.sprites; }, getOffset: function(spriteName) { //Go through all sprites to find the required one for(var i = 0, len = this._sprites.length; i < len; i++) { var sprite = this._sprites[i]; if(sprite.name == spriteName) { //To get the offset, multiply by sprite width //Sprite-specific x and y offset is then added into it. return { x: (i * this._width) + (sprite.x||0), y: (sprite.y||0), width: this._width, height: this._height }; } } return null; } }; |
The basic usage of this class is you create a data-structure which contains the spritesheet definition, and pass it to the object, such as this:
var sprites = new SpriteSheet({ width: 32, height: 32, sprites: [ { name: 'stand' }, { name: 'walk_1', x: 0, y: 1 }, { name: 'walk_2', x: 0, y: 1 }, ] }); |
The definition is simple: First, we define the width and height of a sprite, and then we define a name and x/y offsets for each sprite in the sheet.
When we apply this with an image which has three sprites, each 32×32 in size, we can simply tell the spritesheet that we want the sprite called ‘walk_1’, and we get a nice object with the X and Y positions to draw it from the sheet. We’ll see this in action soon!
The animation class
Now that we have the spritesheet stuff done, we can do the last in the line: The animation class.
The animation class takes a definition, similar to how the spritesheet took, and then handles various things based on that. For example, as each animation comprises of specific frames, it calculates how long a frame has been visible, and changes to the next frame when it has been visible long enough.
On with the code:
var Animation = function(data, sprites) { this.load(data); this._sprites = sprites; }; Animation.prototype = { _frames: [], _frame: null, _frameDuration: 0, load: function(data) { this._frames = data; //Initialize the first frame this._frameIndex = 0; this._frameDuration = data[0].time; }, animate: function(deltaTime) { //Reduce time passed from the duration to show a frame this._frameDuration -= deltaTime; //When the display duration has passed if(this._frameDuration <= 0) { //Change to next frame, or the first if ran out of frames this._frameIndex++; if(this._frameIndex == this._frames.length) { this._frameIndex = 0; } //Change duration to duration of new frame this._frameDuration = this._frames[this._frameIndex].time; } }, getSprite: function() { //Return the sprite for the current frame return this._sprites.getOffset(this._frames[this._frameIndex].sprite); } } |
The comments should explain most of this class. The main point is that with this, we are done!
Now we only need to look at the animation definition format, and then we can combine these three into the most amazing animation machine the internet has ever seen… or something like that anyway.
var walk = new Animation([ { sprite: 'walk_1', time: 0.2 }, { sprite: 'stand', time: 0.2 }, { sprite: 'walk_2', time: 0.2 }, { sprite: 'stand', time: 0.2 } ], sprites); |
Here we define a walk animation. The animation is defined by simply giving the object an array, which contains objects for each frame, defining the name of the sprite and the duration of the frame. The variable sprites in the end is the spritesheet we defined in the previous example.
Putting it all together
Now, let’s put all this together!
I have a spritesheet lying around from some old projects: kunio.gif. Let’s animate this.
You may be familiar with this character – He is Kunio from an old NES game known as Downtown Nekketsu Monogatari, released in english as River City Ransom, and he’s been featured on various flash-animations as well.
I’ve used this sprite in a few other projects:
- In this Flex-based game test (use arrow keys to move, space to punch, doubletap arrows to run)
- In my Palm Pre Accelerometer demo application, which actually uses this same code shown here.
But enough of that, let’s write a quick HTML page to put our code to work:
<!DOCTYPE html> <html> <head> <title>Canvas Spritesheet Animation</title> <script type="text/javascript" src="js/FrameTimer.js"></script> <script type="text/javascript" src="js/SpriteSheet.js"></script> <script type="text/javascript" src="js/Animation.js"></script> <script type="text/javascript"> window.onload = function() { var timer = new FrameTimer(); timer.tick(); var sprites = new SpriteSheet({ width: 32, height: 32, sprites: [ { name: 'stand' }, { name: 'walk_1', x: 0, y: 1 }, { name: 'walk_2', x: 0, y: 1 }, ] }); var ctx = document.getElementById('canvas').getContext('2d'); var walk = new Animation([ { sprite: 'walk_1', time: 0.2 }, { sprite: 'stand', time: 0.2 }, { sprite: 'walk_2', time: 0.2 }, { sprite: 'stand', time: 0.2 } ], sprites); var kunioImage = new Image(); kunioImage.onload = function() { setInterval(function(){ walk.animate(timer.getSeconds()); var frame = walk.getSprite(); ctx.clearRect(0, 0, 300, 300); ctx.drawImage(kunioImage, frame.x, frame.y, 32, 32, 0, 0, 32, 32); timer.tick(); }, 5); }; kunioImage.src = 'img/kunio.gif'; }; </script> </head> <body> <h1>Canvas sprite animation demo</h1> <canvas id="canvas" width="300" height="300"></canvas> </body> </html> |
Here it is. Our great animation system!
A live example and source code
You can see this code in action by clicking here
As usual, don’t expect it to work in Internet Explorer. It has been tested to work in Opera 10 and Firefox 3, probably works in other canvas-supporting browsers as well.
Source:
Conclusion
Animating a spritesheet with canvas is quite simple. This is why JavaScript is quickly becoming a powerful platform for small games in my opinion – very easy to use, and quite ubiquitous.
You can use this same approach to animation in other languages too – For example, the Flex example I linked uses a very similar approach.