Behavior Trees: Complex AI Decision Making
State machines work well for small enemies, but once you need layered decisions—patrol unless alerted, investigate sounds, manage stamina, swap tactics—you quickly run into spaghetti logic. Behavior trees solve this by organizing decisions into tiny, reusable blocks. In this chapter you will build, debug, and ship a behavior tree AI that feels intelligent without becoming impossible to maintain.
What You'll Learn
- Plan behavior trees using goals, sensors, and reusable subtrees
- Understand selector, sequence, decorator, and service nodes
- Share data between nodes using blackboards and context structs
- Implement trees with code (Unity C# / Godot GDScript) or visual editors (Unreal Behavior Trees, Behavior Designer, Polarith AI)
- Test, profile, and extend trees without breaking existing behaviors
Before diving in, make sure you've completed Basic AI Concepts: State Machines and Decision Trees and Simple AI Behaviors: Following and Avoiding. We'll build on those fundamentals.
Behavior Tree Building Blocks
| Node Type | Purpose | Example |
|---|---|---|
| Root | Entry point, usually a single node | Selector |
| Composite | Owns children and controls execution order | Selector, Sequence, Parallel |
| Decorator | Wraps a child to add conditions or loops | Blackboard Check, Cooldown, Repeat |
| Leaf / Task | Runs gameplay code | MoveTo, PlayAnimation, Attack |
| Service | Runs periodically to update data | SensePlayer, SamplePath, SetFocus |
Keep each task focused on one responsibility—animation, navigation, combat, etc. Small tasks are easier to reuse and test.
Execution Flow Cheat Sheet
- Selector (OR): runs children left to right until one succeeds (use for choosing behaviors).
- Sequence (AND): runs children left to right until one fails (use for multi-step actions).
- Parallel: runs children simultaneously (great for mixing locomotion + perception).
- Decorator: modifies a child result (e.g., “only run if stamina > 30%”).
Step 1 – Define Goals and Perception
- List high-level goals (
Patrol,Investigate,Combat,Retreat). - Document the triggers (sight, sound, timers, damage).
- Map goals to composite nodes. Example:
Root
└── Selector (High-Level Goals)
├── Sequence (Retreat)
├── Sequence (Combat)
├── Sequence (Investigate)
└── Sequence (Patrol)
- Identify reusable subtrees (
FindCover,CircleTarget,PlayAlertBark). Save them in your editor (Unity Behavior Designer Subtree, Unreal BT Subtree, Godot scene).
Pro Tip: Keep your sensor logic (raycasts, overlaps, audio cues) outside the tree. Update a blackboard (
playerVisible,lastHeardLocation) via services so tree nodes stay pure and testable.
Step 2 – Design a Blackboard Schema
Blackboard (or context) data is the glue between nodes.
| Key | Type | Source | Used By |
|---|---|---|---|
playerActor |
object | perception service | MoveTo, Attack |
playerVisible |
bool | sight trace | Combat selector |
lastHeardLocation |
vector | audio service | Investigate sequence |
coverPoints |
array |
level authoring | FindCover subtree |
healthPercent |
float | stats component | Retreat decorator |
Updating Blackboard Values
- Unity: ScriptableObject
Blackboardor Behavior Designer variables - Unreal:
BTService_BlueprintBaseupdates Blackboard keys every tick - Godot: Dictionary stored on the AI node, passed to behavior tree functions
Keep naming consistent and document units (meters, seconds) to avoid misusing values later.
Step 3 – Implement the Tree (Unity Example)
Below is a simplified C# implementation using ScriptableObjects for nodes. Libraries like Behavior Designer, NodeCanvas, or XNode give you editors, but rolling your own teaches the underlying flow.
public abstract class BTNode : ScriptableObject
{
public enum State { Running, Success, Failure }
public abstract State Tick(BTContext context, float deltaTime);
}
[CreateAssetMenu(menuName = "BT/Composite/Selector")]
public class SelectorNode : BTNode
{
public List<BTNode> children;
public override State Tick(BTContext ctx, float dt)
{
foreach (var child in children)
{
var result = child.Tick(ctx, dt);
if (result != State.Failure)
return result;
}
return State.Failure;
}
}
[CreateAssetMenu(menuName = "BT/Tasks/MoveToTarget")]
public class MoveToTargetNode : BTNode
{
public float stopDistance = 1.5f;
public override State Tick(BTContext ctx, float dt)
{
if (!ctx.HasTarget) return State.Failure;
ctx.Agent.SetDestination(ctx.TargetPosition);
return ctx.Agent.remainingDistance <= stopDistance ? State.Success : State.Running;
}
}
BTContext wraps your agent, nav mesh agent, Animator, and blackboard data. At runtime, tick the root node inside Update or FixedUpdate, passing delta time.
Unity Workflow Checklist
- [ ] Build ScriptableObject tree assets for selectors, sequences, decorators
- [ ] Inject a Blackboard reference into
BTContext - [ ] Add
BTServiceMonoBehaviours to update senses - [ ] Use Timeline or Animator triggers inside leaf nodes, not composites
- [ ] Profile allocations with Unity Profiler (ScriptableObjects help reuse data)
Step 4 – Godot GDScript Implementation
Godot 4 ships with built-in behavior tree nodes, but you can also script your own for 2D/3D projects.
class_name BTNode
enum State { RUNNING, SUCCESS, FAILURE }
func tick(context: Dictionary, delta: float) -> State:
return State.SUCCESS
class_name SelectorNode
extends BTNode
var children: Array[BTNode] = []
func tick(context, delta):
for child in children:
var result = child.tick(context, delta)
if result != State.FAILURE:
return result
return State.FAILURE
class_name MoveToNode
extends BTNode
@export var speed := 4.0
func tick(context, delta):
if context["player_visible"]:
var dir = (context["player_position"] - context["self"].global_position).normalized()
context["self"].global_position += dir * speed * delta
return State.RUNNING
return State.FAILURE
Store the tree structure in a .tres resource or a dedicated scene, then tick it from _physics_process. Use signals to update blackboard entries (player_visible, player_position) when sensors trigger.
Step 5 – Testing and Debugging
Visual Debugging
- Unity: Draw Gizmos for current target, cover, and debug lines between nodes. Use the Behavior Designer or NodeCanvas runtime inspector to see which node is active.
- Unreal: Enable
Show > Gameplay Debugger (’’ key)to view blackboard values, EQS scores, and the active branch. - Godot: Use
print_tree()or customRichTextLabeloverlays to show running nodes and timers.
Automated Tests
Create lightweight test harnesses that tick the tree with mocked blackboard values.
[Test]
public void RetreatSequenceFiresWhenHealthLow()
{
var ctx = new BTContext { HealthPercent = 0.2f, PlayerVisible = true };
var result = retreatSequence.Tick(ctx, 0.016f);
Assert.AreEqual(BTNode.State.Running, result);
}
Combine unit tests with in-editor simulation (Unity Play Mode, Unreal PIE, Godot Remote) to catch regressions before QA.
Common Mistakes to Avoid
- Monolithic tasks: If a node plays an animation, rotates, and fires a projectile, you cannot reuse it. Split responsibilities.
- No cooldowns: Without decorator cooldowns, selectors can thrash between choices every frame.
- Forgetting failure paths: Always handle what happens when
MoveTofails (blocked path, lost target). - Global state abuse: Store data in the blackboard, not static singletons, so you can duplicate AI actors cleanly.
- Decorator spaghetti: Nesting decorators 5+ deep is a smell—break parts into subtrees.
Production Checklist
- [ ] Document your tree and share diagrams in the project wiki
- [ ] Version control each subtree asset (Unity .asset, Unreal .uasset)
- [ ] Expose key numbers (stopping distance, search radius) via data tables or ScriptableObjects
- [ ] Profile CPU cost on target hardware; long-running tasks should yield (
await) or run in jobs - [ ] Localize bark text and VO triggered by task nodes
- [ ] Link telemetry events (
AI_StateChanged,AI_TargetLost) for analytics in later chapters
Further Reading & Tools
- Unreal Engine Behavior Tree Quick Start Guide
- Unity Behavior Designer Asset
- Godot Behavior Tree Addon
- Halcyon Behaviour Tree Visualizer
- GDC Talk: Designer-Friendly Behavior Trees
Next Steps
Ready to level up? Head to Chapter 7: Machine Learning in Games - TensorFlow.js where you'll augment handcrafted trees with ML-driven behaviors. Keep iterating on your tree—add stealth, squad coordination, or boss phases—and commit the changes so your future self understands the logic.