Great stories have great characters. And like books and movies, video games also need characters with interesting behaviors. For example, the protagonist in Braid — the best-selling platform game of all time — can manipulate time. That ingenious behavior set the game apart from its contemporaries.
Behaviors are the soul of any video game, and adding behaviors to Snail Bait's inert sprites implemented in the previous installment immediately makes the game more interesting, as shown in Figure 1:
Figure 1. The state of Snail Bait at the end of this article
Recall from the Sprite objects section in the preceding article that Snail Bait's sprites don't implement their own activities such as running, jumping, or exploding. Instead, sprites rely on other objects — known as behaviors — to control how they act.
Figure 1 shows the snail shooting a snail bomb. Other behaviors that you can't see in Figure 1's static image are:
- The runner runs
- Buttons pace back and forth on their platforms
- Rubies and sapphires sparkle
Table 1 summarizes those behaviors:
Table 1. Behaviors discussed in this article
| Sprites | Behavior | Description |
|---|---|---|
| Buttons, snails |
paceBehavior
| Paces back and forth along a platform |
| Runner |
runBehavior
| Cycles through the runner's images to make it appear as though the runner is running |
| Snail |
snailShootBehavior
| Shoots a snail bomb from the snail's mouth |
| Snail |
cycleBehavior
| Cycles through a sprite's images |
| Snail bomb |
snailBombMoveBehavior
| Moves the snail bomb horizontally to the left while it's visible in the canvas |
The behaviors listed in Table 1 represent less than half of the game's behaviors — as you can see from the Snail Bait's sprites and behaviors table in the first article in this series. They are also the most basic of the sprites' behaviors; jumping, for example, is considerably more complex, as you will see in forthcoming articles. Nonetheless, there's a lot to learn from the implementation of the simpler behaviors in this article, including how to:
- Implement behaviors and assign them to sprites
- Cycle a sprite through a sequence of images
- Create flyweight behaviors to save on memory usage
- Combine behaviors
- Use behaviors to shoot projectiles
Any object can be a behavior as long as it has an execute() method. That method takes three arguments: a sprite, the time, and the frame rate for the game's animation. A behavior's execute() method modifies the sprite's state depending on the time and animation frame rate.
Behaviors are powerful because:
- They decouple sprites from the way they behave.
- You can change sprite behaviors at run time.
- You can implement behaviors that work with any sprite.
- Stateless behaviors can be used as flyweights.
Before I discuss the implementation details of the behaviors listed in Table 1, I'll give you a high-level overview of behaviors — how to implement them and associate them with sprites — by looking at the runner's collective behaviors.
Snail Bait's runner has four behaviors, listed in Table 2:
Table 2. The runner's behaviors
| Behavior | Description |
|---|---|
runBehavior
| Cycles through the runner's cells from the sprite sheet to make it appear as though the runner is running |
jumpBehavior
| Controls all aspects of jumping: ascent, descent, and landing |
fallBehavior
| Controls the vertical movement of the runner as she falls |
runnerCollideBehavior
| Detects, and reacts to, collisions between the runner and the other sprites |
I specify the runner's behaviors with an array of objects that I pass to the Sprite constructor, as shown in Listing 1:
Listing 1. Creating SnailBait's runner
var SnailBait = function () {
...
this.runner = new Sprite('runner', // Type
this.runnerArtist, // Artist
[this.runBehavior, // Behaviors
this.jumpBehavior,
this.fallBehavior,
this.runnerCollideBehavior
]);
};
|
The runner's behaviors are shown in Listing 2, with implementation details removed:
Listing 2. Runner behavior objects
var SnailBait = function () {
...
this.runBehavior = {
execute: function(sprite, time, fps) { // sprite is the runner
...
}
};
this.jumpBehavior = {
execute: function(sprite, time, fps) { // sprite is the runner
...
}
};
this.fallBehavior = {
execute: function(sprite, time, fps) { // sprite is the runner
...
}
};
this.runnerCollideBehavior = {
execute: function(sprite, time, fps) { // sprite is the runner
...
}
};
};
|
Every animation frame, Snail Bait iterates over its array of sprites, invoking each sprite's update() method, shown in Listing 3:
Listing 3. Executing behaviors
Sprite.prototype = {
update: function (time, fps) {
for (var i=0; i < this.behaviors.length; ++i) {
if (this.behaviors[i] === undefined) { // You never know
return;
}
this.behaviors[i].execute(this, time, fps);
}
}
};
|
The Sprite.update() method iterates over the sprite's behaviors, invoking each behavior's execute() method. Snail Bait continuously — once per animation frame — invokes all behaviors associated with all visible sprites. A behavior's execute() method, therefore, is not like most other methods, which are invoked relatively infrequently; instead, each execute() method is like a little motor that's constantly running.
Now that you understand how sprites and behaviors fit together, I'll concentrate on implementing them individually.
Snail Bait does two things that make it appear as though the runner is running. First, as I discussed in the Scrolling the background section in the second article in this series, the game continuously scrolls the background, making it appear as though the runner is moving horizontally. Second, the runner's run behavior cycles the runner through a sequence of images from the game's sprite sheet, as shown in Figure 2:
Figure 2. Running sequence
The code in Listing 4 implements the run behavior:
Listing 4. The runner's
runBehavior
var SnailBait = function () {
...
this.BACKGROUND_VELOCITY = 32, // pixels/second
this.RUN_ANIMATION_RATE = 17, // frames/second
...
this.runAnimationRate,
this.runBehavior = {
// Every |
The runBehavior object's execute() method periodically advances the runner's artist to the next image in the runner's sequence from the sprite sheet. (You can see Snail Bait's sprite sheet in the Sprite artists and sprite sheets section in the fourth article in this series.)
How often the runBehavior advances the runner's image
determines how quickly the runner runs. That time interval is set with the runner's runAnimationRate attribute. The runner is not running when the game starts, so its runAnimationRate is initially zero. When the player turns left or right, however, Snail Bait sets that attribute to 17 frames/second, as shown in Listing 5, and the runner starts to run:
Listing 5. Turning starts the run animation
SnailBait.prototype = {
...
turnLeft: function () {
this.bgVelocity = -this.BACKGROUND_VELOCITY;
this.runner.runAnimationRate = this.RUN_ANIMATION_RATE; // 17 fps, see Listing 4
this.runnerArtist.cells = this.runnerCellsLeft;
this.runner.direction = this.LEFT;
},
turnRight: function () {
this.bgVelocity = this.BACKGROUND_VELOCITY;
this.runner.runAnimationRate = this.RUN_ANIMATION_RATE; // 17 fps, see Listing 4
this.runnerArtist.cells = this.runnerCellsRight;
this.runner.direction = this.RIGHT;
},
};
|
The turnLeft() and turnRight()
methods, which are invoked by the game's keyboard event handlers, control how quickly
the runner cycles through its image sequence with the runAnimationRate attribute, as I discussed previously. Those methods also control how fast the runner moves from left to right by setting the bgVelocity attribute, which represents the rate at which the background scrolls.
The runner's run behavior discussed in the preceding section maintains state — namely, the time the behavior last advanced the sprite's image. That state tightly couples the runner to the behavior. So, for instance, if you wanted to make another sprite run, you would need to have another run behavior.
Behaviors that do not maintain state are more flexible; for example, they can be used as flyweights. A flyweight is a single instance of an object, used by many other objects simultaneously. Figure 3 illustrates a stateless pace behavior that makes sprites pace back and forth on a platform. A single instance of that behavior is used for the game's buttons and its snail, all of which pace back and forth on their platforms, shown in Figure 3:
Figure 3. Button pacing sequence
Listing 6 shows Snail Bait's createButtonSprites() method, which adds the lone pace behavior to each button:
Listing 6. Creating pacing buttons
SnailBait.prototype = {
...
createButtonSprites: function () {
var button,
buttonArtist = new SpriteSheetArtist(this.spritesheet,
this.buttonCells),
goldButtonArtist = new SpriteSheetArtist(this.spritesheet,
this.goldButtonCells);
for (var i = 0; i < this.buttonData.length; ++i) {
if (i === this.buttonData.length - 1) {
button = new Sprite('button',
goldButtonArtist,
[ this.paceBehavior ]);
}
else {
button = new Sprite('button',
buttonArtist,
[ this.paceBehavior ]);
}
button.width = this.BUTTON_CELLS_WIDTH;
button.height = this.BUTTON_CELLS_HEIGHT;
button.velocityX = this.BUTTON_PACE_VELOCITY;
button.direction = this.RIGHT;
this.buttons.push(button);
}
},
...
};
|
Listing 7 shows the paceBehavior object:
Listing 7. The pace behavior
var SnailBait = function () {
...
this.paceBehavior = {
checkDirection: function (sprite) {
var sRight = sprite.left + sprite.width,
pRight = sprite.platform.left + sprite.platform.width;
if (sRight > pRight && sprite.direction === snailBait.RIGHT) {
sprite.direction = snailBait.LEFT;
}
else if (sprite.left < sprite.platform.left &&
sprite.direction === snailBait.LEFT) {
sprite.direction = snailBait.RIGHT;
}
},
moveSprite: function (sprite, fps) {
var pixelsToMove = sprite.velocityX / fps;
if (sprite.direction === snailBait.RIGHT) {
sprite.left += pixelsToMove;
}
else {
sprite.left -= pixelsToMove;
}
},
execute: function (sprite, time, fps) {
this.checkDirection(sprite);
this.moveSprite(sprite, fps);
}
},
|
The pace behavior modifies a sprite's horizontal position. The behavior implements time-based motion to calculate how many pixels to move the sprite for the current animation frame by dividing the sprite's velocity (which is specified in pixels/second) by the animation's frame rate (in frames/second), which results in pixels/frame. (See the Time-based motion section in the second article in this series for more information about time-based motion.)
The first behavior I discussed in this article — runBehavior — is a stateful behavior that's tightly coupled to a single sprite. The paceBehavior, which I discussed next, is a stateless behavior, which decouples it from individual sprites, so a single instance can be used by multiple sprites.
Behaviors can be generalized even further: You can decouple them not only from individual sprites, but also from the game itself. Snail Bait uses three behaviors that can be used in any game:
bounceBehaviorcycleBehaviorpulseBehavior
The bounce behavior bounces a sprite up and down, the cycle behavior cycles a sprite through a set of images, and the pulse behavior manipulates a sprite's opacity to make it appear as though the sprite is pulsating.
The bounce and pulse behaviors both involve nonlinear animation, which I will discuss in forthcoming articles. The cycle behavior cycles through a sprite's images linearly, however, so I will use the implementation of that behavior to illustrate implementing behaviors that can be used in any game.
Snail Bait's rubies and sapphires sparkle, as shown in Figure 4:
Figure 4. Sparkling ruby sequence
Snail Bait's sprite sheet contains sequences of images for both rubies and sapphires; cycling through those images creates the sparkling illusion.
Listing 8 shows the Snail Bait method that creates rubies. A nearly identical method (not shown) creates sapphires. The createRubySprites() method also creates a cycle behavior that every 500ms displays the next image from the ruby-sparkling sequence for 100ms.
Listing 8. Creating rubies
SnailBait.prototype = {
...
createRubySprites: function () {
var ruby,
rubyArtist = new SpriteSheetArtist(this.spritesheet, this.rubyCells);
for (var i = 0; i < this.rubyData.length; ++i) {
ruby = new Sprite('ruby', rubyArtist,
[ new CycleBehavior(100, // animation duration
500) ]); // interval between animations
...
}
},
...
};
|
Listing 9 shows the cycle behavior:
Listing 9. The
CycleBehavior behavior
// This behavior advances the sprite artist through
// the sprite's images at a specified animation rate.
CycleBehavior = function (duration, interval) {
this.duration = duration || 0; // milliseconds
this.interval = interval || 0;
this.lastAdvance = 0;
};
CycleBehavior.prototype = {
execute: function(sprite, time, fps) {
if (this.lastAdvance === 0) {
this.lastAdvance = time;
}
// During the interval start advancing if the interval is over
if (this.interval && sprite.artist.cellIndex === 0) {
if (time - this.lastAdvance < this.interval) {
sprite.artist.advance();
this.lastAdvance = time;
}
}
// Otherwise, if the behavior is cycling, advance if duration is over
else if (time - this.lastAdvance > this.duration) {
sprite.artist.advance();
this.lastAdvance = time;
}
}
};
|
The cycle behavior will work with any sprite that has a sprite sheet artist, meaning the behavior is not specific to Snail Bait and so can be reused in a different game. The sprite-specific run behavior in Listing 4 has a lot in common with the game-unspecific cycle behavior in Listing 9; in fact, the cycle behavior was derived from the run behavior. (The run behavior could be a more general cycle behavior, but the run behavior also takes into account the runner's animation rate.)
Individual behaviors encapsulate specific actions such as running, pacing, or sparkling. You can also combine behaviors for more complicated effects; for example, as the snail paces back and forth on its platform, it periodically shoots snail bombs, as shown in Figure 5:
Figure 5. The snail shooting sequence
The snail shooting sequence is a combination of three behaviors:
paceBehaviorsnailShootBehaviorsnailBombMoveBehavior
paceBehavior and snailShootBehavior are associated with the snail; snailBombMoveBehavior is associated with snail bombs. When Snail Bait creates sprites, it specifies the first two behaviors in the Sprite constructor, as you can see in Listing 10:
Listing 10. Creating snails
SnailBait.prototype = {
...
createSnailSprites: function () {
var snail,
snailArtist = new SpriteSheetArtist(this.spritesheet, this.snailCells);
for (var i = 0; i < this.snailData.length; ++i) {
snail = new Sprite('snail',
snailArtist,
[ this.paceBehavior,
this.snailShootBehavior,
new CycleBehavior(300, // 300ms per image
1500) // 1.5 seconds between sequences
]);
snail.width = this.SNAIL_CELLS_WIDTH;
snail.height = this.SNAIL_CELLS_HEIGHT;
snail.velocityX = this.SNAIL_PACE_VELOCITY;
snail.direction = this.RIGHT;
this.snails.push(snail); // Push snail onto snails array
}
},
};
|
Every 1.5 seconds, the snail's CycleBehavior cycles through the snail's images in the sprite sheet, shown in Figure 6, and displays each image for 300 ms, which makes it look as though the snail is periodically opening and closing its mouth. The snail's paceBehavior moves the snail back and forth on its platform.
Figure 6. Sprite sheet images for the Snail shooting sequence
Snail bombs are created by the armSnails() method, shown in Listing 11, which Snail Bait calls when the game begins. That method iterates over the game's snails, creates a snail bomb for each snail, equips each bomb with a snailBombMoveBehavior, and stores a reference to the snail in the snail bomb.
Listing 11. Arming snails
SnailBait.prototype = {
...
armSnails: function () {
var snail,
snailBombArtist = new SpriteSheetArtist(this.spritesheet, this.snailBombCells);
for (var i=0; i < this.snails.length; ++i) {
snail = this.snails[i];
snail.bomb = new Sprite('snail bomb',
snailBombArtist,
[ this.snailBombMoveBehavior ]);
snail.bomb.width = snailBait.SNAIL_BOMB_CELLS_WIDTH;
snail.bomb.height = snailBait.SNAIL_BOMB_CELLS_HEIGHT;
snail.bomb.top = snail.top + snail.bomb.height/2;
snail.bomb.left = snail.left + snail.bomb.width/2;
snail.bomb.visible = false;
this.sprites.push(snail.bomb);
}
},
};
|
The snail's snailShootBehavior shoots the snail's snail bomb, as shown in Listing 12:
Listing 12. Shooting snail bombs
SnailBait.prototype = {
...
this.snailShootBehavior = { // sprite is the snail
execute: function (sprite, time, fps) {
var bomb = sprite.bomb;
if (! bomb.visible && sprite.artist.cellIndex === 2) {
bomb.left = sprite.left;
bomb.visible = true;
}
}
},
};
|
Because the snailShootBehavior is associated with the snail, the sprite passed to the behavior's execute() method is the snail.
A snail maintains a reference to its snail bomb, so the snailShootBehavior accesses the bomb through the snail. The snailShootBehavior then checks to see if the snail's current image is the one on the far right in Figure 6, meaning the snail is on the verge of opening its mouth; if that's the case, the behavior puts the bomb in the snail's mouth and makes it visible.
Shooting the snail bomb, therefore, involves positioning the bomb and making it visible under the right conditions. Subsequently moving the bomb is the responsibility of the snailBombMoveBehavior, shown in Listing 13:
Listing 13. Snail bomb move behavior
SnailBait = function () {
this.SNAIL_BOMB_VELOCITY = 450,
...
};
SnailBait.prototype = {
this.snailBombMoveBehavior = {
execute: function(sprite, time, fps) { // sprite is the bomb
if (sprite.visible && snailBait.spriteInView(sprite)) {
sprite.left -= snailBait.SNAIL_BOMB_VELOCITY / fps;
}
if (!snailBait.spriteInView(sprite)) {
sprite.visible = false;
}
}
},
|
As long as the snail bomb is in view, the snailBombMoveBehavior moves the bomb to the left at a rate of snailBait.SNAIL_BOMB_VELOCITY (450) pixels/second. Once the bomb has moved out of view, the behavior makes the bomb invisible.
In the next article in this series, I delve further into time and behaviors by examining the runner's jump behavior. You'll see how to implement a JavaScript stopwatch to time the jump. That fundamental technique — timing animations — is something that you'll use a lot in your own games.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample code | j-html5-game5.zip | 1.2MB | HTTP |
Information about download methods
Learn
-
Core HTML5 Canvas: (David Geary, Prentice Hall, 2012): David Geary's book offers extensive coverage of the Canvas API and game development. Also check out the
companion website and blog.
-
Snail Bait: Play Snail Bait online in any HTML5-enabled browser (best in Chrome version 18+).
-
Mind-blowing apps with HTML5 Canvas: Watch David Geary's presentation from Strange Loop 2011.
-
HTML5 Game Development: Watch David Geary's presentation from the Norwegian Developer's Conference (NDC) 2011.
-
Platform games: Read about platform games at Wikipedia.
-
Side-scroller video games: Read about side-scroller video games at Wikipedia.
-
Strategy pattern: Check out Wikipedia's article on the Strategy design pattern.
-
Aggregate Objects via Components: This Replica Island blog post discusses Replica Island's use of behaviors (referred to as game components).
-
HTML5 fundamentals: Learn HTML5 basics with this developerWorks knowledge path.
Get products and technologies
-
Replica Island: You can download the source for this popular open source platform video game for Android.
Discuss
- Get involved in the developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

The author of Core HTML5 Canvas, David Geary is also the co-founder of the HTML5 Denver User's Group and the author of eight Java books, including the best-selling books on Swing and JavaServer Faces. David is a frequent speaker at conferences, including JavaOne, Devoxx, Strange Loop, NDC, and OSCON, and he is a three-time JavaOne Rock Star. He wrote the JSF 2 fu and GWT fu article series for developerWorks. You can follow David on Twitter at @davidgeary.



