Frame-based Animation

Frame-based animation relies on multiple images with slight differences between each image, which are presented to the viewer in rapid succession.

Pre-digital movies used a strip of celluloid film to capture successive static images of moving people and scenes for supplying these frames.

Pre-digital animated film, on the other hand, relied on animators drawing or painting the scene, then photographing these static images in succession on film - very similar to stop-motion animation.

One of the largest innovations in this style of animation was the use of multiple transparent 'cels', upon which different scene and character elements could be painted. Multiple cels could be stacked in front of a camera to compose a scene. Distances from the camera could also create scaling effects.

Multi-pan camera for cel animaiton

Similarly, we can compose a static frame of a game's scene with multiple independently animated elements, collectively known as sprites (The term 'sprite' originally referred to small fairies, and was adopted in early game systems to refer to a special kind of hardware sprite which would overlay an animated element on the already-composted scene as it was rendered).

Sprite Animation

Turning our attention to sprites, then, we need a set of frames to animate. One common practice is to place all the frames side-by-side in a single image known as a spritesheet, and only copy a portion of that image to the drawing surface.

For example, this frog spritesheet contains eight 64x64 pixel frames:

Frog spritesheet

In using the JavaScript canvas, we would supply the context2D's drawImage() method with the spritesheet image, a source rectangle, and a destination rectangle, i.e.:

ctx.drawImage(spritesheet, // Source rectangle 0, 0, 64, 64, // Destination rectangle 50, 50, 64, 64 );

This would draw the frame of the frog jumping in the top left corner to a position 50 pixels from the left and top of the canvas. However, to make the frog really jump we would need to advance to the next frame in the sequence.

Remember, we're drawing to our canvas at 1/60th of a second, but changing frames at 1/16 of a second is sufficient to create the illusion of motion. If we swapped a frame every time we redrew the scene at this speed, the frog would appear to be a blur. So we would need to draw the same frame for several redraws of the scene. This means our frog will need to have a separate animation timer to track when it needs to change frames.

If we bundle our Sprite in a class, say Frog, we can add this timer directly to it:

function Frog() { this.timer = 0; this.frame = 0; }

Remember our update method receives the elapsed time as an argument; we can increment the timer using this value, and when it reaches a certain value, advance the frame and start the timer over:

Frog.prototype.update(elapsedTime) { this.timer += elapsedTime; // if the timer has reached 1/16 of a second, advance the frame if(this.time > 1000/16) { this.timer = 0; this.frame++; } }

And rendering now can use the frame property of the frog to draw the appropriate animation frame:

Frog.prototype.render(elapsedTime, ctx) { ctx.drawImage(spritesheet, // Source rectangle this.frame * 64, 0, 64, 64, // Destination rectangle 50, 50, 64, 64 ); }

The keen-eyed observer might notice an oversight in the above code... what happens when our frame member reaches 4? We'd no longer have our source rectangle within our image! We need to decide if our animation should be looping, in which case we would reset our frame to 0, or if we should transition to another animation.

Sprite Animation State

The second sounds suspiciously like the state pattern, and that indeed is a good fit. Let's think of our frog as having two states: jumping or idle. We'll need to add another instance variable to our class:

function Frog(){ this.state = "idle"; this.timer = 0; this.frame = 0; }

Now, we can choose a different row of our image to render based on the current state, i.e.:

Frog.prototype.draw(elapsedTime, ctx) { var row = (state == "idle") ? 64 : 0; ctx.drawImage(spritesheet, // Source rectangle this.frame * 64, row, 64, 64, // Destination rectangle 50, 50, 64, 64 ); }

And when we reach the end of the jump animation, transition to the idle state:

Frog.prototype.update(elapsedTime) { switch(this.state) { case "jumping": this.timer += elapsedTime; // if the timer has reached 1/16 of a second, advance the frame if(this.time > 1000/16) { this.timer = 0; this.frame++; // if we've finished the animation, switch to the idle state if(frame > 3) { this.state = "idle"; this.frame = 0; } } break; } }

We can of course expand upon this basic functionality to account for more states and animation sequences. Moreover, we can combine multiple sprites within a scene, much like composting cel animations!