Behavior Trees & Utility AI
Two complementary ways to structure NPC decision-making, plus how to combine them. A
behavior tree (BT) expresses structured, prioritized, reactive logic as a tree that is
"ticked" each step. Utility AI answers "how much do I want each option right now?" by
scoring actions with normalized curves and picking the best. Ship believable agents by using a
BT for structure and Utility AI where graded trade-offs matter.
This skill is the implementation companion to game-ai (which helps you choose between
FSM / BT / steering / pathfinding). Read game-ai to pick a model; read this to build the
runtime.
When to use
- Use to build a reusable BT runtime: a
Blackboard, Node base, action/condition leaves,
Sequence/Selector/Parallel composites, and decorators (Inverter, Cooldown, Repeat).
- Use to build a Utility AI decider: response curves, considerations, and an evaluator that
scores and selects actions (max, softmax, or weighted-random for variety).
- Use to build hybrid AI — a BT whose leaf delegates the "which attack / which target"
choice to a utility evaluator.
When not to use: to choose between FSM, BT, steering, or pathfinding, and for A*/navmesh
routing, use game-ai. For Unreal's asset-based BehaviorTree/Blackboard, BTTask/BTService
and AIController, use unreal-behavior-trees. For the navmesh agent that moves the NPC, use
unity-navmesh or the engine's navigation node.
Core workflow
- Pick the model. Structured, prioritized, interruptible behavior → BT. Continuous
"score every option" decisions (targeting, needs, item choice) → Utility. Both → hybrid.
- Design the Blackboard first. One typed key/value store per agent is the shared memory that
decouples nodes; leaves read/write it and never hold references to each other.
- Write leaves. Conditions return
Success/Failure immediately; actions return
Running across frames until they finish. Keep leaves small and side-effect-explicit.
- Compose.
Selector = OR/fallback (first non-failure wins); Sequence = AND (stop at first
non-success); Parallel for concurrent branches. Wrap with decorators for policy (invert,
cooldown, repeat, force-success).
- For Utility: enumerate considerations, map each raw fact through a normalized 0..1 curve,
combine (weighted product with compensation, or weighted sum), then select the max — add
hysteresis so agents don't flip-flop on ties.
- Tick deliberately. Tick the tree/evaluator once per decision step (often slower than
render). Preserve
Running state between ticks; verify by drawing the active path and the
per-action scores on screen while tuning.
Architecture at a glance
A behavior tree evaluates top-down, left-to-right; each node returns a status up to its parent:
flowchart TD
Root["Selector (root)"] --> Combat["Sequence: Combat"]
Root --> Patrol["Action: Patrol"]
Combat --> See["Condition: CanSeePlayer?"]
Combat --> InRange{"Selector: Reach"}
Combat --> Attack["Action: Attack (Running)"]
InRange --> Close["Condition: InAttackRange?"]
InRange --> MoveTo["Action: MoveToPlayer (Running)"]
Utility AI is a scoring pipeline — every candidate action is scored, then one is selected:
facts (distance, health, ammo…)
│ each fact → a normalized 0..1 response curve (consideration)
▼
score(action) = weight · combine(consideration_1 … consideration_n) # product+compensation or sum
▼
select: argmax · or softmax / weighted-random for variety · + hysteresis to avoid jitter
Status is a three-value enum shared by every node — this is the contract that makes the tree
composable:
public enum Status { Success, Failure, Running }
public abstract class Node
{
public abstract Status Tick(Blackboard bb, float dt);
public virtual void Reset() { } // called when a parent abandons this subtree
}
// Selector = fallback/OR: return the first child that is not Failure.
public sealed class Selector : Composite
{
public override Status Tick(Blackboard bb, float dt)
{
for (; _current < Children.Count; _current++)
{
var s = Children[_current].Tick(bb, dt);
if (s != Status.Failure) return s; // Success or Running stops the scan
}
_current = 0;
return Status.Failure; // every child failed
}
}
The reciprocal Sequence (AND — stop at first non-Success), Parallel, the Blackboard, the
leaf base classes, and every decorator are in references/behavior-tree-core.md.
Utility scoring in one snippet
// A consideration maps one raw fact to 0..1 through a response curve.
float Score(Blackboard bb)
{
float distance01 = Curves.InverseLerp01(bb.Get<float>("distToPlayer"), 20f, 2f); // near = 1
float health01 = Curves.Sigmoid(bb.Get<float>("health01"), k: 8f, mid: 0.4f); // hurt = low
// Product + compensation keeps a single 0 from vetoing while low values still dampen.
return Curves.CompensatedProduct(new[] { distance01, health01 });
}
The full curve library (linear, quadratic, exponential, logistic/sigmoid, smoothstep), the
Consideration/UtilityAction types, and the UtilityEvaluator selection strategies are in
references/utility-ai-system.md.
Pitfalls
- Re-ticking a
Running action from the root every frame restarts it. Return Running and
resume where you left off; only Reset() a subtree when a parent actually abandons it.
- Deep trees re-evaluated wholesale each tick waste time and cause thrash. Prefer shallow
trees and conditional aborts (a higher-priority condition can interrupt a lower branch).
- Un-normalized considerations. If one curve outputs 0..100 and another 0..1, the big one
dominates. Every consideration must return 0..1.
- Utility jitter on near-ties. Add hysteresis: give the currently-running action a small bonus
so the agent commits instead of oscillating.
- Allocating nodes, closures, or arrays every tick creates GC spikes. Build the tree once at
spawn; keep per-tick work allocation-free.
References
references/behavior-tree-core.md — Blackboard, Node/leaf base classes, action & condition
leaves, Sequence/Selector/Parallel, and the decorator library (full C#).
references/utility-ai-system.md — response-curve library, Consideration, UtilityAction,
and the UtilityEvaluator (argmax, softmax, weighted-random, hysteresis).
references/practical-examples.md — a guard Patrol→Combat BT, a villager needs-based Utility
AI, and a hybrid agent, as drop-in templates.
references/best-practices-and-pitfalls.md — memory management, profiling, avoiding deep trees,
event-driven aborts, and combining Utility AI with BTs (hybrid architecture).
Related skills
game-ai — choose between FSM / BT / steering; A* and navmesh pathfinding.
unreal-behavior-trees — Unreal's asset-based BT/Blackboard, tasks, decorators, services.
unity-navmesh — the NavMeshAgent that carries out "move to" intents.
physics-tuning — agent radius, movement, and collision response for the motion layer.
tower-defense, fps-shooter, rpg — genres that compose this decision layer.
1---2name: ai-behavior-trees-utility-ai3description: Build a production behavior-tree runtime (Blackboard, action/condition leaves, sequence/selector/parallel composites, decorators) and a Utility AI system (response curves — linear, exponential, sigmoid, quadratic — considerations, and action evaluators), plus hybrid BT-drives-Utility agents. Use when implementing a reusable behavior-tree or utility-based decision system, or tuning enemy/NPC decisions beyond a simple FSM, or when the user mentions behavior tree, blackboard, decorator, selector, sequence, tick status, utility AI, response/scoring curve, or consideration. For choosing between FSM/BT/steering or for pathfinding, use game-ai; for Unreal's BehaviorTree/Blackboard assets, use unreal-behavior-trees.4---5
6# Behavior Trees & Utility AI
7
8Two complementary ways to structure NPC decision-making, plus how to combine them. A
9**behavior tree (BT)** expresses *structured, prioritized, reactive* logic as a tree that is
10"ticked" each step. **Utility AI** answers *"how much do I want each option right now?"* by
11scoring actions with normalized curves and picking the best. Ship believable agents by using a
12BT for structure and Utility AI where graded trade-offs matter.
13
14This skill is the **implementation** companion to `game-ai` (which helps you *choose* between
15FSM / BT / steering / pathfinding). Read `game-ai` to pick a model; read this to build the
16runtime.
17
18## When to use
19
20- Use to build a **reusable BT runtime**: a `Blackboard`, `Node` base, action/condition leaves,
21 `Sequence`/`Selector`/`Parallel` composites, and decorators (Inverter, Cooldown, Repeat).
22- Use to build a **Utility AI** decider: response curves, considerations, and an evaluator that
23 scores and selects actions (max, softmax, or weighted-random for variety).
24- Use to build **hybrid AI** — a BT whose leaf delegates the "which attack / which target"
25 choice to a utility evaluator.
26
27**When *not* to use:** to *choose* between FSM, BT, steering, or pathfinding, and for A*/navmesh
28routing, use `game-ai`. For Unreal's asset-based `BehaviorTree`/`Blackboard`, `BTTask`/`BTService`
29and `AIController`, use `unreal-behavior-trees`. For the navmesh agent that *moves* the NPC, use
30`unity-navmesh` or the engine's navigation node.
31
32## Core workflow
33
341. **Pick the model.** Structured, prioritized, interruptible behavior → **BT**. Continuous
35 "score every option" decisions (targeting, needs, item choice) → **Utility**. Both → **hybrid**.
362. **Design the Blackboard first.** One typed key/value store per agent is the shared memory that
37 decouples nodes; leaves read/write it and never hold references to each other.
383. **Write leaves.** *Conditions* return `Success`/`Failure` immediately; *actions* return
39 `Running` across frames until they finish. Keep leaves small and side-effect-explicit.
404. **Compose.** `Selector` = OR/fallback (first non-failure wins); `Sequence` = AND (stop at first
41 non-success); `Parallel` for concurrent branches. Wrap with decorators for policy (invert,
42 cooldown, repeat, force-success).
435. **For Utility:** enumerate considerations, map each raw fact through a **normalized 0..1 curve**,
44 combine (weighted product with compensation, or weighted sum), then select the max — add
45 hysteresis so agents don't flip-flop on ties.
466. **Tick deliberately.** Tick the tree/evaluator once per *decision step* (often slower than
47 render). Preserve `Running` state between ticks; verify by drawing the active path and the
48 per-action scores on screen while tuning.
49
50## Architecture at a glance
51
52A behavior tree evaluates top-down, left-to-right; each node returns a status up to its parent:
53
54```mermaid
55flowchart TD
56 Root["Selector (root)"] --> Combat["Sequence: Combat"]
57 Root --> Patrol["Action: Patrol"]
58 Combat --> See["Condition: CanSeePlayer?"]
59 Combat --> InRange{"Selector: Reach"}
60 Combat --> Attack["Action: Attack (Running)"]
61 InRange --> Close["Condition: InAttackRange?"]
62 InRange --> MoveTo["Action: MoveToPlayer (Running)"]
63```
64
65Utility AI is a scoring pipeline — every candidate action is scored, then one is selected:
66
67```text
68facts (distance, health, ammo…)
69 │ each fact → a normalized 0..1 response curve (consideration)
70 ▼
71score(action) = weight · combine(consideration_1 … consideration_n) # product+compensation or sum
72 ▼
73select: argmax · or softmax / weighted-random for variety · + hysteresis to avoid jitter
74```
75
76**Status is a three-value enum** shared by every node — this is the contract that makes the tree
77composable:
78
79```csharp
80public enum Status { Success, Failure, Running }
81
82public abstract class Node
83{
84 public abstract Status Tick(Blackboard bb, float dt);
85 public virtual void Reset() { } // called when a parent abandons this subtree
86}
87```
88
89```csharp
90// Selector = fallback/OR: return the first child that is not Failure.
91public sealed class Selector : Composite
92{
93 public override Status Tick(Blackboard bb, float dt)
94 {
95 for (; _current < Children.Count; _current++)
96 {
97 var s = Children[_current].Tick(bb, dt);
98 if (s != Status.Failure) return s; // Success or Running stops the scan
99 }
100 _current = 0;
101 return Status.Failure; // every child failed
102 }
103}
104```
105
106The reciprocal `Sequence` (AND — stop at first non-`Success`), `Parallel`, the `Blackboard`, the
107leaf base classes, and every decorator are in `references/behavior-tree-core.md`.
108
109## Utility scoring in one snippet
110
111```csharp
112// A consideration maps one raw fact to 0..1 through a response curve.
113float Score(Blackboard bb)
114{
115 float distance01 = Curves.InverseLerp01(bb.Get<float>("distToPlayer"), 20f, 2f); // near = 1
116 float health01 = Curves.Sigmoid(bb.Get<float>("health01"), k: 8f, mid: 0.4f); // hurt = low
117 // Product + compensation keeps a single 0 from vetoing while low values still dampen.
118 return Curves.CompensatedProduct(new[] { distance01, health01 });
119}
120```
121
122The full curve library (linear, quadratic, exponential, logistic/sigmoid, smoothstep), the
123`Consideration`/`UtilityAction` types, and the `UtilityEvaluator` selection strategies are in
124`references/utility-ai-system.md`.
125
126## Pitfalls
127
128- **Re-ticking a `Running` action from the root every frame restarts it.** Return `Running` and
129 resume where you left off; only `Reset()` a subtree when a parent actually abandons it.
130- **Deep trees re-evaluated wholesale each tick** waste time and cause thrash. Prefer shallow
131 trees and *conditional aborts* (a higher-priority condition can interrupt a lower branch).
132- **Un-normalized considerations.** If one curve outputs 0..100 and another 0..1, the big one
133 dominates. Every consideration must return 0..1.
134- **Utility jitter on near-ties.** Add hysteresis: give the currently-running action a small bonus
135 so the agent commits instead of oscillating.
136- **Allocating nodes, closures, or arrays every tick** creates GC spikes. Build the tree once at
137 spawn; keep per-tick work allocation-free.
138
139## References
140
141- `references/behavior-tree-core.md` — Blackboard, `Node`/leaf base classes, action & condition
142 leaves, `Sequence`/`Selector`/`Parallel`, and the decorator library (full C#).
143- `references/utility-ai-system.md` — response-curve library, `Consideration`, `UtilityAction`,
144 and the `UtilityEvaluator` (argmax, softmax, weighted-random, hysteresis).
145- `references/practical-examples.md` — a guard Patrol→Combat BT, a villager needs-based Utility
146 AI, and a hybrid agent, as drop-in templates.
147- `references/best-practices-and-pitfalls.md` — memory management, profiling, avoiding deep trees,
148 event-driven aborts, and combining Utility AI with BTs (hybrid architecture).
149
150## Related skills
151
152- `game-ai` — choose between FSM / BT / steering; A* and navmesh pathfinding.
153- `unreal-behavior-trees` — Unreal's asset-based BT/Blackboard, tasks, decorators, services.
154- `unity-navmesh` — the `NavMeshAgent` that carries out "move to" intents.
155- `physics-tuning` — agent radius, movement, and collision response for the motion layer.
156- `tower-defense`, `fps-shooter`, `rpg` — genres that compose this decision layer.