Download as pdf or txt
Download as pdf or txt
You are on page 1of 16

HTML5 2D game development: Implementing Sprite

behaviors
Equipping Snail Bait's characters with behaviors

David Geary January 08, 2013

In this series, HTML5 maven David Geary shows you how to implement an HTML5 2D video
game one step at a time. In this installment, you'll learn how to implement the essence of any
video game: sprite behaviors.

View more content in this series

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:

© Copyright IBM Corporation 2013 Trademarks


HTML5 2D game development: Implementing Sprite behaviors Page 1 of 16
developerWorks® ibm.com/developerWorks/

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

Manipulating time
In Braid, the protagonist Tim manipulates time, but every video game is a master at
manipulating time. In this article, you will see the undercurrent of time flowing through
behaviors. And in the next two articles in this series, I'll show you how to bend time itself

HTML5 2D game development: Implementing Sprite behaviors Page 2 of 16


ibm.com/developerWorks/ developerWorks®

to implement nonlinear motion, which is the basis for realistic motion such as running and
jumping.

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

Behavior fundamentals
Replica Island's behaviors
The idea for behaviors comes from a popular open source Android game named Replica
Island. Many of Snail Bait's graphics also come from Replica Island. See Resources for links
to the game and to a blog post in which the game's creator talks about behaviors.

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.

Runner 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

HTML5 2D game development: Implementing Sprite behaviors Page 3 of 16


developerWorks® ibm.com/developerWorks/

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:

HTML5 2D game development: Implementing Sprite behaviors Page 4 of 16


ibm.com/developerWorks/ developerWorks®

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.

The Strategy design pattern


Behaviors are an implementation of the Strategy design pattern, which encapsulates
algorithms into objects (see Resources). At run time, you can mix and match those
algorithms to assign a collection of behaviors to a sprite. Behaviors give you more flexibility
than hard-coding their algorithms directly into individual sprites.

Running
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:

HTML5 2D game development: Implementing Sprite behaviors Page 5 of 16


developerWorks® ibm.com/developerWorks/

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 runAnimationRate milliseconds, this behavior advances the
// runner's artist to the next frame of the sprite sheet.

lastAdvanceTime: 0,

execute: function(sprite, time, fps) {


if (sprite.runAnimationRate === 0) {
return;
}

if (this.lastAdvanceTime === 0) { // skip first time


this.lastAdvanceTime = time;
}
else if (time - this.lastAdvanceTime > 1000 / sprite.runAnimationRate) {
sprite.artist.advance();
this.lastAdvanceTime = time;
}
}
},
...
};

HTML5 2D game development: Implementing Sprite behaviors Page 6 of 16


ibm.com/developerWorks/ developerWorks®

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 flow of time


Like the runner's run behavior, nearly all behaviors are predicated on time. And because a
game's animation is constantly in effect, many functions that modify a game's behavior, such
as turnLeft() and turnRight() in Listing 5, do so by simply setting game variables.
When the game draws the next animation frame, those variables influence the game's
behavior.

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.

Flyweight behaviors
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

HTML5 2D game development: Implementing Sprite behaviors Page 7 of 16


developerWorks® ibm.com/developerWorks/

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;

HTML5 2D game development: Implementing Sprite behaviors Page 8 of 16


ibm.com/developerWorks/ developerWorks®

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.)

Game-unspecific behaviors
Flyweights and state
The paceBehavior can be used as a flyweight because it's stateless. It's stateless because
it stores state — each sprite's position and direction — in the sprites themselves.

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.

HTML5 2D game development: Implementing Sprite behaviors Page 9 of 16


developerWorks® ibm.com/developerWorks/

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:

• bounceBehavior
• cycleBehavior
• pulseBehavior

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.

Sparkling rubies
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.

HTML5 2D game development: Implementing Sprite behaviors Page 10 of 16


ibm.com/developerWorks/ developerWorks®

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;
}
}
};

Generalizing behaviors
It's a good idea to look for opportunities to generalize behaviors so they can be used in a
wider range of circumstances.

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

HTML5 2D game development: Implementing Sprite behaviors Page 11 of 16


developerWorks® ibm.com/developerWorks/

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.)

Combining behaviors
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:

• paceBehavior
• snailShootBehavior
• snailBombMoveBehavior

HTML5 2D game development: Implementing Sprite behaviors Page 12 of 16


ibm.com/developerWorks/ developerWorks®

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) {

HTML5 2D game development: Implementing Sprite behaviors Page 13 of 16


developerWorks® ibm.com/developerWorks/

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;
}
}
},

};

Behavior-based games
With a behavior-based game, once you've got the basic infrastructure implemented, fleshing
out the game is mostly a matter of implementing behaviors. Freed from the concerns of
the game's underlying mechanics, such as animation, frame rates, scrolling backgrounds,
and so forth, you can make your game come to life by concentrating almost exclusively on
implementing behaviors. And because behaviors can be mixed and matched at runtime, you
can rapidly prototype scenarios by combining behaviors.

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:

HTML5 2D game development: Implementing Sprite behaviors Page 14 of 16


ibm.com/developerWorks/ developerWorks®

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.

Next time
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.

HTML5 2D game development: Implementing Sprite behaviors Page 15 of 16


developerWorks® ibm.com/developerWorks/

Downloadable resources
Description Name Size
Sample code j-html5-game5.zip 1.2MB

© Copyright IBM Corporation 2013


(www.ibm.com/legal/copytrade.shtml)
Trademarks
(www.ibm.com/developerworks/ibm/trademarks/)

HTML5 2D game development: Implementing Sprite behaviors Page 16 of 16

You might also like