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.
  • Navigationpathfinding 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 TargetActor on 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:

  1. Combat branch — Sequence: target valid → in attack range → not on cooldown → melee task.
  2. Chase branch — Sequence: target valid → move to target (Running) with time limit decorator.
  3. Investigate branch — Sequence: heard noise flag set → move to LastKnownPosition → clear flag.
  4. 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

ApproachStrengthsWeaknesses
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