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.
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:
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.
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.
Screenshot 1: Multiple dogs making random decisions in structured behaviors.
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  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  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!
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:
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.
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.
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.
Figure 4: Using assumptions to monitor a sequence of actions.
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.