Using canvas to do bitmap sprite animation in JavaScript

Using canvas to do bitmap sprite animation in JavaScript

August 21, 2009 – 7:02 pm Tags: , ,

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:

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.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值