Use the Behavior System to share diverse functionality between objects in your game.
The Behavior System is the single most important idea I’ve had as a game programmer. They are extremely useful on their own, but they also inspired other powerful systems, most notably Robot Functions and the Scripted Event System. I hope that I can do them justice in this article.
What are Behaviors?
Behaviors are small objects that add functionality to a parent object. The parent has a reference to the behavior, and the behavior has a reference to the parent, set when the behavior is initialized. The behavior manipulates the data of it’s parent object to produce a desired set of functionality. The real strength behind behaviors is that they allow totally different objects to share the same capabilities.
For example, the same code (behavior) that slides your menus onto the screen also moves around enemies in the game world. The same behavior that animates your walking player character also animates a glowing effect behind your powerups. The same behavior that makes your widgets clickable can be applied to any rectangular object, and they become clickable too, with just a couple lines of code.
If you are familiar with component-based designs, Behaviors could be considered a hybrid between a “pure component” system and a typical class hierarchy. Unlike a pure component system, Behaviors supplement a traditional class hierarchy (rather than replacing it), giving you the full freedom to use “components” AND inheritance to generate objects in your game.
Benefits of Behaviors
Behaviors help you transform huge amounts of previously object-specific code into library-code, which can now be shared by many diverse entities in your game, including menus, cinemas, characters, effects, etc. When you add a behavior to an object, you save the time of re-implementing the same functionality again. You avoid bugs and a lot of testing, because you know that the behavior already works elsewhere. If you add more content to the behavior, all of the present and future parents of the behavior get to make use of the new content. Behaviors also keep your parent classes shorter, more organized, and easier to understand by collecting some of the different things that the parent can do and distilling them down into easily manageable objects.
Origins of the system
The earliest inspiration for behaviors came from an article that I read several years ago about objects in a first-person shooter (I’ll try to find the article again so that I can credit the author). I don’t remember the specifics, but basically the programmer was describing how objects in the game like the player, pickups, grenades, and enemies don’t follow a normal class heirarchy (e.g., they could all extend GameObject and branch as they get more specific), but instead are an aggregate of different chracteristics, like “affected by gravity” and “destructable.”
This allows objects to only have the attributes that they need. Otherwise, all of the functionality for every object would need to be contained in the top superclass, GameObject, making each individual object much heavier because they have to ignore a bunch of functionality that they aren’t actually using. Either that, or you’d have to copy and paste a lot of code to get the objects to behave how you want, which is much worse. This idea seemed really cool, but I couldn’t figure out a good way to implement it.
The main motivation for behaviors in their current form came near the end of Cash Cow’s development when I realized that I had written slight variations on the same code in many different classes. For example, I wrote code to slide the coins to a specific point when they combine, and I wrote similar code to slide the menus in and out. I knew that writing these slight variations on the same code again and again required a whole lot more time, testing, and frustration than if the menus and the coins could somehow share this “sliding” behavior, but how?
Thus, the Behavior System was born, as a solution to both of the above situations. Not only do behaviors add attributes to their parents, they can also add more complex capabilities related to game logic, motion, rendering options, and more. It might make more sense with some examples (I’ll write all the examples in this article in Java, since I first implemented them in Java).
Behavior Examples
One really common behavior is animation. In this case, I mean animating by periodically changing an object’s image to the next image in a pre-defined set of frames, like frames of a person walking. Many objects in your game need to do this kind of animation, but they might have nothing else in common. For example, in Primate Panic, the propellers on the plane in the first cinema use an animation behavior to spin, and the chameleons use an animation behavior when the stick out their tongues. The monkeys use one as they climb vines, the humans use one when the throw a monkey, and… you get the idea, they’re everywhere. They’ve saved me a ton of time.
While some behaviors are general purpose, like animation, others are more specific and will probably only ever operate on one class. This means that instead of accepting a simple interface as the “parent” object, it would take a reference to a single class, like “Monkey.” In these cases, behaviors are still very useful because they allow you to greatly simplify and organize the capabilities of the parent.
Here are some behavior examples from Primate Panic (there are dozens more):
- A WalkBehavior syncs up the player character’s walking frame with the distance in the game world that s/he has actually moved, making it look like the player is really walking on the ground. Monkeys use this too.
- When you click an area on the overworld map, FlipBehaviors “flip” the boxes with all the level icons while changing their image.
- When a monkey pulls on a trap door handle, a TrapDoorBehavior moves the monkey and pull bar down, opens the trap door, and waits a couple of seconds before telling the monkey to drop and the trap door to close.
- A FadeBehavior periodically fades the ghosts in and out, with a little extra code in the Ghost class to activate and deactivate their collision box in the process.
- A ButtonBehavior is responsible for nearly all the logic in the menu buttons. ButtonBehavior detects the mouse’s presence, stores button state data, and notifies the parent GUI when it is clicked (see Object-Oriented Programming: The GUI Stack.)
Example 2 is a case of a fairly general behavior. Any object that can scale and change images can do the same flipping effect as those icons. This means that if I ever wanted, I could make the monkeys flip like that, the GUI windows, the raft, etc. Same deal with the FadeBehavior for ghosts: any object that can change its alpha value can fade in and out just like the ghosts. All it has to do is implement an interface, CanFade, that contains method stubs for getting and setting its alpha.
As for the ButtonBehavior, any object with just a couple important functions, like functions to get its location and dimensions, can become a button just by containing and updating its own ButtonBehavior. Again, this means that, in addition to whatever else they do, spacial objects in any part of your game could also operate as buttons just by implementing a simple interface and updating a ButtonBehavior object. In fact, most objects in your game that store their coordinates and size probably already implement the interface necessary to be a button.
What’s the point? Without any extra work, a monkey picking fruit could be a button. Folliage could be a button. The shopkeeper could be a button. Darkfellow’s grave in the last cinema could be a button. Buttons could be buttons. The list goes on and on.
Note: in most of the last examples, you probably wouldn’t need to use a ButtonBehavior, but instead a simpler variation that just detects when it’s parent is clicked. All the additional state data and graphic changes of actual buttons is more complicated than most clickable things use. That’s why I have another behavior, ClickableBehavior, which is a simplification of ButtonBehavior for widgets like the “buttons” on the title screen that don’t actually get depressed like normal buttons when clicked.
Read on for implementation–>
Implementing Behaviors
“Behavior” is an abstract superclass that all of your actual behaviors will extend. Having all your behaviors extend this one class is very important–you’ll see why in the context of the other systems. The Behavior class just contains some basic variables and methods common to all behaviors. The most important variable is an active boolean, which provides an easy way to turn the behavior on and off. Important methods are update(), redraw() (although most behaviors don’t use redraw, just leaving it empty), and eventCallback(), which I’ll explain in a sec. So let’s see some code. This is a basic example of how to implement the Behavior superclass:
public abstract class Behavior implements ActionEvent
{
// active is true if the bevahior should update() and redraw()
private boolean active;
// not all behaviors have listeners, but many do.
private ActionListener listener;
// Call this method from outside the behavior.
public void update()
{
// Don't update if the behavior is not active.
if (active == false) return;
updateMe();
}
private void updateMe()
{
// subclasses should override this update() method so that they
// don't all have to do the "if (active == false)" line in their update()
}
public void redraw()
{
// Don't redraw if the behavior is not active.
if (active == false) return;
redrawMe()
}
private void redrawMe()
{
// subclasses override this redraw(), for the same
// reason that they override updateMe()
}
// The behavior calls this when it's finished.
public void behaviorDone()
{
// This MUST be before the callback on the next line.
active = false;
if (listener != null) listener.eventCallback(this);
}
public void setActive(Boolean a) { active = a; }
public boolean getActive() {return active; }
// These methods implement the ActionEvent interface.
// They allow the someone to listen to the behavior.
public void setListener(ActionListener l) {listener = l}
public ActionListener getListener() {return lisener}
}
One thing I haven’t mentioned yet is why Behavior implements the ActionEvent interface and has a reference to a listener. This is mostly for use in the Scripted Event System, as I’ll show in that article, but is also helpful without any other systems. You’ll find that a lot of your behaviors have a defined beginning and an end, like how the FlipBehavior ends when the flipping action is finished. For these behaviors, it makes sense that some object (usually the behavior’s parent) will want to be listenting for the Behavior to finish so that it can either reset the behavior or let the behavior stop and take some other action.
The ActionListener interface just contains one method for listening to these generic event callbacks, called “eventCallback(ActionEvent e).” The calling behavior is passed into this method so that the listener can figure out which behavior actually made the callback by comparing it to the behavior references that it owns.
A great example of a behavior that does use a listener is AnimationBehavior. Often times, you want the object to cycle through it’s set of animation frames, stop when it’s done, and inform its parent. Here is a very basic implementation of AnimationBehavior:
public class AnimationBehavior extends Behavior
{
private Drawable parent;
private Image[] animation;
private double frameDuration;
private int currFrame;
private double currTime;
public AnimationBehavior(Drawable setParent, double[] setFrameDuration,
Image[] setAnimation, ActionListener setListener)
{
parent = setParent;
frameDuration = setFrameDuration;
animation = setAnimation;
listener = setListener;
currFrame = 0;
active = true;
}
// Overriden from Behavior
private void updateMe()
{
currTime += GET_TIME_PASSED(); // since the last update
// time to change frames
if (currTime >= frameDuration)
{
currTime -= frameDuration;
currFrame++;
// Advance the frame of the parent.
if (currFrame < animation.length)
{
parent.setImage(animation[currFrame]);
}
// There are no frames left. The animation is done.
else
{
behaviorDone(this);
// active is now false, the parent is informed.
}
}
}
// Call this to start the behavior over.
public void reset()
{
active = true;
currFrame = 0;
}
}
// Here's the tiny Drawable interface that the parent implements:
public interface Drawable
{
public void setImage(Image newImg);
public Image getImage();
}
Once you initialize this behavior with a parent and a set of images, all you have to do is call update() and the parent object will cycle through its animation frames. When the animation is finished, it will inform its listener (if one is set) and stop animating on its own.
Here's another fairly common behavior that doesn't use a listener and doesn't have a defined stop time. This one is used to move your objects around in space with acceleration and velocity.
public class MotionBehavior extends Behavior
{
private double xVel = 0, yVel = 0;
private double maxXVel = 10000, maxYVel = 10000;
private double xAccel = 0, yAccel = 0;
private CanMove parent;
public MotionBehavior(CanMode setParent)
{
active = false;
parent = setParent;
}
public void updateMe()
{
xVel += xAccel;
yVel += yAccel;
// Enforce the maximum velocities. When really coding this,
// you'd need to make sure that the velocities don't go too
// far negative either.
if (xVel > maxXVel) xVel = maxXVel;
if (yVel > maxYVel) yVel = maxYVel;
parent.getX() = parent.getX() + xVel;
parent.getY() = parent.getY() + yVel;
}
// Now just add getters and setters for the
// velocities, max velocities, and accelerations.
public void setXVel(double v) {xVel = v;}
...
}
// And here's the CanMove interface that the parent implements:
public interface CanMove
{
public double getX();
public double getY();
public void setX(double x);
public void setY(double y);
}
Now that you've seen how some common Behaviors work, you probably want to see the payoff.
Behaviors in Action
Here is a very simplified example of Monkeys use behaviors to perform a few tasks.
public class Monkey extends GameObject implements ActionListener
{
// NOTE: extending GameObject gives Monkeys a bunch of generic
// methods like getX(), getY(), setImage(), etc, and implements
// simple related interfaces, like Moves, Draws, etc.
private AnimationBehavior walk;
private AnimationBehavior jump;
private MotionBehavior motion;
private ClickBehavior clickMe;
private int state = 0;
// states
private static final int
FOLLOWING = 0, // following the character
CLIMING = 1, // climbing a vine
FALLING = 2; // free-falling or rising after a throw
public Monkey()
{
// The monkey is both the parent and the listener.
walk = AnimationBehavior(this, 5, Graphics.monkeyWalkImgs, this);
walk.setActive(true);
jump = AnimationBehavior(this, 5, Graphics.monkeyJumpImgs, this);
jump.setActive(false);
// This behavior operates the motion of the monkey while it's
// in the air.
motion = MotionBehavior(this);
motion.setActive(false);
// Reports when the monkey is clicked.
// Parameters: (parent, listener).
clickMe = ClickBehavior(this, this)
clickMe.active = true;
}
public void update()
{
// Only the behaviors that are active will actually update.
// Of course, you could do a switch on the monkey state and
// pick the ones you want to update that way.
walk.update();
jump.update();
motion.update();
clickMe.uopdate();
if (state == FALLING && groundCollision() == true)
{
state = FOLLOWING;
motion.setActive(false);
walk.reset();
}
}
public void throwMe()
{
state = FALLING;
motion.setActive(true);
motion.setYVel(-15); // propell the monkey upward
motion.setYAccel(GRAVITY_CONSTANT); // apply gravity
jump.reset();
}
// Reset the walk animation here.
// You could also build a repeating option into the behavior
// itself.
public void eventCallback(ActionEvent caller)
{
if (caller == walk)
{
walk.reset();
}
else if (caller == clickMe)
{
GameSounds.playMonkeyYell();
}
}
}
What we've got so far is just a fraction of what monkeys need to do. But let's take a look at all that we've accomplished already, with a very small number of statements:
- The monkey plays its whole jump animation automatically when it's thrown, and stops it when the animation is over.
- The monkey plays its walk animation while it's walking, and repeats the animation by listening to its callback.
- MotionBehavior provides upward velocity to the monkey when it's thrown, and automatically applies gravity to the monkey while it's in the air.
- The monkey will start walking again once it lands on the ground and engage its walking animation.
- ClickBehavior automatically detects when the monkey is clicked, and the monkey makes a sound. Okay, this isn't actually in the game, but I wanted to show you how easy it is to make any object into a widget.
The behaviors have kept this Monkey class short, organized, bug-resistant, flexible, and simple to code. The behavior system standardizes the interface to all of this additional functionality so that you don't have to think about it once you've coded it the first time.
Behaviors Vs. Robot Functions
Robot Functions and Behaviors are similar in that they extend the capabilities of a parent object. They also both make a callback to a listener when they finish their task. Here are the key differences between the two:
- Robot Functions only have a access to one variable of the parent. Behaviors may have access to all of the parent's variables and methods.
- Robot Functions always have a pre-determined end, but not all behaviors do.
- Robot Functions just define a special curve for altering a value, whereas Behaviors can do much more with their input.
- A Behavior may contain Robot Functions to complete its work, but Robot Functions would never contain Behaviors.
When all you need to do is operate on specific variables over time (such as scale, alpha, position, etc.) use Robot Functions. When you want more complex capabilities or need to coordinate several Robot Functions, use Behaviors.
---
Behaviors by themselves have been a huge asset to my game programming, but I've only shown you HALF of the power of behaviors. The other half comes when they are used in the context of the Scripted Event System. If you like the concept behind Behaviors and Robot Functions, you'll really want to check out that article to truly see them in action.
Want to read more? Here's a link to the first ever discussion of Behaviors at Gamedev.net:
http://www.gamedev.net/community/forums/topic.asp?topic_id=492938&PageSize=25&WhichPage=1