Using Conditions as the Building Blocks of a Sensory System

Alex J. Champandard on December 26, 2007

Wednesdays on AiGameDev.com is dedicated to tutorials. This article continues the long-running series which focuses on building simple dog behaviors for a simulation game.

At this stage, the decision making process is in place, but it’s based on random decisions instead of taking into account any information from the world. This, in effect, is where the sensory system fits in.

Context-Sensitive Behaviors

The purpose of the sensory system is to make the AI logic more dynamic and responsive to its environment. In a behavior tree, this is achieved in two ways:

  1. Allow decisions in a static tree to be informed by sensory data. For example, two fixed idle and playing behaviors as sub-trees are selected depending on the presence of another dog.

  2. Use data from the world to parameterize behaviors dynamically. For example, an investigate behavior must varies based on the location of the event.

The first option is the next logical step, and the simplest to implement also if you’re getting started with your AI. Over the next few weeks, you’ll learn how this can be achieved in practice.

Group Behaviors

Screenshot 1: Multiple dogs making random decisions in structured behaviors.

Introducing Conditions

To achieve this context-sensitiveness in the behaviors you’ll need to use conditions in your tree, for example isDogNearby or isTired. Essentially, conditions serve multiple purposes as they:

  • Serve as leaves in the behavior tree, which are implemented natively in C++.

  • Check information from the world procedurally by accessing the world model.

  • Either succeed or fail, passing back the status to the parent task (like all other tasks do).

These conditions are the read-only counterpart of actions, and are extremely flexible. With the right conditions, it’s possible to implement any kind of AI logic simply using the sequences and selectors that are already in place. Their role is two fold:

  • Succeed silently to cause the nearest sequence to move on to the next step. This is useful for enabling behaviors when they are applicable.

  • Fail cleanly to cause the nearest selector to fall back to a different subtree. This is useful to disable behaviors when they are not appropriate.

Conditions that follow these guidelines can replace random decisions in the tree and make them more context-sensitive.

Designs for First Sensations

In the comments for the previous article, Bjoern Knafla suggested [1] using social interaction as a proof of concept for sensory systems:

“For example if the low level sensory system tells a dog about another dog in his neighborhood (via a spatial sensory system). […] Based on this knowledge he might decide to run to the other dog and start a ‘lets play’ behavior with the associated animation.”

Mikkel proposed [2] building on the existing behaviors:

“Let scent accumulate over the time at a dogs position (in some relatively coarse-grained grid). […] Each cycle, all dogs deposits a fixed amount of smell at it’s current position, while smell either dissipates or disappears from all positions. Support this with a tracking behavior that […] has the dog following a trail of smell using a combination of the sniffing and sneaking animations.”

This is a great way to improve the randomly defecating behaviors and build something much more purposeful!

Actor Interactions

Screenshot 2: Potential for sensing other dogs nearby.

Location Sensitive Behaviors

The common theme behind these suggestions is allowing the AI to detect certain areas, and dynamically modulate the behaviors. This can be implemented within a few iterations. Over the next few weeks, you’ll learn how to build the AI to achieve the following:

  1. Sense annotated areas set by the designers in a data-driven way throughout the world. This is useful for triggering certain behaviors as Mikkel suggested.

  2. Detect other entities in the world (primarily dogs) that move around dynamically. These senses are useful for building up group behaviors as Bjoern mentioned.

Now, how are conditions used to build such location-sensitive behaviors?

Designing Conditional Behaviors

The majority of the conditions are to be used as pre-conditions for normal behaviors. For example, the Sleeping behavior would only be executed if the condition isTired is true.

In practice, this is done by building behaviors as sequences. The first few tasks in the behaviors are be conditions that check the necessary preconditions and bail out if they are not met. The last tasks of the behaviors would instead be actions that execute if the sequence hasn’t bailed out already.

Sequence Pre-conditions

Figure 3: Using conditions at the start of sequences to verify preconditions.

Other conditions can be used as assumptions for normal behaviors. For example, a Playing behavior would only be executed while the condition isDogNearby remains true.

In practice, this is achieved by attaching a parallel above the sequence of actions, and connecting conditions to it. When either condition fails because the assumption is broken, the whole sub-tree bails out.

Sequence Assumptions

Figure 4: Using assumptions to monitor a sequence of actions.

Implementation Strategy

Based on these two uses of conditions, you’ll understand the need for two different modes for executing conditions:

  • Monitoring — Keep executing until the condition changes. These are useful for maintaining assumptions while behaviors are running, and bailing out otherwise.

  • Instant Check — Just succeed or fail immediately based on the condition. These are useful for placing as assertions at the start of sequences.

Next week, you’ll learn more about the implementation of these conditions, and notably how this allows both an event-driven and polling approach for efficiency and flexibility.

Discussion 3 Comments

FuriCuri on December 27th, 2007

In my implementation of BT AI system I embed condition in each BT node and call it StartCondition. The idea was to unify all conditions, cause there are "Pause" and "Terminate" conditions too. So I can set start/pause/terminate condition to any BT node. Maybe you'll find this approach more efficient or not - just want to hear your opinion :)

alexjc on December 28th, 2007

Well, you generally have two ways of approaching the implementation of your behavior trees (and conditions): [LIST] [*]Make your node/task API complex enough to support every feature you need. [*]Keep the API as simple as possible and try to build up the same complexity by combination. [/LIST] It sounds like you went for 1), which is definitely the most common in the games industry. I prefer 2) personally as it keeps things much simple in the codebase. So in this case, I have conditions and actions as derivatives of the same Task API, and then use them in sequences and parallels interchangeably, which provides a lot of flexibility. The features you mentioned about your conditions I used in my Tasks API (for actions too): start, suspend, resume, stop, all with observers, etc. I guess it all works out the same in the end, so it's just a matter of preference. Does that answer your question(s)? Alex

FuriCuri on December 29th, 2007

Yeah, thanks Alex. Clear and straight answers as always - just what I need :)

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!