When
you write casual games using the HTML5 Canvas element, you need a way to handle
your sprites. There are several libraries available to help you write games,
including ImpactJS and
CraftyJS.
Introduction
On
the official EaselJS
site, you’ll find interesting samples and some basic documentation.
We’ll use the sprites
sample as a base, along with resources available in the XNA 4.0
Platformer sample.
If
you follow my blog, you know I love playing with this sample. I’ve used it in
two previous articles:
The
platformer sample has been updated by the XNA team and is available for Xbox
360, PC & Windows Phone 7: App Hub –
platformer. Download it to play around with it, then extract the sprites
to use with EaselJS.
I’m going
to use 2 PNG files as source of sprite sequences.
A
running monster, which contains 10 different sprites.
Our
monster in idle mode, containing 11 different sprites:
Note:
These samples don’t work properly in Firefox 5.0, apparently due to a bug in their
canvas implementation. It has been tested ok in
IE9, IE10, Chrome 12, Opera 11 and Firefox Aurora 7.0.
Tutorial 1: Building the SpriteSheet and the BitmapSequence
We’ll
start by running the monster from one end of the canvas to the other.
The
first step is to load the complete sequence contained in the PNG file with this
code:
var imgMonsterARun = new Image();
function init() {
canvas = document.getElementById("testCanvas");
imgMonsterARun.onload = handleImageLoad;
imgMonsterARun.onerror = handleImageError;
imgMonsterARun.src = "img/MonsterARun.png";
}
This
code will be called first to initialize our game’s content. Once loaded, you
can start the game.
EaselJS
exposes a SpriteSheet object
to handle the sprite. Thus, by using this code:
var spriteSheet = new SpriteSheet(
imgMonsterARun, 64, 64, {
walk_left: [0, 9]
});
...I’m
indicating that I’d like to create a new sequence named "walk_left
" that
will be made of the imgMonsterARun
image. This image will be split into
10 frames with a size of 64x64 pixels. This is the core object to load our
sprite and create our sequences. There could be several sequences created from
the same PNG file if you want, like in the rats sprite sample on the EaselJS
site.
After
that, you’ll need to use the BitmapSequence
object. It helps animate the sequence and position the sprites on screen.
Let’s
review the initializing code of this BitmapSequence
:
bmpSeq = new BitmapSequence(spriteSheet);
bmpSeq.regX = bmpSeq.spriteSheet.frameWidth/2|0;
bmpSeq.regY = bmpSeq.spriteSheet.frameHeight / 2 | 0;
bmpSeq.gotoAndPlay("walk_left");
bmpSeq.shadow = new Shadow("#454", 0, 5, 4);
bmpSeq.name = "monster1";
bmpSeq.speed = 1;
bmpSeq.direction = 90;
bmpSeq.vX = 3|0.5;
bmpSeq.vY = 0;
bmpSeq.x = 16;
bmpSeq.y = 32;
bmpSeq.currentFrame = 0;
stage.addChild(bmpSeq);
The
constructor of the BitmapSequence
object simply needs the SpriteSheet
element as a parameter. We’re then giving a name to the sequence, setting some
parameters like the speed and the initial position of our first frame. Finally,
we add this sequence to the display list by using the Stage
object and its addChild()
method.
Next,
you need to decide what you’d like to do in the animation loop. This animation
loop is called every xxx milliseconds and lets you update the position of your
sprites. For that, EaselJS exposes a Ticker
object that provides a centralized tick or heartbeat broadcast at a set
interval.
All you
have to do is subscribe to the tick event and implement a .tick() method that
will be called back.
This
code is for instance registering the event:
Ticker.addListener(window);
Ticker.setInterval(17);
And
here is the code that will be called every 17ms (when possible) to update the
position of our monster:
function tick() {
if (bmpSeq.x >= screen_width - 16) {
bmpSeq.direction = -90;
}
if (bmpSeq.x < 16) {
bmpSeq.direction = 90;
}
if (bmpSeq.direction == 90) {
bmpSeq.x += bmpSeq.vX;
bmpSeq.y += bmpSeq.vY;
}
else {
bmpSeq.x -= bmpSeq.vX;
bmpSeq.y -= bmpSeq.vY;
}
stage.update();
}
You
can test the final result here: easelJSSpritesTutorial01
to view the complete source code.
But
wait! There are 2 problems in this animation!
-
The animation steps are weird, since the character loops through its different
sprites too fast
- The character only walks normally from right to left—otherwise it looks like
he’s doing the Moonwalk!
Let’s
fix it.
Tutorial 2: Controlling
the Animation speed and Flipping the Sprites
The
simplest way I’ve found to fix the animation’s speed is by using a modulus
operator to avoid drawing/updating my sequence during each tick.
There’s
currently an open issue about this on EaselJS 0.3.2: https://github.com/gskinner/EaselJS/issues/60
To make
the character walk normally from left to right, we just need to flip each
frame.
EaselJS
exposes a SpriteSheetUtils
object for that and a flip()
method.
spriteSheet = SpriteSheetUtils.flip(
spriteSheet,
{
walk_right: ["walk_left", true, false, null]
});
You’re
essentially making a derivative sequence named "walk_right
" based
on the "walk_left" sequence that’s flipped horizontally.
Finally,
here is the code that slows down the speed animation and handles which sequence
to play based on the character position:
function tick() {
var speedControl = Ticker.getTicks() % 4;
if (speedControl == 0) {
if (bmpSeq.x >= screen_width - 16) {
bmpSeq.direction = -90;
bmpSeq.gotoAndPlay("walk_left")
}
if (bmpSeq.x < 16) {
bmpSeq.direction = 90;
bmpSeq.gotoAndPlay("walk_right");
}
if (bmpSeq.direction == 90) {
bmpSeq.x += bmpSeq.vX;
bmpSeq.y += bmpSeq.vY;
}
else {
bmpSeq.x -= bmpSeq.vX;
bmpSeq.y -= bmpSeq.vY;
}
stage.update();
}
}
You
can test the final result here: easelJSSpritesTutorial02
Tutorial 3: Loading Multiple Sprites and Playing with Multiple Animations
It’s
time to load the idle state of the monster.
The
idea here is to make the monster travel a single round-trip, then play the idle
state.
We’ll
have to load multiple PNG files from the web server. It’s very important to
wait until all resources are loaded, otherwise you might try to draw non-yet
downloaded resources.
Here’s
a simple way to do it:
var numberOfImagesLoaded = 0;
var imgMonsterARun = new Image();
var imgMonsterAIdle = new Image();
function init() {
canvas = document.getElementById("testCanvas");
imgMonsterARun.onload = handleImageLoad;
imgMonsterARun.onerror = handleImageError;
imgMonsterARun.src = "img/MonsterARun.png";
imgMonsterAIdle.onload = handleImageLoad;
imgMonsterAIdle.onerror = handleImageError;
imgMonsterAIdle.src = "img/MonsterAIdle.png";
}
function handleImageLoad(e) {
numberOfImagesLoaded++;
if (numberOfImagesLoaded == 2) {
numberOfImagesLoaded = 0;
startGame();
}
}
This
code is very simple. For instance, it doesn’t handle the errors properly by
trying to re-download the image in case of a first failure.
When
you’re building a game, you will need to write your own content download
manager if the JS library you’re using doesn’t implement it.
To add
the idle sequence and set the position parameters, you just need the same kind
of code previously seen:
var spriteSheetIdle = new SpriteSheet(
imgMonsterAIdle, 64, 64, {
idle: [0, 10]
});
bmpSeqIdle = new BitmapSequence(spriteSheetIdle);
bmpSeqIdle.name = "monsteridle1";
bmpSeqIdle.speed = 1;
bmpSeqIdle.direction = 0;
bmpSeqIdle.x = 16;
bmpSeqIdle.y = 32;
Now,
in the tick()
method, we need to stop the walking animation once we’ve reached
the left side of the screen, and play the idle animation instead.
Here
is the code to stop your monster:
if (bmpSeq.x < 16) {
bmpSeq.direction = 90;
bmpSeq.gotoAndStop("walk_left");
stage.removeChild(bmpSeq);
bmpSeqIdle.gotoAndPlay("idle");
stage.addChild(bmpSeqIdle);
}
You
can test the final result here: easelJSSpritesTutorial03.
That’s
all folks! If you’re ready for more, check out HTML5
Gaming: building the core objects & handling collisions with EaselJS.