An in-depth, plain-language guide to behavior trees: what they are, how they work, how nodes communicate, and how Ember helps you build them.
What Problem Does Ember Solve?
The Challenge
When building AI for games, robots, or simulations, you need to model decision-making and actions. Simple "if-then" code quickly becomes messy:
- "If enemy visible, attack; else if health low, retreat; else patrol; but what if we're reloading? And what about the objective?"
Traditional approaches (state machines, decision trees, scripts) become hard to maintain as complexity grows.
The Solution: Behavior Trees
Behavior trees are a visual, hierarchical way to organize logic. Instead of tangled code, you build a tree of nodes where:
- Each node has a clear role
- The structure shows the flow of control
- You can see the whole logic at a glance
- Changes are local—edit one branch without breaking others
Ember is a visual editor for behavior trees. It lets you:
- Create trees by clicking and connecting—no coding
- Edit existing XML behavior tree files
- Visualize structure, navigate between trees, and manage multi-file projects
- Validate trees for common errors before you run them
What Is a Behavior Tree?
Definition
A behavior tree is a hierarchical structure of nodes. It looks like an upside-down tree:
[Root]
│
┌────────┴────────┐
[Sequence] [Selector]
│ │
┌────────┼────────┐ ┌─────┴─────┐
[Condition] [Action] [Action] [Action] [Action]
- Root: The top node; execution starts here
- Branches: Control nodes (Sequence, Selector, etc.) that decide which children run
- Leaves: Action, Condition, or SubTree nodes that do actual work
Where Did Behavior Trees Come From?
Behavior trees were popularized in video game AI (Halo 2, 2004) as a replacement for finite state machines. They spread to:
- Robotics — Robot decision-making (navigation, manipulation)
- Simulation — Training, testing, virtual agents
- Automation — Workflow and task orchestration
Why a Tree?
| Benefit | Explanation |
| Modularity | Each branch can be designed and tested separately |
| Reusability | SubTrees let you reuse logic across trees |
| Readability | The structure shows intent: "try A, then B, then C" |
| Reactivity | Trees can re-evaluate from the root each tick, adapting to changes |
How Nodes Communicate
Nodes communicate in two ways: through the tree structure (parent-child) and through a blackboard (shared data).
1. Parent–Child Communication (Control Flow)
Parents tell children when to run and receive their result.
Parent (Sequence)
│
"Run your first child"
│
▼
Child 1 returns Success
│
"Run your next child"
│
▼
Child 2 returns Failure
│
Sequence stops, returns Failure to its parent
Flow of control:
- A tick arrives at the root
- The root passes the tick to its children (according to its type)
- Children execute and return Success, Failure, or Running
- The parent uses that result to decide what to do next
- The result propagates back up the tree
2. Blackboard Communication (Shared Data)
Nodes that are not parent-child can share data through a **blackboard**—a shared key-value store.
┌─────────────────────────────────────────────────────────────┐
│ BLACKBOARD │
│ target_position: (10, 5, 0) │
│ enemy_visible: true │
│ health: 75 │
└─────────────────────────────────────────────────────────────┘
▲ ▲ ▲
│ write │ read │ read
│ │ │
[GetTarget] [MoveToTarget] [Attack]
(Action) (Action) (Action)
How it works:
- Output port: A node writes a value to the blackboard (e.g. target = position)
- Input port: A node reads a value from the blackboard (e.g. goal = target)
- In-out port: A node reads and writes the same variable
In Ember, you connect nodes to blackboard variables by typing {variable_name} in the Properties panel.
3. Combined Example: Data Flow
Sequence
├── GetTarget → writes {target} to blackboard
├── IsTargetReachable → reads {target}, returns Success/Failure
├── MoveToTarget → reads {target}, moves there
└── Attack → reads {target}, attacks
The Sequence controls the order. The blackboard passes data between nodes.
The Tick-Based Execution Model
Behavior trees run in ticks. Each tick:
- The game/robot/simulation calls tick() on the tree
- The tick propagates from root to leaves
- Nodes execute and return a status
- The status propagates back to the root
- Next tick, the process repeats
Tick Flow Diagram
Tick 1:
Root (Selector)
→ Try Child 1 (Sequence)
→ Child 1.1 (Condition): Success
→ Child 1.2 (Action): Running ← Action needs more time
← Sequence returns Running
← Selector returns Running
(Tree is "busy"—Action still running)
Tick 2:
Root (Selector)
→ Resume Child 1 (Sequence)
→ Child 1.1: (already succeeded, may be skipped in reactive mode)
→ Child 1.2 (Action): Success ← Action finished
← Sequence returns Success
← Selector returns Success
(Tree completed successfully)
Reactive vs Non-Reactive
| Mode | Behavior |
| Reactive | Each tick starts from the root. Higher-priority branches can preempt running nodes. More responsive to changes. |
| Non-reactive | Resumes from Running nodes. More efficient, but less responsive to external changes. |
Node Types in Detail
Control Nodes
Sequence
Runs children in order. Stops on first Failure. Succeeds only if all succeed.
Sequence
├── Condition A → Success → continue
├── Condition B → Success → continue
├── Action C → Running → return Running (wait for next tick)
└── (Action C finishes) → Success → Sequence succeeds
Use case: "Do A, then B, then C. If any step fails, abort."
Example: "Patrol"
Sequence
├── GetNextWaypoint (get next point)
├── MoveToWaypoint (walk there)
└── WaitAtWaypoint (pause, then repeat)
Flow diagram:
[Sequence]
│
┌─────┴─────┐
│ │
▼ ▼
[Child 1] [Child 2] ...
Success? ──► Run next
Failure? ──► Stop, return Failure
Running? ──► Return Running, resume next tick
Selector (Fallback)
Runs children in order. Stops on first Success. Fails only if all fail.
Selector
├── Condition A → Failure → try next
├── Condition B → Failure → try next
└── Action C → Success → Selector succeeds
Use case: "Try A. If that fails, try B. If that fails, try C."
Example: "Choose behavior"
Selector
├── IsEnemyVisible? → Attack
├── IsHealthLow? → Retreat
├── HasObjective? → GoToObjective
└── Patrol (default)
Flow diagram:
[Selector]
│
┌─────┴─────┐
│ │
▼ ▼
[Child 1] [Child 2] ...
Success? ──► Stop, return Success
Failure? ──► Try next child
Running? ──► Return Running
All failed? ──► Return Failure
Parallel
Runs all children at the same time. Succeeds or fails based on a threshold.
Parallel (success_threshold=2)
├── Action A → Success
├── Action B → Running
└── Action C → Success
→ 2 succeeded, threshold met → Parallel succeeds
Use case: "Do several things at once. Need at least N to succeed."
Decorator Nodes
Decorators have one child and modify its behavior.
| Decorator | Input | Output |
| Inverter | Success | Failure |
| Failure | Success |
| Running | Running |
| Repeat(N) | Run child N times | Success when all succeed |
| Retry(N) | Run child up to N times | Success when child succeeds |
| Timeout(T) | Run child, max T seconds | Failure if time exceeded |
| ForceSuccess | Any | Success |
| ForceFailure | Any | Failure |
Example: Inverter
"Inverter" makes a condition mean the opposite:
IsDoorClosed? → Success when closed
Inverter(IsDoorClosed?) → Success when OPEN
Leaf Nodes
Action
Performs an action. Returns Success, Failure, or Running.
- Success: Action completed (e.g. reached destination)
- Failure: Action failed (e.g. path blocked)
- Running: Action in progress (e.g. still walking)
Condition
Checks something. Returns only Success or Failure (never Running).
- Should be fast and side-effect free
- Examples: IsEnemyVisible?, IsHealthLow?, HasAmmo?
SubTree
References another behavior tree. Enables modular design and reuse.
MainTree
├── Sequence
│ ├── SubTree(PatrolBehavior)
│ └── SubTree(CombatBehavior)
Node States Summary
| State | Meaning | Who returns it |
| Success | Task completed successfully | All node types |
| Failure | Task failed or cannot be done | All node types |
| Running | Task in progress, need more ticks | Actions (and thus parents) |
Real-World Example: Game Enemy AI
Root (Selector) "What should I do?"
│
├── Selector "Combat"
│ ├── Sequence "Attack"
│ │ ├── IsEnemyVisible? (Condition)
│ │ ├── IsInRange? (Condition)
│ │ └── Attack (Action, uses {target} from blackboard)
│ │
│ └── Sequence "Chase"
│ ├── IsEnemyVisible? (Condition)
│ ├── SetTarget (Action, writes {target})
│ └── MoveToTarget (Action, reads {target})
│
├── Selector "Survival"
│ ├── Sequence "Heal"
│ │ ├── IsHealthLow? (Condition)
│ │ ├── GoToHealthPack (Action)
│ │ └── UseHealthPack (Action)
│ │
│ └── Sequence "Flee"
│ ├── IsHealthCritical? (Condition)
│ └── RunAway (Action)
│
└── Patrol (Action) "Default: just patrol"
Flow: Combat has priority. If no combat, try survival. If no survival needed, patrol.
Blackboard in Depth
Port Types
| Port | Direction | Example |
| Input | Read only | goal — "Where should I move?" |
| Output | Write only | target — "I found the enemy here" |
| In-out | Read and write | counter — "Increment this" |
Scoping
| Scope | Visibility |
| Local | Only within one tree |
| Shared | Across SubTrees in a project |
| Global | Project-wide |
Example: Passing Data
Sequence
├── FindEnemy
│ Output: {enemy_position}
│
├── MoveTo
│ Input: {enemy_position} (reads what FindEnemy wrote)
│
└── Attack
Input: {enemy_position}
How Ember Helps You
Visual Editing
- See the whole tree at once
- Drag to pan, scroll to zoom
- Right-click to add nodes
- Select to edit properties
No Coding for Basic Use
- Add nodes from the context menu
- Edit IDs, names, and ports in the Properties panel
- Connect to blackboard with {variable_name}
Validation
Ember checks for:
- Missing SubTree references
- Circular dependencies
- Invalid configurations
- Blackboard type mismatches
Multi-File Projects
- Split trees across files
- Reference trees with filename.xml::TreeID
- Shared blackboards across files
Quick Reference: Decision Guide
| I want to... | Use... |
| Do A, then B, then C | Sequence |
| Try A, if fail try B | Selector |
| Do A and B at the same time | Parallel |
| Flip Success/Failure | Inverter |
| Repeat something N times | Repeat |
| Do something with a time limit | Timeout |
| Reuse another tree | SubTree |
| Check something | Condition |
| Do something | Action |
| Pass data between nodes | Blackboard {variable} |
See Also