Guide
Game behavior trees explained
A guard hears a noise, investigates, loses sight of the player, returns to patrol,
spots them again, flanks, and attacks — all without a thousand-line
if/else block. That orchestration is what behavior trees
(BTs) were built for. Popularized in AAA titles like Halo and now
standard in Unity, Unreal, and Godot, a behavior tree is a directed hierarchy of
tasks evaluated top-down each frame (or on a throttled tick). Unlike a flat
finite state machine,
trees compose reusable subtrees — “patrol,” “chase,”
“melee attack” — and let designers rearrange priority without rewriting
code. This guide explains node types, the blackboard pattern, tick semantics,
classic combat-AI recipes, debugging in production, and how BTs compare to FSMs,
GOAP, and utility AI.
Why behavior trees exist
Early enemy AI was often a single FSM with states like Idle,
Patrol, Chase, and Attack. That works until
you need layered behaviors: investigate suspicious sounds without fully
abandoning patrol routes, interrupt attacks when stunned, or let ranged enemies
fall back while melee units close distance. Each new edge multiplies transition
count — the “transition explosion” problem FSM guides warn about.
Behavior trees invert the problem. Instead of enumerating every state-to-state jump, you declare priority-ordered goals at the root: “If I can attack, attack; else if I can chase, chase; else patrol.” Composites walk children until one succeeds or all fail. Subtrees are data: designers drag nodes in Unreal’s Behavior Tree editor or Unity’s visual tooling without recompiling C#. Programmers implement leaf tasks (move, shoot, play animation); designers wire them into trees.
Where BTs fit in the AI stack
- Perception — line-of-sight checks, hearing radius, aggro tables (often separate from the tree).
- Decision — the behavior tree chooses what to try next.
- Navigation — pathfinding and steering move the agent; BT leaves call into navmesh APIs.
- Animation — montages and blend trees react to BT task success/failure.
Node types: composites, decorators, and leaves
Most engines share a common vocabulary. Names vary (Unreal uses Selector/Sequence; some texts say Priority/Sequence), but semantics align.
Composite nodes
- Selector (fallback / OR) — Tries children left-to-right until one returns Success. If all fail, the selector fails. Root selectors encode priority: attack before chase before idle.
- Sequence (AND) — Runs children in order; fails fast on first child failure. A “reload sequence” might be: has ammo? → play reload anim → refill magazine.
- Parallel — Runs multiple children concurrently with a success/failure policy (e.g. succeed when any child succeeds, fail when all fail). Useful for moving while aiming.
Decorator nodes
Decorators wrap a single child and modify its result or execution:
- Inverter — Success becomes Failure and vice versa.
- Repeat / Loop — Re-executes the child until N successes or forever.
- Cooldown — Blocks re-entry for a time window (prevents spamming grenades).
- Blackboard condition — Runs child only if a key matches (e.g.
TargetActor != null). - Time limit — Fails the child if it runs longer than N seconds (stuck-path recovery).
Leaf nodes: tasks and services
- Task / Action — Does work over one or more ticks: move to point, play attack, wait. Returns Running until finished, then Success or Failure.
- Condition — Instant check: line of sight? in range? returns Success/Failure without Running.
- Service (Unreal) / background node — Low-frequency side logic attached to a composite, e.g. scan for targets every 0.5s while patrolling.
The blackboard: shared AI memory
Trees stay stateless; persistent data lives on a blackboard — a
key-value store keyed by typesafe names: TargetActor,
PatrolIndex, LastKnownPosition, AlertLevel.
Perception systems write; tasks read and write. This separation lets multiple agents
share tree assets with different blackboard instances per NPC.
Good blackboard hygiene prevents subtle bugs:
- Clear
TargetActoron death or leash reset — stale pointers cause chasing corpses. - Use world positions, not references, for “last known” locations when targets can be destroyed.
- Namespace keys per squad (
SquadFocusTarget) when coordinating group tactics. - Avoid storing heavy objects; store IDs and resolve through your ECS or entity manager.
Tick model and status codes
Each frame (or on a budgeted AI tick every N ms), the engine calls
Tick on the root. Nodes propagate one of three statuses:
- Success — Goal achieved; parent composite decides what runs next.
- Failure — Could not complete; selector tries siblings.
- Running — In progress; next tick resumes this node (critical for move-to tasks spanning many frames).
Re-entrancy matters: when a high-priority branch suddenly succeeds
(player spotted during patrol), the tree must abort lower-priority Running children
— cancel path following, stop idle fidgets. Engines expose abort types:
Lower Priority (Unreal) or explicit Reset() on subtree
change. Without abort logic, guards “slide” while also chasing.
Budget AI ticks for crowds: not every NPC needs a full tree evaluation every frame. Stagger ticks across entities, simplify distant LOD trees to selector → idle only, and cap path requests per frame so frame time stays stable.
Classic pattern: patrol, investigate, chase, attack
A workable root selector for a humanoid enemy:
- Combat branch — Sequence: target valid → in attack range → not on cooldown → melee task.
- Chase branch — Sequence: target valid → move to target (Running) with time limit decorator.
- Investigate branch — Sequence: heard noise flag set → move to LastKnownPosition → clear flag.
- Patrol branch — Loop: get next waypoint → move to waypoint → wait 2s.
Perception runs as a service on patrol: raycast or overlap query updates
TargetActor and raises alert level. Damage events can inject a
“forced chase” decorator with higher priority. Leash radius checks fail
chase tasks when kiting pulls bosses out of
level bounds.
Subtree reuse
Extract BT_MoveToBlackboardKey, BT_PlayMontage, and
BT_FaceTarget as shared subtrees. Rangers and melee share chase logic;
only the attack subtree differs. Version subtrees in source control — diffing JSON
beats diffing monolithic scripts.
BTs vs FSMs, GOAP, and utility AI
| Approach | Strengths | Weaknesses |
|---|---|---|
| FSM | Simple agents, player controllers, animation states, predictable transitions. | Transition explosion on complex NPCs; hard to reorder priorities. |
| Behavior tree | Designer-friendly, priority ordering, modular subtrees, industry tooling. | Deep trees can be opaque without debug viz; Running/abort bugs. |
| GOAP | Planners find action chains toward goals dynamically; great for emergent sims. | Planning cost at runtime; harder to author and debug than trees. |
| Utility AI | Scores many actions continuously; smooth preference shifts (flee vs fight). | Tuning score curves; less visual than node graphs for some teams. |
Production games mix patterns: FSM for player movement, BT for NPCs, utility layer to pick which subtree root gets weight when multiple goals compete. Start with BTs when designers need iteration speed; consider utility when you want gradual preference (low health → higher flee score) without adding dozens of selector branches.
Engine notes: Unity, Unreal, Godot, and the browser
- Unreal Engine — First-class Behavior Tree + Blackboard assets, AIController owns tick, EQS for spatial queries. Industry reference implementation.
- Unity — No built-in BT until recent AI packages; teams use Behavior Designer, NodeCanvas, or open-source
com.fluid.behavior-tree. Data-oriented trees serialize as ScriptableObjects. - Godot 4 — No official visual BT; popular addons (Beehave, LimboAI) provide selectors/sequences with debugger panels.
- Browser / Web — Lightweight JSON-defined trees in TypeScript work for multiplayer if AI runs server-side; keep trees small and tick rate low.
Regardless of engine, implement leaves as pure functions where possible: input blackboard + delta time → status. Unit-test conditions (in range, has ammo) without spinning up the whole scene.
Debugging and common failures
- Visual debugger — Highlight active node each tick; replay logs from playtests.
- Infinite Running — Move task never reaches acceptance radius; add fail decorator or increase radius.
- Priority inversion — Patrol sibling listed before chase; reorder selector children.
- Missing abort — Old patrol Running while chase starts; enable abort on lower priority.
- Blackboard races — Two services write TargetActor; serialize perception updates.
- Network desync — Client decorative BT vs server authoritative BT; only server decides damage.
Record blackboard snapshots when playtesters report “AI froze” — often a Failed move with no fallback sibling.
Design checklist
- Keep root selector depth shallow; push detail into named subtrees.
- Every Running task needs a timeout or failure path.
- Match tick rate to gameplay needs — 10 Hz is enough for many RPG NPCs.
- Document blackboard keys in a single schema file.
- Playtest with debug draw for aggro radius, paths, and active node.
- Profile: BT tick + pathfinding dominate AI cost, not selector overhead.
Related reading
- Game state machines explained — when FSMs beat trees for simple agents
- Pathfinding in games explained — navmesh and A* for move-to tasks
- Game level design explained — arenas, cover, and leash volumes for AI
- Entity component systems explained — storing per-agent AI data cleanly