RiverMan Media

RiverMan Media 1

Newsletter

Keep up with info on our latest games, articles, and other projects. You can unsubscribe at any time.

Subscribe

XML RSS 2.0 RSS 2.0 

Object Oriented Game Programming: The Behavior System
Object Oriented Game Programming: The Behavior System - Implementation
Written by Paul Stevens   
Thursday, 01 May 2008 07:04
Article Index
Object Oriented Game Programming: The Behavior System
Implementation
Behavior case studies
All Pages

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.
 


 
Comments (2)
Just a thought
1 Sunday, 25 July 2010 18:43
You could have the AnimationBehavior be a listener, besides an event (inherited from behavior, though how is an Action event different from an Event?). I thought, when the motion behavior for jump starts, it could fire off an event indicating it started, so the animation could start playing. Having the motion automatically synced with the animation seemed like a good idea, though I'm still trying to think of the possibilities and if this is good idea or not.

Anyway, love this post, great ideas!
RE: Just a thought
2 Wednesday, 28 July 2010 00:11
RiverMan_Paul
Thanks for writing! I'm glad you enjoyed the article.

When I think of an Event, I think of any object whose purpose is to represent something that "just happened", and exists to inform an interested listener. An example would be a MouseEvent--when the mouse moves, the system would create a MouseEvent object than describes where the mouse moved to, and may contain other information (such as where it moved from, the current status of the mouse buttons, etc.).

ActionEvents use the same general idea in the context of Behaviors. When the Behavior (which IS an ActionEvent) is finished running, it informs its listener, and passes itself to the listener in the process. Code-wise, the fundamental reason that Behaviors, RobotFunctions, and Effects are all ActionEvents (meaning they implement the ActionEvent interface) is so that ActionLists and ActionQueues can contain ActionEvents as a single type. An ActionList, for example, is really a collection of RobotFunctions, Behaviors, and Effects, but as far as it knows, they are all just ActionEvents. For a full explanation of this ActionEvent idea, check out the Scripted Event System article (http://rivermanmedia.com/programming/28-object-oriented-game-programming-the-scripted-event-system).

I think you are on the right track with the idea to have an AnimationBehavior also be a listener so that it can sync itself up with other aspects of the animation (such as motion). What I might recommend for this case would be to subclass AnimationBevahior and add that idea in (e.g., JumpAnimation extends AnimationBehavior and also implements ActionListener). This way you can use the AnimationBehavior as-is, but also have a special version that's specific to jumping.

Very cool idea!

Add your comment

Your name:
Your website:
Subject:
Comment: