Frame animation with HTML5 canvas
Recently I’ve been experimenting in HTML5 game development and like my last post, this post is to document my findings.
I’ve been playing with animation, more specifically keyframe animation.
A key frame in animation and filmmaking is a drawing that defines the starting and ending points of any smooth transition. The drawings are called “frames” because their position in time is measured in frames on a strip of film. A sequence of keyframes defines which movement the viewer will see, whereas the position of the keyframes on the film, video or animation defines the timing of the movement. Because only two or three keyframes over the span of a second do not create the illusion of movement, the remaining frames are filled with inbetweens.
In my case I’m looking for a technique called keyframing, where by we define every frame of an animation, in much the same way as an aminated gif.
Firstly we must create our keyframes and there are may ways you could do this. The first is to create seperate files for each frame, however this has the drawback that the browser has to download each image before you can realistically start your animation. A better way would be to create one file with all the frames such as the image below.
This image contains 16x16 keyframes. We could modify the image and reduce the space between each image and recalculate the position of each frame, however I’ll leave that as an advanced exercise for you.
Most implimentations out there use simple math to work out the frame location:
var coin = {x: 10, y: 10};
var coin_image = new Image();
coin_image.src = 'spinning_coin_gold.png';
var canvas = document.getElementById('canvas');
var draw_context = canvas.getContext('2d');
var total_frames = 8;
var frame = 0;
function loop() {
// clear the screen
draw_context.clearRect(0, 0, canvas.width, canvas.height);
// keep the frame number between 0 and total_frames
frame = frame % total_frames;
// draw the frame from coin_image to the canvas, starting frame * 16 across coin_image for 16 pixels
draw_context.drawImage(coin_image, frame * 16, 0, 16, 16, coin.x, coin.y, 16, 16);
frame += 1;
window.webkitRequestAnimationFrame(loop);
}
loop();
The code is fairly simple, we just increase our counter and move that many lots of pixels across the image. However I feel that are a couple of drawbacks with this approach.
- Our code is inflexible and not reusable
- We can’t have our frames on multiple lines of our image (though this could be done with some more math)
- We can’t set the speed of our animation independantly of the loop speed (60fps in most browsers)
Introducing the Animation object
Tackling the points in order, well start by creating an object we can make instances of to deal with all our animations.
function Animation(frame_count) {
this.frame = 0;
this.frameCount = frame_count != undefined ? frame_count : 1;
}
Animation.prototype.frame = 0;
Animation.prototype.frameCount = 0;
Animation.prototype.paused = false;
Animation.prototype.play = function animationPlay() {
this.paused = false;
};
Animation.prototype.pause = function animationStop() {
this.paused = true;
};
Animation.prototype.stop = function animationStop() {
this.paused = true;
this.frame = 0;
};
Animation.prototype.update = function animationUpdate() {
if (this.paused) {
return this.frame;
}
this.frame += 1;
this.frame = this.frame % this.frameCount;
return this.frame;
};
Okay, looking good, now we have an Animation object we can reuse in our original code making our code more reusable and clean:
var coin = {x: 10, y: 10};
var coin_image = new Image();
coin_image.src = 'spinning_coin_gold.png';
var canvas = document.getElementById('canvas');
var draw_context = canvas.getContext('2d');
var coin_animation = new Animation(8);
var frame;
function loop() {
// clear the screen
draw_context.clearRect(0, 0, canvas.width, canvas.height);
frame = coin_animation.update();
// draw the frame from coin_image to the canvas, starting frame * 16 across coin_image for 16 pixels
draw_context.drawImage(coin_image, frame * 16, 0, 16, 16, coin.x, coin.y, 16, 16);
window.requestAnimationFrame(loop); // shim? http://paulirish.com/2011/requestanimationframe-for-smart-animating/
}
loop();
Notice I added 3 convenience methods, play, pause and stop. Now we have an easy way to start and stop our animation independently of the render loop.
Sheets full of sprites
By now in HTML5 games, we’ve all learned that sprite sheets are great for games.
The advantages of using Sprite Sheets?
- Fewer HTTP requests – The Command Center went from 81 requests to a SINGLE HTTP request
- Better Compression – An advantage of storing the images in a single file is that the header information doesn’t repeat and the combined file’s size is much smaller than the sum of the individual files. The command center went from 496KB in 81 files to only 37KB in a single file. (Less than 8% of the original size, which is incredible)
- Easier Manipulation – With all the sprites in a single image file, it became easier to do RGB color manipulations, and I was able to optimize the drawing code for performance.
From almost a 1,000 requests to 120 requests in one simple code rewrite. And the total download size went from a few MBs to around 200KB.
HTML5 Game Development: Using sprite sheets for better performance (and protecting your server)
To solve this, our animation object needs to return the exact coordinates of the image in the sprite sheet. We can achieve this with a simple object detailing the coordinates of each frame.
{
x: 0,
y: 0,
width: 16,
height: 16
}
We’ll want to pass multiple to our animation object, so we should update our Animation to accept an array of frames. It will be assumed that the order in the array is the order the frames are shown in the animation.
function Animation(frames) {
this.frame = 0;
this.frames = frames != undefined ? frames : [];
this.frameCount = this.frames.length;
}
Animation.prototype.frame = 0;
Animation.prototype.frames = null;
Animation.prototype.frameCount = 0;
Animation.prototype.paused = false;
Animation.prototype.play = function animationPlay() {
this.paused = false;
};
Animation.prototype.pause = function animationStop() {
this.paused = true;
};
Animation.prototype.stop = function animationStop() {
this.paused = true;
this.frame = 0;
};
Animation.prototype.update = function animationUpdate() {
if (this.paused) {
return this.frames[this.frame];
}
this.frame += 1;
this.frame = this.frame % this.frameCount;
return this.frames[this.frame];
};
With minimal changes our animation now returns a frame object. But we’ll need to update out implimentation to understand how to use a frame.
var coin = {x: 10, y: 10};
var sprite_sheet = new Image();
sprite_sheet.src = 'sprite_sheet.png';
var canvas = document.getElementById('canvas');
var draw_context = canvas.getContext('2d');
var coin_animation = new Animation([
{ x: 0, y: 0, width: 16, height: 16 },
{ x: 16, y: 0, width: 16, height: 16 },
{ x: 32, y: 0, width: 16, height: 16 },
{ x: 48, y: 0, width: 16, height: 16 },
{ x: 64, y: 0, width: 16, height: 16 },
{ x: 80, y: 0, width: 16, height: 16 },
{ x: 96, y: 0, width: 16, height: 16 },
{ x: 112, y: 0, width: 16, height: 16 }
]);
var frame;
function loop() {
// clear the screen
draw_context.clearRect(0, 0, canvas.width, canvas.height);
frame = coin_animation.update();
// draw the frame from coin_image to the canvas, starting frame * 16 across coin_image for 16 pixels
draw_context.drawImage(sprite_sheet, frame.x, frame.y, frame.width, frame.height, coin.x, coin.y);
window.requestAnimationFrame(loop); // shim? http://paulirish.com/2011/requestanimationframe-for-smart-animating/
}
loop();
Whilst you might think this example is contrived but just think, if there is very little space in your sprite sheet you can place them anywhere and then reference the x and y coordinates.
Too fast, too slow
Woah, our animation is running at 60FPS! Way too fast. No worries, we can add a little more code and have and independent FPS for each animation. Let me explain.
In order to have independent frame rates we increment an internal counter with a supplied frame duration. Once the required duration has ellapsed, we increase our internal frame counter as before.
Now for the code.
function Animation(fps, frames) {
this.frame = 0;
this.frames = frames != undefined ? frames : [];
this.frameCount = this.frames.length;
this.duration = 1000 / fps;
this.elapsedTime = 0;
}
Animation.prototype.frame = 0;
Animation.prototype.frames = null;
Animation.prototype.frameCount = 0;
Animation.prototype.duration = 0;
Animation.prototype.paused = false;
Animation.prototype.play = function animationPlay() {
this.paused = false;
};
Animation.prototype.pause = function animationStop() {
this.paused = true;
};
Animation.prototype.stop = function animationStop() {
this.paused = true;
this.frame = 0;
};
Animation.prototype.update = function animationUpdate(dt) {
if (this.paused) {
return this.frames[this.frame];
}
this.elapsedTime += dt;
if (this.elapsedTime > this.duration) {
this.frame += 1;
this.frame = this.frame % this.frameCount;
this.elapsedTime -= this.duration;
}
return this.frames[this.frame];
};
Let me explain a one of the quirks of this code, we set this.elapsedTime -= this.duration. If we were to just set this.elapsedTime = 0, our animation would stutter as frames could be faster or slower depending on the the CPU or other environmental factors. We also need to update the run loop.
var last_frame_update_time = 0;
function loop(timestamp) {
// clear the screen
draw_context.clearRect(0, 0, canvas.width, canvas.height);
var dt = timestamp - last_frame_update_time;
frame = coin_animation.update(dt);
// draw the frame from coin_image to the canvas, starting frame * 16 across coin_image for 16 pixels
draw_context.drawImage(sprite_sheet, frame.x, frame.y, frame.width, frame.height, coin.x, coin.y);
last_frame_update_time = timestamp;
window.requestAnimationFrame(loop); // shim? http://paulirish.com/2011/requestanimationframe-for-smart-animating/
}
Wrapping
Hopefully this has been interesting. Hopefully it will be useful. Let me know what you think on Twitter and you can find a copy of the code on Github.
Is dependency injection needed in Node.js?
Recently I felt the need to write a DI manager.
As I completed my implementation I started to wonder the simple nature of it. If you adhere to the one module per file mindset, I’d wager you don’t need it. Take the following for example.
var Pen = require('Pen');
var Notepad = require('Notepad');
function Reporter() {
this.pen = new Pen();
this.notepad = new Notepad();
}
Reporter.prototype.pen = null;
Reporter.prototype.notepad = null;
Reporter.prototype.write = function (note) {
this.pen.write(note, this.notepad);
}
var reporter = new Reporter();
A reporter needs a pen and notepad to write, here we manually setup the dependencies.
We could do this with dependency injection:
var di = new DependencyManager();
di.register(Pen);
di.register(Notepad);
di.register(Reporter, {
pen: 'Pen',
notepad: 'Notepad'
});
var reporter = di.create('Reporter');
My question is, is there a need for DI in Node?
The benefit I can see is that with DI we can override the instances of pen and notepad at run time more easily.
Dirty rectangles
Recently I’ve been experimenting in HTML5 game development and like my last post, this post is to document my findings.
Dirty Rectangles, what are they?
Imagine canvas animation is similar to cel animation, in that you compose your animation using serveral layers. Cel animators don’t want to have to draw the entire character for each frame if the only thing that is moving is the character’s mouth. (thanks to @speakersfive for this analogy)
With dirty rectangles, we detect which areas of our game have changed and therefore need updating.
How do we detect a dirty rectangle
The simplest way is to keep an array of ractangles, foreach
over them on the next draw pass and clear those areas of our canvas.
You might be thinking; “what about my background or HUD?”, remember the cel animation analogy? Well just like cel animation we’d create our game in layers, leaving our background unaffected by our player’s movement.
Whenever we make a change to the player’s position, we’d mark the player sprite as dirty and add its rectangle to the array of dirty rectangles
How do we code this>
Coding this is fairly straight forward, so lets start with our game object.
// Game is our game object and hold all data about our game // as well as update and draw methods Game.dirtyRectangles = [];
function Player(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
* Move player by x and y increments
* @param {Number} x player x increment
* @param {[type]} y player y increment
* @return {void}
*/
Player.prototype.move = function Player_move(x, y) {
var original_x = this.x;
var original_y = this.y;
this.x += x;
this.y += y;
// if some change to the position was made
if (original_x != this.x || original_y != y) {
this.dirty = true;
// add this rectangle to the dirty rectangles array
// note: it's the rectangle before the movement was made
Game.dirtyRectangles.push({
x: original_x,
y: original_y,
width: this.width,
height: this.height
});
}
};
So let me explain what we are doing here.
This prototype is configured to have an x and y position and a width and height. These properties control the position of the player and dirty rectangle size and position.
When we call move() to move out player we first store
the existing position and then update it. Next we check if the
position changed since the start of the method, when we stored the
position, if it did then we set the dirty property and
push an object holding the bounding rectangle of the previous position
into the dirty rectangle array.
Next we move on to our game loop’s draw method.
Game.draw = function Game_draw() {
var ctx = Game.canvasContext;
var i, dirtyRectangleCount = this.dirtyRectangles.length;
for (i = 0; i < dirtyRectangleCount; i+= 1) {
var rect = this.dirtyRectangles[i];
// clear this rectangle from the canvas
ctx.clearRect(rect.x, rect.y. rect.width, rect.height);
}
// clear the dirty rectangle list
this.dirtyrectangles = [];
// then redraw any dirty sprites
var spritesCount = this.sprites.length;
for(i = 0; i < spritesCount; i += 1) {
var sprite = this.sprites[i];
// is the sprite dirty?
if (sprite.dirty) {
sprite.draw(ctx); // pass canvas context to the sprite
}
}
};
Okay, here we iterate of the the dirty rectangles array and clear those parts of the canvas. then we iterate over the sprites and redraw any sprites that have been marked as dirty. Simple.
Next we’ll need to mark our sprite as clean again. The best place to do this is probably the sprite’s draw method.
/**
* Draw the player
* @param {CanvasContext} ctx canvas context
* @return {void}
*/
Player.prototype.draw = function Player_draw(ctx) {
/*
...
our drawing code
...
*/
// set our sprite to clean again
this.dirty = false;
};
And that’s it. Only the changed parts of our screen will be updated using dirty rectangles.
Feel free to give me feedback on twitter @bytespider
requestAnimationFrame and HTML5 game loops
When looking at writing a HTML5 game, I first went down the usual route of using setTimeout(run, 1000/60);. However due to the nature of javascript, this isn’t always optimal. Javascript is single threaded so you can’t always guarantee that your frame will be rendered when you expected it.
In walks requestAnimationFrame
Thankfully there’s a new kid on the block, requestAnimationFrame, and the browser will call your function when it has rendering time free and passing the time.
But how do we update our game loop to use this? In much the same way as setTimeout() thankfully.
function main(time) {
run();
window.requestAnimationFrame(main);
}
window.requestAnimationFrame(main);
Gotcha
This one stumped me for a while, but in my simple game the CPU was hitting 100%. So I added code to reduce the frame rate.
var skipTicks = 1000 / 30, nextTick = new Date().getTime();
function run(time) {
while (time > nextTick) {
update();
draw();
nextTick += skipTicks;
}
};
However my CPU usage was still 100%. I had a think and it dawned on me, requestAnimationFrame makes a request for rendering time and the browser will give the next available spot. But the browser is so fast, we’re practically requesting a frame every millisecond, no wonder my CPU hits 100%.
So I’ve updated my code, to wait 10ms before requesting the next animation frame.
function main(time) {
run();
setTimeout(function () {
window.requestAnimationFrame(main);
}, 10);
}
window.requestAnimationFrame(main);
Object cloning using ES5
function clone(object) {
var cloned = Object.create(object.prototype || null);
Object.keys(object).map(function (i) {
cloned[i] = object[i];
});
return cloned;
}
Using CommonJS modules for UI View components in Titanium
I’ve been playing with using CommonJS modules in Appcelerator Titanium to create reusable components and prototypes. I start with a basic object structure:
function Car() {
this.init.apply(this, arguments);
}
Car.prototype.init = function (make, model) {
this.make = make;
this.model = model;
};
Car.prototype.make = null;
Car.prototype.model = null;
Car.createWithMakeAndModel = function (make, model) {
return new Car(make, model);
};
module.exports = Car;
Whilst you may argue there is some redundancy in the pattern I use, I makes sections of code easy to override later on if I decide to use it as a prototype.
You may also frown upon the use of module.exports = Car;. However I find that
var Car = require("Car");
looks and feels better than
var Car = require("Car").Car;
You may argue that you should never have access to the constructor and that you should use factory methods. Whilst your argument is valid, I prefer to be able to make sure that my object X is an instance of Y using the built in instanceof operator.
How this applies to views and windows
For views and windows I add .view and .wnd properties to my object inside my init() method, which is where I call all other parts of my view construction. Take the following example of a list view.
function UIListView()
{
this.init.apply(this, arguments);
}
UIListView.prototype.init = function () {
this.view = Ti.UI.createTableView();
};
UIListView.prototype.populate = function (data) {
var i = 0, len = data.length, row;
for (; i < len; ++i)
{
row = Ti.UI.createTableViewRow(data[i].asTableViewRow());
this.view.appendRow(row);
}
};
module.exports = UIListView;
As you can see I can now call populate() at anytime to update my view with new data.
I’d like to experiment more with patterns like this. I’d love to hear your opinions and suggestions for other patterns or structures, so tweet me or send me an email.
Chosen does allow you to set when the search box should show
I started using chosen in a few projects but it frustrated me that the search box would always show regardless of how many options I had.
I read through the website and discover theres no mention of a setting, so being a good little community developer I decide to implement the functionality.
After getting my head round the coffeescript source, which in my opinion is far worse to read and grok than javascript, I add the code. Then i think I’ll be really good and make it configurable before I submit a pull-request.
Thats when I discover disable_search_threshold, and wondering what it was I try it. Turns out that it’s exactly the functionality I had just written and wasted time on.
So next time you’re using chosen and you need to hide search for the first, lets say, 10 items do this:
$(".chzn-select").chosen({
disable_search_threshold: 10
});
jsOAuth in the browser, my response to OAuth in web browsers
- Developer:
- We are on a look out for a Javascript OAuth library that works in browsers.
- Rob Griffiths:
- There are some things you need to consider when trying to get OAuth working in the browser using Javascript.
- Firstly, Javascript source is viewable by anyone with the inclination to do so, this means that your OAuth Key and Secret are publicly accessible, meaning that an attacker could use your credentials to sign requests and gain access tokens, in theory at least. In my opinion the attacker would have to fool a user into thinking they were your site, which isn't too hard but users are stupid and can be fooled easily.
- Secondly, if your site contacts a third party webservice, you'll be bound by in build browser security as you are making a cross domain request. The security disables any XHR from talking to a domain other than the one it was created on.
- Unless you use CORS (cross origin resource sharing), however most web services dont support this, which is a shame as this would make the web a better place allowing sites to interact at the client level creating interesting application mashups.
- So I'd ask this;
- - Does the webservice you're planning to use support CORS?
- - Do you have a plan to hide your secret in plain sight?
- Most people solve these issue by building a server-side script to proxy their XHR.
