Now that we have presented the principle of sprite extraction (sprites as sub-images of a single composite image), let's write a small sprite animation framework.
Here is how you would create and animate a sprite:
var robot;
window.onload = function() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
// Load the spritesheet
spritesheet = new Image();
spritesheet.src = SPRITESHEET_URL;
// Called when the spritesheet has been loaded
spritesheet.onload = function() {
...
robot = new Sprite();
// 1 is the posture number in the sprite sheet. We have
// only one with the robot.
robot.extractSprites(spritesheet, NB_POSTURES, 1,
NB_FRAMES_PER_POSTURE,
SPRITE_WIDTH, SPRITE_HEIGHT);
robot.setNbImagesPerSecond(20);
requestAnimationFrame(mainloop);
}; // onload
};
function mainloop() {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw sprite at 0, 0 in the small canvas
robot.draw(ctx, 0, 0, 1);
requestAnimationFrame(mainloop);
}
Try the example on JSBin that uses this framework first! Experiment by editing line 20: robot.setNbImagesPerSecond(20); changing the value of the parameter and observing the result.
In this small framework we use "SpriteImage ", a JS object we build to represent one sprite image. Its properties are: the global sprite sheet to which it belongs, its position in the sprite sheet, and its size. It also has a draw method for drawing the sprite image at an xPos, yPos position, and at anappropriate size.
function SpriteImage(img, x, y, width, height) {
this.img = img; // the whole image that contains all sprites
this.x = x; // x, y position of the sprite image in the whole image
this.y = y;
this.width = width; // width and height of the sprite image
this.height = height;
this.draw = function(ctx, xPos, yPos, scale) {
ctx.drawImage(this.img,
this.x, this.y, // x, y, width and height of img to extract
this.width, this.height,
xPos, yPos, // x, y, width and height of img to draw
this.width*scale, this.height*scale);
};
}
We define the Sprite model. This is the one we used to create the small robot in the previous example.
A Sprite is defined by an array of SpriteImage objects.
It has a method for extracting all SpriteImages from a given sprite sheet and filling the above array.
It has a draw method which will draw the current SpriteImage. A Sprite is an animated object, therefore, calling draw multiple times will involve an automatic change of the current SpriteImage being drawn.
The number of different images to be drawn per second is a parameter of the sprite.
Here is the code of the Sprite model:
function Sprite() {
this.spriteArray = [];
this.currentFrame = 0;
this.delayBetweenFrames = 10;
this.extractSprites = function(spritesheet,
nbPostures, postureToExtract,
nbFramesPerPosture,
spriteWidth, spriteHeight) {
// number of sprites per row in the spritesheet
var nbSpritesPerRow = Math.floor(spritesheet.width / spriteWidth);
// Extract each sprite
var startIndex = (postureToExtract -1) * nbFramesPerPosture;
var endIndex = startIndex + nbFramesPerPosture;
for(var index = startIndex; index < maxIndex; index++) {
// Computation of the x and y position that corresponds to the sprite
// index
// x is the rest of index/nbSpritesPerRow * width of a sprite
var x = (index % nbSpritesPerRow) * spriteWidth;
// y is the divisor of index by nbSpritesPerRow * height of a sprite
var y = Math.floor(index / nbSpritesPerRow) * spriteHeight;
// build a spriteImage object
var s = new SpriteImage(spritesheet, x, y, spriteWidth, spriteHeight);
this.spriteArray.push(s);
}
};
this.then = performance.now();
this.totalTimeSinceLastRedraw = 0;
this.draw = function(ctx, x, y) {
// Use time based animation to draw only a few images per second
var now = performance.now();
var delta = now - this.then;
// Draw currentSpriteImage
var currentSpriteImage = this.spriteArray[this.currentFrame];
// x, y, scale. 1 = size unchanged
currentSpriteImage.draw(ctx, x, y, 1);
// if the delay between images is elapsed, go to the next one
if (this.totalTimeSinceLastRedraw > this.delayBetweenFrames) {
// Go to the next sprite image
this.currentFrame++;
this.currentFrame %= this.spriteArray.length;
// reset the total time since last image has been drawn
this.totalTimeSinceLastRedraw = 0;
} else {
// sum the total time since last redraw
this. totalTimeSinceLastRedraw += delta;
}
this.then = now;
};
this.setNbImagesPerSecond = function(nb) {
// delay in ms between images
this.delayBetweenFrames = 1000 / nb;
};
}
This time, we have changed the parameters of the sprites and sprite sheet. Now you can select the index of the posture to extract: the woman sprite sheet has 8 different postures, so you can call:
womanDown.extractSprites(spritesheet, NB_POSTURES, 1,
NB_FRAMES_PER_POSTURE,
SPRITE_WIDTH, SPRITE_HEIGHT);
womanDiagonalBottomLeft.extractSprites(spritesheet, NB_POSTURES, 2,
NB_FRAMES_PER_POSTURE,
SPRITE_WIDTH, SPRITE_HEIGHT);
womanLeft.extractSprites(spritesheet, NB_POSTURES, 3,
NB_FRAMES_PER_POSTURE,
SPRITE_WIDTH, SPRITE_HEIGHT);
As usual, we used key listeners, an inputStates global object, and this time we created 8 woman sprites, one for each direction.
Notice that we added a drawStopped method in the Sprite model in order to stop animating the woman when no key is pressed for moving her.
Game development tutorial (5 May 2020): Draw images and sprite animations