Tutorial
icon-dog4

How to Affect Behavior Choices Dynamically Using Instant Conditional Checks

Alex J. Champandard on January 16, 2008

Sensory systems have many different roles, as discussed in the last article. However, the best place to start is to implement a single condition that checks information from the world, and affects the decisions in a static behavior tree. For example, picking a fixed growling reaction to another dog when it’s nearby.

This tutorial is part of a series which focuses on building simple dog behaviors in a simulation game. This week’s article looks at how to build instant conditional checks like isDogNearby in a very similar way to the other actions. Also, you’ll be able to read a few tips for how to architect the source code your game logic, as this becomes more important when building up the complexity in a sensory system.

Video 1: Both dogs start off facing each other by design, then select behaviors randomly. At 0:13 the gray dog picks a new behavior, and since the brown dog is nearby, it picks a growling behavior. Once the brown dog has finished randomly rolling over, it also growls back at the gray dog. They both then back off around 0:17 as part of the same reaction behavior, and resume selecting behaviors randomly as they are out of range again.

Instant Condition

As you learned last time, certain behaviors require preconditions. These are conditions that should be checked just before the behavior runs to make sure it’s applicable. For example, it’s necessary to check if another dog is nearby before starting a behavior that reacts to it!

For this to work, the conditions must be implemented so they:

  1. Complete instantly if the condition is currently true.

  2. Fail silently if the condition doesn’t match at the moment.

The difference between the other actions (e.g. Rotate, Translate, Animate) and these instant conditions is that they last much longer, obviously. However, desipite this difference, it’s wise to use the exact same API to keep the implementation simple. In practice, both actions and conditions implement the same Task object, which means you can plug them together into composite behaviors interchangeably.

Behavior Condition

Screenshot 2: Two dogs within range of each other growling!

Code Structure

Building a sensory system means that more data will be exchanged within the engine. This can cause various dependency problems if there’s no formal architecture, and you quickly end up with a spaghetti mess as the codebase grows. Of course, having this structure is also very useful when you build your AI actions, and not only conditions.

The whole topic of structuring game logic is certainly worth another whole series of tutorials (it can be a controversial topic too), so this article sticks to describing what’s necessary to support the simple behaviors in the video, which is good starting advice generally. What you need to be aiming for is this:

  • Establish layering in the codebase, with modular libraries for graphics, animation audio, AI core, scene graph, etc.

  • Build a very simple Entity class that has optional components from each of these libraries.

  • Implement Actions and Conditions that depend on the Entity but only access the components they need.

In practice, you can implement a nice modular component system rather trivially in C++ using pointers to forward declared types.

namespace alive { class Brain; } // AI
namespace Ogre { class SceneNode; } // World Graph
namespace Ogre { class Entity; } // Model & Animation

struct Entity
{
  alive::Brain* m_pBrain;
  Ogre::SceneNode* m_pNode;
  Ogre::Entity* m_pEntity;
};

If you feel so inclined, you should add accessors to set and get each of these objects safely, and possibly give them a NullObject by default. If you’re feeling more ambitious with component systems, you could try something more object oriented using an abstract component type with a visitor — but it’s not entirely necessary here!

Game Entity Class


Figure 1: The Entity class is composed of three lower-level classes, each from a separate library.

In the game logic, you’ll need to store a reference to all active Entity objects. Using an normal array, or a std::vector in C++, is a perfectly acceptable first choice. If it turns out to be to slow to access specific entities by position or type, then optimizations can be easily added later.

Implementing a Simple Condition

In this tutorial, the behaviors only need one condition for isDogNearby. However, entity checks are very common so it’s wise to implement all common logic once and for all in a base class. (Node, a Condition in the following code is defined to being an Action since there are no differences.)

struct EntityCheck : public Condition
{
  // A customizable entry point (a.k.a. visitor) for 
  // application logic.  
  DEFINE_VISITABLE_AS(EntityCheck);

  // When initializing this class, the game logic uses a
  // visitor to call the setup() function.
  void setup(Entity&, Entities&);

  // Store a pointer to the entity bound to this condition.
  Entity* m_pEntity;
  // Also need to access all other entities in the world.
  Entities* m_pEntities;
};

Any class that derives from this will be initialized with the correct entities executed within a behavior tree. In particular, the isEntityNearby condition simply overrides the main execute() function which implements the logic for the condition:

Status EntityNearby::execute()
{
  if (m_pEntity == NULL || m_pEntities == NULL)
  {
    return FAILED;
  }

  Vector3 position = m_pEntity->m_pNode->getPosition();

  // Loop over all the objects in the world.
  Entities::iterator i = m_pEntities->begin();
  for (; i != m_pEntities->end(); ++i)
  {
    Entity* other = *i;
    if (m_pEntity == *i || other->m_pDogNode == NULL)
    {
      continue;
    }

    // Check the distance to object compared to the reference.
    Vector3 d = (position-other->m_pNode->getPosition());
    if (d.squaredLength() < m_Settings.getDistanceSq())
    {
	  return COMPLETED;
    }
  }

  return FAILED;
}

That’s nearly the simplest code you can write to check if another entity is nearby. There’s lots of room for improvement in performance and features, but it’s good enough for a first design.

Responding to Other Dogs

With the isEntityNearby condition implemented, it’s possible for the dogs to react to each other. Here are the two behaviors chosen for the video, which seem the simplest and most appropriate:

  1. A fighting animation, which looks like the dog is biting and shaking something, combined with a growling sound.

  2. A rather short barking animation which can be looped multiple times, combined with the typical woof sound.

The most important part, however, is to make sure the dogs don’t get stuck forever growling and barking at each other. Keep in mind that the dogs don’t have any memory yet, and in fact, they’ve only just started seeing! So the only way to prevent problems is the same solution that was applied to keep the random behaviors consistent: structuring the behavior tree.

This time, after barking or growling, each dog with turn slightly and move backwards. This is achieved by playing a limping animation in reverse, which doesn’t look too bad. (See these animation tricks for ways to get the most out of your assets.)

Growl Behavior Tree

Figure 3: The behavior tree for the new growl reaction. It’s a sequence that starts with a condition, then a parallel for sound and animation, and finally a parametric motion for limping backwards.

The sequence that represents this behavior can be put in any of the top-level selectors of the behavior, depending on when it should be activated. If you’re unfamiliar with any of the concepts in this behavior tree, see the previous tutorials.

Thinking Ahead

In summary, you can build simple instant conditions just like actions; the difference is that they check for information inside components of the game entities, using the code and data from other libraries (e.g. the scene graph in this case). When you plug such conditions at the start of sequences, they act as pre-conditions that dynamically filter the activation of any behavior. This essentially makes the choices in the tree subject to the constraints of the environment.

Of course, there’s lots of room for improvement in future tutorials and essays, including:

  • How to allow information from the sensory system to interrupt behaviors and not only affect upcoming decisions.

  • How behaviors can adapt parametrically to objects in the environment.

Sensory System

Screenshot 4: The condition isDogNearby triggers a static reaction.

Stay tuned to AiGameDev.com for more in this series. Questions welcome of course!

Discussion 0 Comments

If you'd like to add a comment or question on this page, simply log-in to the site. You can create an account from the sign-up page if necessary... It takes less than a minute!