Behavior Trees
The decision structure behind every modern NPC brain. Behavior trees replaced hand-crafted FSMs in AAA because they decompose AI into reusable, debuggable modules. We build one from scratch: sequences, selectors, decorators, parallel nodes, blackboards, reactive evaluation, and the tick loop that drives it all. Live widgets, C++ and Rust code, cited sources from Isla 2005 to Colledanchise 2018.
01Why behavior trees
Every NPC in a shipped AAA title makes decisions. Patrol a route, chase a target, take cover, reload, call for backup, retreat when hurt. In the early 2000s, those decisions lived in finite state machines: one state per behavior, explicit transitions between them. The approach works at five states. At fifteen it becomes unmanageable. At fifty it is a maintenance hazard that no one on the team wants to touch.
Behavior trees solved this by replacing the explicit-transition model with a tree of modular nodes evaluated top-down every frame. Adding a new behavior means grafting a subtree onto an existing branch, not wiring transitions to every other state. Halo 2[1] shipped the first production BT (or, more precisely, a behavior DAG, but the design pattern is the same one the industry adopted). Unreal Engine's built-in AI system[6] is a behavior tree editor. Unity lacks a built-in BT, but Behavior Designer and NodeCanvas fill the gap as middleware. Most AAA studios that hand-roll their AI systems (Naughty Dog[8], Guerrilla[9], IO Interactive) use some variant of the pattern.
The core advantages over FSMs:
- Modularity. A subtree is self-contained. You can move it, copy it, disable it, or hand it to another designer. FSM states are tangled by their transitions.
- Scalability. Adding a branch to a BT is O(1) work. Adding a state to an FSM is O(n) transitions in the worst case.
- Debuggability. A BT's evaluation path is a single top-down walk. You can visualize it as a highlighted path through the tree. An FSM's history is a sequence of state transitions, and the current behavior depends on which transition fired last.
- Re-evaluation. A reactive BT re-checks higher-priority branches every tick. An FSM stays in its current state until an explicit transition fires, which means missed opportunities and stale decisions.
A working understanding of every node type in a behavior tree. Sequence, selector, decorator, parallel. The tick loop and the three return statuses. Blackboards for inter-node communication. Reactive vs memory evaluation. Utility-based selection for scoring branches instead of using fixed priority. The trade-off against GOAP and HTN planning. Code in C++ and Rust. Five interactive widgets. And the case studies: Halo, Unreal, The Last of Us, Horizon Zero Dawn, Hitman.
02A short history
Behavior trees emerged from the intersection of game AI engineering and robotics research. The timeline:
03The FSM problem
An with n states has up to n(n - 1) directed transitions. Three states: 6 transitions. Eight states: 56. Not every pair needs a transition in practice, but every time a designer adds a new state, they have to answer "can the agent transition here from every existing state, and can it transition from here to every existing state?" Missing a transition is a bug (agent gets stuck in a state). Adding a redundant one is also a bug (agent escapes a state it shouldn't leave). The cost of answering the question is O(n) per new state; the total is O(n2).
Hierarchical FSMs (HFSMs) mitigate this by nesting sub-FSMs inside states, but the transition explosion still applies within each level. Pushdown automata (PDA-style stacks of states) help with "go to X, then return," but the core problem remains: the interaction between states is encoded in transitions, and transitions scale quadratically.
The widget below makes this visible. Add states to the FSM on the left and watch the transition arrows multiply. The behavior tree on the right adds a leaf for each new behavior, and the edge count grows linearly.
04Tree structure and tick
A behavior tree is a rooted tree. Every node has zero or more children. The tree is evaluated from the root every tick (typically once per frame, though some engines tick AI at a lower rate to save CPU). Each node's tick() function returns one of three statuses:
- SUCCESS: the node did its job.
- FAILURE: the node tried and could not do its job.
- RUNNING: the node is still working. Come back next tick.
Node types fall into three categories:
- Composite nodes have multiple children and control evaluation order. Sequence, Selector, and Parallel.
- Decorator nodes have exactly one child and modify its result. Inverter, Repeat, Cooldown.
- Leaf nodes have no children. Either an action (does work) or a condition (tests the world).
Evaluation is top-down, left-to-right. The root ticks its children according to its composite type. Children tick their children in turn. The result propagates back up. One walk through the tree per tick.
The widget below shows this evaluation in action. A selector at the root tries three branches: Attack (sequence of condition + action), Chase (sequence of condition + action), and Patrol (single action). Switch the scenario to change which conditions pass, then step through or auto-play to watch the tick propagate.
05Sequences
A Sequence is AND logic. It executes its children left to right. If a child returns SUCCESS, the sequence moves to the next child. If a child returns FAILURE, the sequence stops and returns FAILURE. If all children return SUCCESS, the sequence returns SUCCESS. If any child returns RUNNING, the sequence returns RUNNING.
The mental model: "do A, then B, then C. If any step fails, abort." A patrol sequence might be: [HasWaypoint?, MoveToWaypoint, Wait(2s), NextWaypoint]. If HasWaypoint? fails, the entire patrol sequence fails and the parent selector tries something else.
06Selectors (fallbacks)
A Selector (sometimes called Fallback or Priority) is OR logic. It executes children left to right. If a child returns FAILURE, the selector moves to the next child. If a child returns SUCCESS, the selector stops and returns SUCCESS. If all children fail, the selector returns FAILURE. If any child returns RUNNING, the selector returns RUNNING.
The mental model: "try A. If that fails, try B. If that fails, try C." The leftmost child has the highest priority. A top-level selector with [AttackSequence, ChaseSequence, PatrolAction] tries combat first, pursuit second, and only patrols if neither applies.
The widget below puts a sequence and a selector side by side with the same three children. Toggle each child's result to see how the composite outcome differs.
07Decorators
A decorator wraps exactly one child and modifies its behavior or result. Common decorators:
- Inverter (NOT). Flips SUCCESS to FAILURE and vice versa. RUNNING passes through. Useful for negating conditions: "if NOT EnemyVisible, patrol."
- Repeat(n). Ticks its child up to n times. Returns SUCCESS after n completions, RUNNING while iterating, FAILURE if the child fails during any iteration.
- RetryUntilSuccess(n). Retries the child up to n times on FAILURE. Returns SUCCESS as soon as the child succeeds. Returns FAILURE after n failed attempts.
- Cooldown(t). After the child completes (SUCCESS or FAILURE), returns FAILURE for the next t seconds without ticking the child. Prevents behaviors from re-triggering immediately (barks, reload checks, perception queries).
- TimeLimit(t). If the child has been RUNNING for more than t seconds, forces a FAILURE. Prevents stuck actions from blocking the tree forever.
Unreal's BT system uses decorators differently from the academic standard. In Unreal, decorators attach to composite nodes as auxiliary nodes rather than being tree nodes in their own right. The effect is the same (the decorator gates or modifies the composite's evaluation), but the visual layout in the editor differs. Decorators in Unreal also serve as abort triggers: a decorator can specify that when its condition changes, it aborts the subtree below and forces re-evaluation.[6]
08Parallel nodes
A Parallel node ticks all its children every tick, regardless of individual results. Two success policies are common:
- Succeed-on-all. Returns SUCCESS only when every child has returned SUCCESS. Returns FAILURE if any child fails. The "all must pass" gate.
- Succeed-on-one. Returns SUCCESS as soon as any child succeeds. Returns FAILURE only if all children fail. The "any will do" gate.
A typical use: [Parallel: MoveTo(cover), PlayAnimation(sprint)]. The agent moves and plays the sprint animation at the same time. When MoveTo finishes, the parallel node reports SUCCESS and the animation can be interrupted.
"Parallel" refers to the evaluation policy: all children are ticked in the same frame. The ticking itself happens sequentially on one thread. No concurrency primitives, no race conditions. The name is unfortunate because it invites confusion with parallel programming. Some codebases rename it to "Concurrent" or "SimpleParallel" (Unreal) to reduce this confusion.
09Blackboards
Nodes in a behavior tree need to share data: the target entity, its last-known position, the agent's health, ammo count, whether a perception query returned a result. The is a key-value store that serves this purpose. Every node can read from and write to the blackboard. The blackboard is scoped to one agent (one blackboard instance per entity), though some systems allow parent scopes for squad-level data.
Unreal's blackboard is a first-class asset paired with the BT. You define typed keys (Object, Vector, Float, Bool, Enum) in the blackboard definition. Decorators can observe specific keys and trigger re-evaluation when the value changes, which is the mechanism behind Unreal's event-driven BT model.[6]
The widget below shows a behavior tree reading from a live blackboard. Adjust the sliders to change perception data, and watch the tree re-evaluate in real time.
10Actions and conditions
Leaf nodes are where the actual work happens. Two kinds:
- Conditions test world state and return SUCCESS or FAILURE in a single tick. IsTargetVisible, IsHealthBelow(30), HasAmmo, IsInCover. They never return RUNNING.
- Actions perform work. MoveTo, Attack, PlayAnimation, Reload, Wait(t). Actions that complete in one tick return SUCCESS or FAILURE. Actions that span multiple frames (movement, animation playback) return RUNNING until they finish.
The RUNNING status is what makes BTs handle multi-frame actions cleanly. A MoveTo action that takes 200 frames to complete returns RUNNING on each of those frames. The parent sequence sees RUNNING, returns RUNNING itself, and the tree pauses evaluation of that branch until next tick. This propagation continues all the way up to the root.
Code: base node, sequence, selector
enum class Status { Success, Failure, Running };
// Base node. Every node in the tree implements tick().
class Node {
public:
virtual ~Node() = default;
virtual Status tick() = 0;
};
// Sequence: AND logic. Fail on first failure, succeed when all succeed.
class Sequence : public Node {
std::vector<std::unique_ptr<Node>> children;
public:
void addChild(std::unique_ptr<Node> child) {
children.push_back(std::move(child));
}
Status tick() override {
for (auto& child : children) {
Status result = child->tick();
if (result != Status::Success)
return result; // FAILURE or RUNNING propagates up
}
return Status::Success; // all children succeeded
}
};
// Selector: OR logic. Succeed on first success, fail when all fail.
class Selector : public Node {
std::vector<std::unique_ptr<Node>> children;
public:
void addChild(std::unique_ptr<Node> child) {
children.push_back(std::move(child));
}
Status tick() override {
for (auto& child : children) {
Status result = child->tick();
if (result != Status::Failure)
return result; // SUCCESS or RUNNING propagates up
}
return Status::Failure; // all children failed
}
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Status { Success, Failure, Running }
// Base trait. Every node implements tick().
pub trait Node {
fn tick(&mut self) -> Status;
}
// Sequence: AND logic. Fail on first failure, succeed when all succeed.
pub struct Sequence {
children: Vec<Box<dyn Node>>,
}
impl Sequence {
pub fn new(children: Vec<Box<dyn Node>>) -> Self {
Sequence { children }
}
}
impl Node for Sequence {
fn tick(&mut self) -> Status {
for child in &mut self.children {
let result = child.tick();
if result != Status::Success {
return result; // FAILURE or RUNNING propagates up
}
}
Status::Success // all children succeeded
}
}
// Selector: OR logic. Succeed on first success, fail when all fail.
pub struct Selector {
children: Vec<Box<dyn Node>>,
}
impl Node for Selector {
fn tick(&mut self) -> Status {
for child in &mut self.children {
let result = child.tick();
if result != Status::Failure {
return result; // SUCCESS or RUNNING propagates up
}
}
Status::Failure // all children failed
}
}
Code: a concrete action (MoveTo with RUNNING)
// MoveTo: navigate to a target position read from the blackboard.
// Returns RUNNING while pathfinding/moving, SUCCESS on arrival,
// FAILURE if the path is invalid or the target is unreachable.
class MoveTo : public Node {
Blackboard& blackboard;
NavAgent& navAgent;
bool started = false;
public:
MoveTo(Blackboard& blackboard, NavAgent& navAgent)
: blackboard(blackboard), navAgent(navAgent) {}
Status tick() override {
if (!started) {
Vec3 target = blackboard.get<Vec3>("target.position");
if (!navAgent.requestPath(target))
return Status::Failure; // no valid path
started = true;
}
if (navAgent.hasArrived()) {
started = false; // reset for next invocation
return Status::Success;
}
return Status::Running; // still moving
}
};
// MoveTo: navigate to a target position read from the blackboard.
// Returns Running while pathfinding/moving, Success on arrival,
// Failure if the path is invalid or the target is unreachable.
pub struct MoveTo {
started: bool,
}
impl MoveTo {
pub fn new() -> Self { MoveTo { started: false } }
}
impl Node for MoveTo {
fn tick_with(&mut self, blackboard: &Blackboard, nav: &mut NavAgent) -> Status {
if !self.started {
let target = blackboard.get::<Vec3>("target.position");
if !nav.request_path(target) {
return Status::Failure; // no valid path
}
self.started = true;
}
if nav.has_arrived() {
self.started = false; // reset for next invocation
return Status::Success;
}
Status::Running // still moving
}
}
Code: blackboard with typed access
// Blackboard: type-erased key-value store.
// Production code would use a fixed-layout struct for cache locality;
// the std::any version here prioritizes flexibility for prototyping.
class Blackboard {
std::unordered_map<std::string, std::any> data;
public:
template<typename T>
void set(const std::string& key, T value) {
data[key] = std::move(value);
}
template<typename T>
T get(const std::string& key) const {
auto it = data.find(key);
if (it == data.end())
return T{}; // default-constructed if missing
return std::any_cast<T>(it->second);
}
bool has(const std::string& key) const {
return data.count(key) > 0;
}
};
use std::any::Any;
use std::collections::HashMap;
// Blackboard: type-erased key-value store.
// Production code would use a fixed-layout struct for cache locality;
// the Any version here prioritizes flexibility for prototyping.
pub struct Blackboard {
data: HashMap<String, Box<dyn Any>>,
}
impl Blackboard {
pub fn new() -> Self {
Blackboard { data: HashMap::new() }
}
pub fn set<T: 'static>(&mut self, key: &str, value: T) {
self.data.insert(key.to_string(), Box::new(value));
}
pub fn get<T: 'static + Clone>(&self, key: &str) -> Option<T> {
self.data.get(key)?.downcast_ref::<T>().cloned()
}
pub fn has(&self, key: &str) -> bool {
self.data.contains_key(key)
}
}
The code above skips: thread safety (real blackboards need atomic reads or lock guards if ticked from a job system), key-change notifications (Unreal's observer pattern for event-driven re-evaluation), typed key definitions (catching "target.positon" typos at compile time), and scope hierarchies (per-entity vs per-squad vs global). A shipping blackboard also avoids std::string keys in favor of hashed IDs for O(1) lookup without string comparison.
11Memory and reactive behavior
Two evaluation strategies:
- Reactive (stateless). Every tick starts evaluation at the root. If a higher-priority branch's condition has changed since last tick, the tree immediately switches. The benefit is responsiveness: the agent reacts within one tick. The cost is that every condition in the tree above the current RUNNING node is re-checked every frame.
- Memory (resume). The tree remembers which child was RUNNING and resumes from there next tick, skipping higher-priority branches. The benefit is efficiency: you don't re-check conditions that haven't changed. The cost is missed priority changes: if an enemy appears while the agent is running a RUNNING patrol action, the combat branch won't be checked until patrol finishes or is explicitly aborted.
Most production systems use a hybrid. Unreal's BT is memory-based by default, but decorators can specify an AbortType: LowerPriority (abort branches to the right), Self (abort this branch), or Both. When a decorator's observed blackboard key changes, the specified abort fires and forces re-evaluation. This gives reactive behavior where needed without the cost of re-checking every condition every frame.[6]
The widget below demonstrates the interrupt. An agent patrols (RUNNING). Click "Spot Enemy" and watch the higher-priority Combat branch abort Patrol and take over.
12Utility-based selection
Standard selectors pick children by fixed left-to-right priority. replaces that with a scoring function: each child is assigned a utility value (a float computed from threat level, distance, ammo count, health, cooldown timers), and the child with the highest score wins. Dave Mark's Infinite Axis Utility System (GDC AI Summit 2013, refined at GDC 2015[7]) is the canonical reference for this approach.
Hybrid BT+Utility trees are common in production. The tree structure handles the coarse decision hierarchy (combat vs exploration vs dialogue), while utility scoring handles the fine-grained choice within a category (which target to engage, which cover point to use). This avoids the combinatorial explosion of scoring everything against everything, while still getting smooth, context-sensitive decisions where they matter.
Implementation: replace the Selector's left-to-right loop with a "score all children, sort by score, evaluate in score order" loop. The child evaluation still follows the standard tick protocol (SUCCESS/FAILURE/RUNNING), so the rest of the tree machinery is unchanged.
13BT vs GOAP vs HTN
Behavior trees are not the only game AI architecture. Two planning-based alternatives ship in production: (Goal-Oriented Action Planning) and (Hierarchical Task Networks).
| Property | Behavior Tree | GOAP | HTN |
|---|---|---|---|
| Behavior authored by | Designer builds tree structure | Designer defines actions + preconditions; planner finds the sequence | Designer defines task decompositions; planner picks decomposition path |
| Emergent behavior | Low. The tree encodes the decision space explicitly. | High. The planner can combine actions in ways the designer didn't anticipate. | Medium. Constrained by decomposition hierarchy but can still surprise. |
| Debuggability | High. One path through a tree. Visual debuggers show the active branch. | Low. Plans are generated at runtime. Hard to explain why the planner chose a specific action sequence. | Medium. The decomposition tree is readable, but plan selection can be opaque. |
| Shipped in | Halo 2/3, Unreal BT, Hitman | F.E.A.R.[10] | Transformers: Fall of Cybertron[11] |
| CPU cost per tick | Low. One tree walk. O(depth) for memory BTs, O(nodes) for reactive. | Variable. Planning is a search, potentially expensive. Usually amortized by caching plans. | Low to medium. Decomposition is cheaper than GOAP's open-ended search. |
F.E.A.R. (Monolith, 2005) is the canonical GOAP reference. Jeff Orkin's GDC 2006 talk[10] describes a system where the FSM has only three states (Goto, Animate, UseSmartObject) and A* plans over the action space to decide what to do. The result is impressive emergent behavior (soldiers flanking, suppressing, flushing with grenades) but the debugging story is harder: "why did the AI throw a grenade?" requires replaying the planner's search.
HTN planning (Humphreys, Game AI Pro[11]) sits between BTs and GOAP. High-level tasks decompose into subtasks according to designer-authored rules. The planner picks decomposition paths based on world state. High Moon Studios shipped HTN in Transformers: Fall of Cybertron and reported that it was faster than the GOAP system used in the previous game (War for Cybertron).
Guerrilla Games used a different hybrid for Horizon Zero Dawn: HTN planning combined with utility-based decisions for the machine AI, handling both the high-level behavior hierarchy and the fine-grained machine ecology where different machine classes (Acquisition, Transport, Combat, Reconnaissance) coordinate in groups.[9]
14Case studies
Halo 2 / Halo 3 (Bungie)
Isla's GDC 2005 talk[1] describes the Covenant AI as a behavior DAG (directed acyclic graph, not strictly a tree, because some behaviors are shared across branches). The key insight: define behaviors as self-contained modules, evaluated in priority order. The system let Bungie scale from a handful of AI types in Halo 1 to the diverse enemy roster in Halo 2 without the codebase becoming unmaintainable. Halo 3 refined the architecture further, adding better squad-level coordination.
Unreal Engine BT system
Unreal's BT[6] is event-driven rather than polling-based. Decorators observe blackboard keys and trigger re-evaluation only when observed values change. This reduces CPU cost on large NPC counts by avoiding the per-tick full-tree walk. The system includes a visual debugger that highlights the active branch in real time, making it straightforward to diagnose why an NPC chose a particular behavior. Unreal's "Simple Parallel" node replaces the academic Parallel composite: it ticks a primary task and a background task simultaneously, finishing when the primary finishes.
The Last of Us (Naughty Dog)
Naughty Dog's enemy AI[8] uses a priority-stacked skill/behavior system built on FSMs rather than behavior trees. Each NPC has a stack of "skills" (combat, investigate, patrol) prioritized by context. Low-level "behaviors" within each skill are FSM-driven. The architecture handles squad coordination (one suppresses while others advance) through a separate tactical layer. This is a useful contrast: TLOU shipped one of the most praised enemy AI systems in AAA without behavior trees, demonstrating that BTs are not the only viable approach.
Horizon Zero Dawn (Guerrilla Games)
Julian Berteling's GDC 2018 talk[9] covers Guerrilla's transition from Killzone's corridor-shooter AI to Horizon's open-world navigation and animation systems. The machine AI uses HTN planning for high-level decisions and utility scoring for target selection, not behavior trees. Different machine classes (Watchers for reconnaissance, Grazers for acquisition, Thunderjaws for combat) have distinct behavior profiles and coordinate in mixed-species groups. The system uses "information packets" attached to stimuli (arrows, rocks, player movement) that feed into machine sensors, allowing each class to react differently to the same event.
Hitman (IO Interactive)
IO Interactive's crowd system[12] handles over 1,000 NPCs per level. The core NPC AI uses behavior trees for individual decisions (react to player disguise, investigate suspicious behavior, alert guards). The crowd layer on top manages ambient behaviors (walking routes, conversations, scripted vignettes) at scale. The BT architecture built for Hitman: Absolution carried forward into the 2016 and 2018 Hitman releases with incremental refinement. A GDC 2017 talk by Anguelov, Vehkala, and Weber outlined the behavior tree cultivation practices IO uses to keep large trees maintainable.
15Pitfalls
- Deep trees that are hard to read. A tree with 10 levels of nesting is no better than spaghetti code. Keep the tree shallow. Extract reusable subtrees into named sub-behaviors. Unreal lets you reference sub-trees by asset, which keeps the visual graph manageable.
- Over-use of Parallel. Running multiple behaviors simultaneously creates interaction bugs. If a Parallel ticks both "move to cover" and "fire at enemy," the agent may try to shoot while running, producing broken animations. Use Parallel sparingly and test the interaction between concurrent children explicitly.
- Blackboard key collisions. Two subtrees writing to the same blackboard key ("target") without coordination. Agent A's "target" is a cover point; Agent B's "target" is an enemy. Fix: use namespaced keys ("combat.target", "cover.target") or separate blackboard scopes per subtree.
- Forgetting to handle RUNNING. An action that returns RUNNING but never transitions to SUCCESS or FAILURE blocks the tree forever (see the quiz in section 12). Every action that returns RUNNING must have a termination condition. Add TimeLimit decorators as safety nets.
- Priority starvation. A high-priority branch that succeeds every tick prevents lower-priority branches from ever running. If "CheckForEnemies" always succeeds (even when there's nothing to do about the enemy), everything below it starves. Conditions should return FAILURE when their associated behavior is not actionable, not just when the world state doesn't match.
- Condition/action ordering in sequences. Put conditions before actions. A sequence [Fire, HasAmmo?] fires first and checks ammo after, which is backwards. The tree reads left to right, so preconditions go on the left.
16What's next
- Machine-learned BTs. Using reinforcement learning or genetic programming to evolve tree structures from gameplay data. Colledanchise and Ogren cover this in their textbook[5]. The challenge is that learned trees are often unreadable, which undermines the debuggability advantage.
- Procedural BT generation. Generating trees from designer-authored constraints or goal specifications. A middle ground between hand-crafted BTs and full planning systems.
- Event-driven BTs. Unreal already does this. The pure-academic model (tick everything from root every frame) is being replaced by event-driven evaluation where only branches affected by a state change are re-evaluated. Reduces CPU cost for large NPC populations.
- BTs for non-AI systems. Behavior trees work for any reactive decision-making: dialogue selection, tutorial sequencing, cutscene scripting, procedural audio mixing. The node types are the same; the leaf actions change.
17Sources
- Damian Isla. "Handling Complexity in the Halo 2 AI." GDC 2005 / Gamasutra. gamedeveloper.com. The foundational talk on behavior-centric AI architecture that kicked off industry adoption of behavior trees. Describes the Halo 2 Covenant AI as a behavior DAG with priority-ordered evaluation.
- Alex J. Champandard. "Understanding Behavior Trees." AiGameDev.com, 2007. Formalizes the node taxonomy (composite, decorator, leaf) and names the Sequence/Selector pair. The article that standardized BT terminology for the game AI community.
- Alex J. Champandard and Philip Dunstan. "The Behavior Tree Starter Kit." Game AI Pro, Chapter 6. gameaipro.com. Reference implementation of a minimal BT runtime. The code most studios start from when building a custom BT system.
- Alejandro Marzinotto, Michele Colledanchise, Christian Smith, Petter Ogren. "Towards a Unified Behavior Trees Framework for Robot Control." IEEE ICRA 2014. ieeexplore.ieee.org. Formal semantics for BTs grounded in hybrid dynamical systems. Bridges the gap between game AI's informal descriptions and robotics' need for mathematical rigor.
- Michele Colledanchise and Petter Ogren. Behavior Trees in Robotics and AI: An Introduction. CRC Press, 2018. arxiv.org/abs/1709.00084 (preprint). The first comprehensive BT textbook. Covers formal properties, equivalence proofs, learning, and task planning.
- Epic Games. "Behavior Trees in Unreal Engine." Unreal Engine 5 documentation. dev.epicgames.com. Documents the visual BT editor, blackboard asset system, decorator-based abort triggers, and the event-driven evaluation model.
- Dave Mark. "Architecture Tricks: Managing Behaviors in Time, Space, and Depth." GDC AI Summit, 2013. gdcvault.com. Introduces the Infinite Axis Utility System. The canonical reference for utility-based AI scoring, later refined at GDC 2015 with Mike Lewis.
- Max Dyckhoff. "Ellie: Buddy AI in The Last of Us" and Travis McIntosh. "The Last of Us: Human Enemy AI." GDC 2014. gdcvault.com. Priority-stacked skill/behavior system built on FSMs, not behavior trees. Described in McIntosh's Game AI Pro 2 chapter (Ch. 34). A useful contrast showing AAA-quality AI without BTs.
- Julian Berteling. "Beyond 'Killzone': Creating New AI Systems for 'Horizon Zero Dawn'." GDC 2018. gdcvault.com. HTN planning plus utility scoring for machine ecology. Describes how different machine classes coordinate in mixed-species groups using information-packet-based stimulus systems.
- Jeff Orkin. "Three States and a Plan: The A.I. of F.E.A.R." GDC 2006. gamedevs.org. Goal-Oriented Action Planning. The FSM has three states; A* plans over the action space. Emergent flanking, suppression, and grenade-flushing behavior.
- Troy Humphreys. "Exploring HTN Planners Through Example." Game AI Pro, Chapter 12. gameaipro.com. Total-order forward decomposition planner used in Transformers: Fall of Cybertron. Faster than GOAP for the same game's previous title.
- Bobby Anguelov (WB Games Montreal), Mika Vehkala (Remedy), Ben Weber (Twitch). "AI Arborist: Proper Cultivation and Care for Your Behavior Trees." GDC 2017. gdcvault.com. General BT cultivation practices: keeping large trees maintainable, debugging, and testing. Anguelov's prior IO Interactive experience informed the talk but the content is studio-agnostic.
- Matteo Iovino, Edvards Scukins, Jonathan Styrud, Petter Ogren, Christian Smith. "A survey of Behavior Trees in robotics and AI." Robotics and Autonomous Systems, 2022. sciencedirect.com. Comprehensive survey of BT extensions, formal properties, and applications across game AI and robotics. Covers reactive, memory, and hybrid evaluation strategies.