โ ๏ธ The API is still evolving. Expect breaking changes while the library matures.
A Behavior Tree (BT) is a mathematical model of plan execution used in computer science, robotics, control systems and video games. Behavior Trees are used as an alternative to state machines. A Behavior Tree describes switching between a finite set of tasks in a modular fashion. Its strength lies in its ability to create very complex tasks composed of simple tasks, without worrying how the simple tasks are implemented.
This project is a modernized fork of BrainTree and BehaviorTree.CPP focused on debuggable behavior trees for robotics, simulation, and games. The codebase targets C++17, ships with a YAML builder, subtree support, scoped blackboards, and an open source visualizer.
- Use YAML files to describe trees, subtrees, and blackboard values, then instantiate them with a builder class.
- Scoped blackboards: every subtree can inherit or override keys via the
${key}syntax, making parameter passing explicit. You can store your own structure. - Available builtin nodes: reactive/with-memory composites, classic decorators, and sugar actions/conditions for quick experiments.
- Visualizer: inspect running trees without proprietary tools (like Groot).
- Prerequisites:
sudo apt-get install libyaml-cpp-dev- Download and compilation:
git clone https://github.com/Lecrapouille/BehaviorTree --recurse
cd BehaviorTree
make -j8 # builds library + examples
make test -j8 # optional unit tests- This library also comes with a standalone application that listens on port 9090 (by default) and visualizes the running Behavior Tree connected to it.
./build/BehaviorTree-Visualizer --port 9090A blank windows shall appear waiting a connection with a client application using this library. Launch it before a demo to see live node states.
- Run the bundled examples
make examples -j8
./build/Example-GameState # doc/examples/GameState
./build/Example-Patrol # doc/examples/PatrolEach example folder contains its own Makefile, YAML description, and C++ entry
point. Use them as templates for your own projects.
A Behavior Tree (BT) is a directed acyclic graph used to express decision logic.
- Tree: A hierarchical parent-children structure of nodes and a blackboard. The root is the initial node. A sub-tree is just an hierarchy view to make the tree smaller and more human readable.
- Nodes: The basic building blocks of a behavior tree. Nodes are of three types: composite, decorator, and leaf. Each nodes return
SUCCESS,FAILURE, orRUNNING. Parents nodes, depending on their type, decide which children to invoke and how to interpret their status.- Composite: A node that has several children (at least one child).
- Decorator: A node that has exactly one child and modifies its status (decoration).
- Leaf: Nodes that don't have any children; these are the actual actions that interact with the system (i.e., robot commands).
- Blackboard: Since node instances do not know each other and may want to exchange data, they use a shared key/value structure (dictionary) for exchanging data. In the same way than C++ these values are the arguments of functions (here node): they can be either an
inputor andoutputorinput-output.
---
config:
theme: forest
class:
hideEmptyMembersBox: true
---
classDiagram
class Tree
class Node
class Composite
class Decorator
class Leaf
class Blackboard
Node <|-- Composite
Node <|-- Decorator
Node <|-- Leaf
Tree --> Node : root
Tree --> Blackboard
The following diagram shows available nodes by this library. Each node will be fully described in this document
---
config:
theme: forest
classWidth: 200
classFontSize: 14
classFontFamily: "Trebuchet MS"
curve: basis
class:
hideEmptyMembersBox: true
---
classDiagram
Node <|-- Decorator
Node <|-- Composite
Node <|-- Leaf
Composite <|-- Sequence
Sequence <|-- Parallel Sequence
Sequence <|-- Sequence With Memory
Sequence <|-- Reactive Sequence
Composite <|-- Selector
Selector <|-- Parallel Selector
Selector <|-- Selector With Memory
Selector <|-- Reactive Selector
Decorator <|-- Inverter
Decorator <|-- Force Success
Decorator <|-- Force Failure
Decorator <|-- Repeater
Decorator <|-- Timeout
Leaf <|-- Action
Leaf <|-- Condition
Leaf <|-- Always Success
Leaf <|-- Always Failure
- Leaves:
- Condition
- Action
- Always Success/failure
- Decorator:
- Inverter
- Force Success/failure
- Repeater
- Timeout
- Composite:
- Sequence (with or without memory or reactive)
- Selector (with or without memory or reactive)
- Parallel Sequence
- ๐ขโ SUCCESS: The task was completed successfully. In behavior tree visualizer, success nodes are painted in green.
- ๐ด FAILURE: The task could not be completed or a condition was not met. In behavior tree visualizer, failed nodes are painted in red.
- ๐กโ RUNNING: The task is still in progress and needs more time to complete. In behavior tree visualizer, running nodes are painted in yellow.
First, let remember the two important definitions:
- Concurrency: When two or more tasks can start, run, and complete in overlapping time periods. It doesn't necessarily mean they will ever both be running at the same instant.
- Parallelism is when tasks run at the same time in different threads, e.g., on a multi-core processor.
The execution way for running a tree is single-threaded with concurrency: nodes are executed in discrete time concurrently by their tick() callback. Tick is the fundamental execution mechanism in behavior trees:
- Each tick represents one update cycle of the behavior tree. Ticks occur at a frequency determined by the application (e.g., 10Hz for a robot, 60Hz for a game).
- A tick is a propagation of execution signals from parent to children. It flows down the tree from the root to the leaves following specific rules for each node type. When a parent node is ticked, it decides which children to tick based on its type.
- When a node is ticked, it executes its logic and returns a status:
SUCCESS,FAILURE, orRUNNING. Since the logic is not executed inside a thread, the execution should return as soon as possible to avoid blocking the entire flow of execution of the tree.
The tick() function implements a template method pattern with hooks for onSetUp(), onRunning() and onTearDown().
- A node calls
onSetUp()on the first execution. This allows for initialization that can either succeed (SUCCESS) or fail (FAILURE). - A node calls
onRunning()on subsequent ticks, returning SUCCESS, FAILURE, or RUNNING. - When a node transitions from RUNNING to SUCCESS/FAILURE,
onTearDown()is called. This allows for cleanup operations. - Different node types (Composite, Decorator, Leaf) implement different strategies for propagating ticks, as explained in details in next sections of this document.
Here is the sequence diagram showing the execution flow:
sequenceDiagram
participant App as Application
participant Tree as Tree
participant Node as Node
participant Child as Child Node
App->>Tree: tick()
activate Tree
alt Root is null or invalid
Tree-->>App: FAILURE
else Root exists and valid
Tree->>Node: tick()
activate Node
alt Status != RUNNING (First tick)
Node->>Node: onSetUp()
Node-->>Node: status
end
alt Status != FAILURE
Node->>Node: onRunning()
activate Node
alt Composite/Decorator
Node->>Child: tick() children
activate Child
Child-->>Node: child status
deactivate Child
end
Node-->>Node: status
deactivate Node
alt Status != RUNNING
Node->>Node: onTearDown(status)
end
end
Node-->>Tree: status
deactivate Node
Tree-->>App: status
end
deactivate Tree
Let's consider a robot with the following minimal tree that allows it to cross a room.
The tree is composed of the following nodes:
- Root node depicted by the black dot.
- A single composite node named
Sequencewhich is a composite node executing children in order until one fails. This node is given in all behavior tree libraries. - For our example, three project-specific leaves depicting three actions the robot can perform:
- opening a door,
- walking through the doorway,
- and closing the door.
Thanks to the Sequence node, the three actions open the door, walk and close the door are executed in this order and, in our case, immediately return the status SUCCESS. While three actions have not all ended with SUCCESS the status of the Sequence node stays to RUNNING. If one of the actions fails (return FAILURE), the status of the sequence node becomes FAILURE and stop executing children nodes. If all actions end with SUCCESS, the status of sequence node becomes SUCCESS.
In a real situation, each ticked actions of the robot will not immediately return SUCCESS or FAILURE but will last several seconds or minutes to be achieved. Each action shall not take too much time to not block the tree tick() to make reactive the robot by allowing the execution of other nodes. For this reason it will return RUNNING.
You can read the following documents:
- Introduction to Behavior Trees โ roboticseabass.com
- Behavior Trees in Detail โ lisyarus.github.io
- BehaviorTree.CPP Documentation โ related library with similar concepts
Before describing each behavior nodes, let first explain the structure used in YAML files to store a behavior tree. Every YAML file shall contain the BehaviorTree: section and can contain tow optional sections Blackboard: and SubTrees:. For example:
Blackboard: # Initial scoped keys available to all nodes
counter: 42
position:
x: 1.0
y: 2.0
z: 0.5
BehaviorTree: # Root tree definition
Sequence:
children: [...]
SubTrees: # Reusable subtrees with isolated scopes
PatrolLoop:
Sequence:
children: [...]Blackboard: Section
Keys defined under Blackboard populate the root blackboard. Complex structures
are fully supported: nested maps, arrays, booleans, strings, and numbers. All
nodes in the tree can access these values via blackboard->get<T>("key").
Variable References with ${key}
The ${key} syntax allows you to reference any blackboard value by name,
copying it into another field. This works with primitives, maps, and entire
nested structures:
Blackboard:
primary_enemy:
name: "Drone-A"
health: 35
selected_target: ${primary_enemy} # Copies the entire enemy mapSubTree Scope Isolation
When a SubTree node is instantiated, the builder creates a child blackboard
(parent->createChild()) for that subtree. Parameters passed to the subtree
are stored in this child scope, preventing data leakage into the parent. The
child blackboard inherits all parent keys but can override them locally.
BehaviorTree:
SubTree:
name: ProcessTarget
reference: EngageEnemy
parameters:
target: ${primary_enemy} # Passed to child blackboard
SubTrees:
EngageEnemy:
Sequence:
children:
- Action:
name: AimWeapon
parameters:
enemy: ${target} # Reads from child scopeSee doc/examples/GameState/GameState.yaml and doc/examples/Patrol/Patrol.yaml
for realistic examples of global data, subtree composition, and parameter passing.
Simple leaf that always returns SUCCESS. Do not confuse with the decorator Succeeder.
Behavior:
- Returns SUCCESS.
YAML Syntax:
- SuccessSimple leaf that always returns FAILURE. Do not confuse this leaf with the decorator Failer.
Behavior:
- Returns FAILURE.
YAML Syntax:
- FailureEvaluates a condition and returns SUCCESS or FAILURE. Useful for:
- Boolean checks.
- Sensor readings.
- State validation.
Behavior:
- Returns SUCCESS if the condition is true.
- Returns FAILURE if the condition is false.
- Can never return RUNNING.
Example of YAML Syntax:
- Condition:
name: "IsEnemyVisible"
parameters:
range: 100.0The field parameters: is optional.
Performs a short action or a chunk of a long action and returns a status.
Behavior:
- Return SUCCESS if action completes successfully.
- Return FAILURE if action fails.
- Return RUNNING if action is still executing (need more time before succeeding ot failing).
Example of YAML Syntax:
- Action:
name: "MoveToTarget"
parameters:
speed: 1.0
target: [10, 20]The field parameters: is optional.
YAML Syntax: Since these nodes have a single child: field which has a single child node. The syntax follows the pattern:
- <parent node>:
child:
- <child node>:Inverts the result of its child node. Useful for:
- Checking if something is NOT true.
- Creating "unless" conditions.
Mnemonic: Negate, Not.
Behavior:
- Return SUCCESS if child returns FAILURE.
- Return FAILURE if child returns SUCCESS.
- Return RUNNING if child returns RUNNING.
Example YAML Syntax:
- Inverter:
child:
- Condition:
name: "IsEnemyVisible"Similar to the leaf Success but only transforms child failures: It always returns SUCCESS regardless of child's status. It is useful for:
- Implementing "try but don't fail" semantics.
- Optional tasks that shouldn't cause the whole sequence to fail.
- Debugging behavior trees.
- Temporarily disabling failure conditions.
- Testing tree structure.
Behavior:
- If child returns SUCCESS or RUNNING, returns the same status.
- If child returns FAILURE, returns SUCCESS instead.
Example YAML Syntax:
- Succeeder:
child:
- Action:
name: "OptionalTask"The opposite of ForceSuccess. Similar to the leaf Failure but only transforms child success: It always returns SUCCESS regardless of child's status. It is useful for:
- Testing negative conditions.
- Preventing specific success cases.
- Implementing "succeed only if not" semantics.
Behavior:
- If child returns FAILURE or RUNNING, returns the same status.
- If child returns SUCCESS, returns FAILURE instead.
Example YAML Syntax:
- ForceFailure:
child:
- Action:
name: "UndesiredResult"Repeats its child node a specified number of times. It is useful for:
- Retrying failed actions.
- Implementing persistence behaviors.
- Creating loops in behavior trees.
Behavior:
- Repeats child until it succeeds or reaches the limit.
- Can be configured for infinite repetition.
- Returns SUCCESS when child succeeds or limit is reached.
- Returns RUNNING while repeating.
Example YAML Syntax:
- Repeater:
times: 3
child:
- Action:
name: "TryOpenDoor"Limits the execution time of its child. Useful for:
- Preventing actions from running too long.
- Time-critical operations.
- Implementing fallback triggers.
Behavior:
- Starts a timer when the child begins execution.
- Returns FAILURE if the timeout is reached.
- Otherwise, returns the child's status.
Example YAML Syntax:
- Timeout:
milliseconds: 5000
child:
- Action:
name: "LongRunningTask"A Sequence node executes children in order until one fails. It logics is the and operator: if any child returns FAILURE, the sequence stops and returns FAILURE. If all children return SUCCESS, the sequence returns SUCCESS.
Mnemonic: "And", "Do In Order".
Behavior:
- Tick each child node in order.
- Return SUCCESS if all children succeed.
- Return FAILURE if any child fails.
- Return RUNNING if any child is running.
Use Case YAML Syntax:
- Sequence:
children:
- Action:
name: "OpenDoor"
- Action:
name: "Walk"
- Action:
name: "CloseDoor"Used for tasks that must be completed in sequence, like:
- Open the door.
- Walk into the room.
- Close the door.
Beware:
If the action Walk fails, the door will remain open since the last action CloseDoor is skipped.
Executes all children simultaneously. It comes in two forms: Parallel and ParallelAll.
Behavior:
-
Parallel:
- Ticks all children simultaneously.
- Returns SUCCESS if at least M children succeed (M is configurable, default is 1).
- Returns FAILURE if more than N-M children fail (N is the total number of children).
- Returns RUNNING otherwise.
-
ParallelAll:
- Ticks all children simultaneously.
- Returns SUCCESS only if ALL children succeed.
- Returns FAILURE if ANY child fails.
- Returns RUNNING otherwise.
Use Case - Parallel:
- Parallel:
threshold: 2 # Succeeds when at least 2 children succeed
children:
- Action:
name: "MonitorBattery"
- Action:
name: "MonitorObstacles"
- Action:
name: "MonitorEnemies"Perfect for scenarios where:
- You need any one of several conditions to trigger a behavior.
- You want redundant systems where only some need to succeed.
- You need to monitor multiple conditions with different priorities.
Use Case - ParallelAll:
- ParallelAll:
children:
- Action:
name: "MoveForward"
- Action:
name: "ScanEnvironment"
- Action:
name: "MaintainBalance"Perfect for multitasking where:
- All tasks must succeed simultaneously.
- Background processes need to run alongside main tasks.
- Multiple conditions must all be satisfied.
Key differences:
Paralleloffers more flexibility with a configurable success threshold.ParallelAllis more strict, requiring all children to succeed.- Both execute all children at each tick regardless of individual results.
Same than Sequence but memorizes the point where it stopped. Used in:
- Tasks that continue over multiple ticks.
- Preserving progress in a sequence.
- Avoiding redundant execution of already completed steps.
Mnemonic: Continue In Order.
Behavior:
- Similar to Sequence, but it remembers which child it was executing if a child returns RUNNING.
- On the next tick, it will continue from the last running child instead of starting from the beginning.
- Returns SUCCESS if all children succeed.
- Returns FAILURE if any child fails.
- Returns RUNNING if the current child is running.
Use Case:
- SequenceWithMemory:
children:
- Action:
name: "PrepareTask"
- Action:
name: "LongRunningTask"
- Action:
name: "CompleteTask"Same than Sequence but checks all previous children at each tick. Needed when:
- Conditions that must remain true throughout an action.
- Safety checks that need constant verification.
- Tasks that should stop immediately if prerequisites are no longer met.
Mnemonic: Check All
Behavior:
- Always starts from the first child.
- Re-evaluates all previously successful children at each tick.
- If a previously successful child now fails, the sequence fails.
- Returns SUCCESS if all children succeed.
- Returns FAILURE if any child fails.
- Returns RUNNING if the current child is running.
Use Case:
- ReactiveSequence:
children:
- Condition:
name: "IsPathClear"
- Action:
name: "MoveForward"Also known under the name of Selector in other documentation or project. A Selector node tries its children in order until one succeeds. it acts like the or operator: if a child returns SUCCESS, the selector stops and returns SUCCESS. If all children return FAILURE, the selector returns FAILURE. This is useful for fallback strategies.
Mnemonic: "Or", "Try Until Success", "Try alternatives".
Behavior:
- Ticks each child node in order.
- Returns SUCCESS if any child succeeds.
- Returns FAILURE if all children fail.
- Returns RUNNING if any child is running.
Use Case:
- Fallback:
children:
- Action:
name: "FindEnemy"
- Action:
name: "PatrolArea"
- Action:
name: "ReturnToBase"Explanation:
- Try to find an enemy.
- If no enemy found, patrol the area.
- If patrol fails, return to base.
Similar to Fallback because it "tries until success" but memorizes the point where it stopped. Perfect for:
- Complex fallback scenarios that span multiple ticks.
- Recovery strategies that need to continue from where they left off.
- Avoiding restarting already attempted recovery steps.
Mnemonic: Remember And Try
Behavior:
- Similar to Selector, but it remembers which child it was executing if a child returns RUNNING.
- On the next tick, it will continue from the last running child instead of starting from the beginning.
- Returns SUCCESS if any child succeeds.
- Returns FAILURE if all children fail.
- Returns RUNNING if the current child is running.
Use Case:
- FallbackWithMemory:
children:
- Action:
name: "TryPrimaryMethod"
- Action:
name: "TryBackupMethod"
- Action:
name: "EmergencyProtocol"Similar to Fallback because it checks for success and tries again at each tick.
Mnemonic: Check Until Success
Behavior:
- Always starts from the first child.
- Re-evaluates previously failed children at each tick.
- If a previously failed child now succeeds, the selector succeeds.
- Returns SUCCESS if any child succeeds.
- Returns FAILURE if all children fail.
- Returns RUNNING if the current child is running.
Use Case:
- ReactiveFallback:
children:
- Condition:
name: "IsPathA"
- Condition:
name: "IsPathB"
- Action:
name: "FindAlternativePath"Perfect for:
- Continuously checking if preferred options become available.
- Reactive planning that adapts to changing conditions.
- Implementing priority-based decisions that need constant reassessment.
Each code snippet is intentionally short; follow the "Full example" links for complete programs under doc/examples. We show two exclusive construction ways:
- the programmatic construction in the case you do not want to use YAML fil (not recommended).
- and the YAML file (recommended way).
Full example: doc/examples/Nodes/LeafExample.cpp
Leaf nodes are the endpoints of a behavior tree where actual work happens. The two main types are:
- Action โ performs work and returns a status (
SUCCESS,FAILURE,RUNNING) - Condition โ evaluates a predicate and returns
SUCCESSorFAILURE - Success/Failure โ helper nodes that always return a fixed status
Programmatic construction:
Here the syntax to create a condition, using a lambda.
auto battery_ok = bt::Node::create<bt::Condition>(
bt::Condition::Function([bb = blackboard]()
{
return bb->get<int>("battery").value_or(0) > 20;
}),
blackboard);Here the syntax to create an action using a class with an optional blackboard (mandatory for action using parameters).
auto report_enemy = bt::Node::create<ReportEnemy>(blackboard);YAML construction:
# Action node
Action:
name: ReportEnemy
parameters:
target: "Drone-A"
# Condition node
Condition:
name: BatteryOK
parameters:
battery: ${battery}
# Always-success helper
Success:
name: MissionCompleteAction with Blackboard Access:
class ReportEnemy final: public bt::Action
{
public:
explicit ReportEnemy(bt::Blackboard::Ptr blackboard)
{
m_blackboard = std::move(blackboard);
}
bt::Status onRunning() override
{
auto target = m_blackboard->get<std::string>("target");
if (!target)
{
std::cout << "No target assigned\n";
return bt::Status::FAILURE;
}
std::cout << "Engaging " << *target << '\n';
return bt::Status::SUCCESS;
}
};A Sequence node executes its children left to right. If any child returns
FAILURE, the sequence stops and returns FAILURE. If all children return
SUCCESS, the sequence returns SUCCESS.
Full example: doc/examples/Nodes/SequenceExample.cpp
Programmatic construction:
auto sequence = bt::Node::create<bt::Sequence>();
sequence->addChild(bt::Node::create<OpenDoor>());
sequence->addChild(bt::Node::create<WalkThrough>());
sequence->addChild(bt::Node::create<CloseDoor>());YAML construction:
Sequence:
name: EnterRoom
children:
- Action: { name: "OpenDoor" }
- Action: { name: "WalkThrough" }
- Action: { name: "CloseDoor" }In bot case, you have to create the C++ classes OpenDoor, WalkThrough, CloseDoor.
class OpenDoor final: public bt::Action
{
public:
bt::Status onRunning() override
{
std::cout << "Opening door\n";
return bt::Status::SUCCESS;
}
};
class WalkThrough final: public bt::Action
{
public:
bt::Status onRunning() override
{
std::cout << "Walking through doorway\n";
return bt::Status::SUCCESS;
}
};A Selector node tries its children left to right until one succeeds. If a child returns SUCCESS, the selector stops and returns SUCCESS. If all children return FAILURE, the selector returns FAILURE. This is useful for fallback strategies.
Full example: doc/examples/Nodes/SelectorExample.cpp
Programmatic construction:
auto selector = bt::Node::create<bt::Selector>();
selector->addChild(bt::Node::create<ScanPrimary>());
selector->addChild(bt::Node::create<ScanFallback>());YAML construction:
Selector:
name: FindTarget
children:
- Action: { name: "ScanPrimary" }
- Action: { name: "ScanFallback" }In bot case, you have to create the C++ classes ScanPrimary, ScanFallback.
class ScanPrimary final: public bt::Action
{
public:
bt::Status onRunning() override
{
std::cout << "Scanning primary sector ... not found\n";
return bt::Status::FAILURE; // Try next option
}
};
class ScanFallback final: public bt::Action
{
public:
bt::Status onRunning() override
{
std::cout << "Fallback scan succeeded\n";
return bt::Status::SUCCESS; // Selector succeeds
}
};A Parallel node ticks all its children simultaneously and aggregates their
results. You specify success and failure thresholds: if enough children succeed
or fail to meet the threshold, the parallel node returns accordingly.
Full example: doc/examples/Nodes/ParallelExample.cpp
Programmatic construction:
auto parallel = bt::Node::create<bt::Parallel>(2, 2); // successThreshold, failureThreshold
parallel->addChild(bt::Node::create<MonitorBattery>());
parallel->addChild(bt::Node::create<MonitorObstacles>());
parallel->addChild(bt::Node::create<MonitorComms>());YAML construction:
Parallel:
success_threshold: 2
failure_threshold: 2
children:
- Action: { name: "MonitorBattery" }
- Action: { name: "MonitorObstacles" }
- Action: { name: "MonitorComms" }In bot cases, you have to create classes MonitorBattery, MonitorObstacles and MonitorComms.
class MonitorBattery final: public bt::Action
{
public:
bt::Status onRunning() override
{
std::cout << "Battery OK\n";
return bt::Status::SUCCESS;
}
};
class MonitorObstacles final: public bt::Action
{
public:
bt::Status onRunning() override
{
std::cout << "Obstacles detected -> FAILURE\n";
return bt::Status::FAILURE;
}
};Decorators wrap a single child node and modify its behavior. Common decorators
include Inverter (flip success/failure), Retry (retry on failure), and ForceSuccess (always return success).
Full example: doc/examples/Nodes/DecoratorExample.cpp
Programmatic construction:
auto retry = bt::Node::create<bt::Retry>(3); // Max 3 attempts
retry->setChild(bt::Node::create<FlakyConnect>());YAML construction:
# Retry decorator
Retry:
attempts: 3
child:
- Action: { name: "Connect" }
# Inverter decorator
Inverter:
child:
- Condition: { name: "IsEnemyNear" }
# ForceSuccess decorator
ForceSuccess:
child:
- Action: { name: "OptionalCleanup" }Here the action class to create:
class FlakyConnect final: public bt::Action
{
public:
bt::Status onRunning() override
{
++m_attempts;
if (m_attempts < 3)
{
std::cout << "Connect attempt " << m_attempts << " failed\n";
return bt::Status::FAILURE;
}
std::cout << "Connect attempt " << m_attempts << " succeeded\n";
return bt::Status::SUCCESS;
}
private:
int m_attempts = 0;
};A SubTree node allows you to define reusable tree fragments in the SubTrees
section of your YAML file. When instantiated, the builder creates a child
blackboard for parameter isolation. This enables modular, composable behavior trees.
Full examples:
- doc/examples/Nodes/SubTreeExample.yaml
- doc/examples/Patrol/Patrol.yaml
- doc/examples/GameState/GameState.yaml
Simple SubTree Example:
Blackboard:
greetings:
text: "Hello from parent scope"
BehaviorTree:
Sequence:
name: Root
children:
- SubTree:
name: GreetAndReport
reference: GreetingSubTree
SubTrees:
GreetingSubTree:
Sequence:
children:
- Action:
name: PrintGreeting
parameters:
message: ${greetings} # Reads from parent blackboard
- Action:
name: ReportCompletionComplex SubTree with Structured Data:
Blackboard:
enemy_contact:
name: "Intruder-17"
health: 50
position: { x: 2.0, y: 1.0, z: 0.5 }
BehaviorTree:
Sequence:
children:
- SubTree:
name: EngageEnemy
reference: EngageEnemy
SubTrees:
EngageEnemy:
Selector:
children:
- Action:
name: AttemptNonLethal
parameters:
contact: ${enemy_contact} # Entire struct passed
- Action:
name: NeutralizeThreat
parameters:
target: ${enemy_contact}Each SubTree is instantiated with an isolated child blackboard. Parameters
from the parent scope are passed explicitly, preventing unintended data leakage
between subtrees.
The blackboard is a shared data store accessible by all nodes in the tree. It supports primitives (int, bool, string, double) and complex nested structures (maps, arrays).
Use descriptive keys:
blackboard->set<int>("battery", 75);
blackboard->set<std::string>("target", "Drone-A");Access values with type safety:
auto battery = blackboard->get<int>("battery");
if (battery && *battery > 20) {
// Proceed
}Store complex structures as maps (YAML):
Blackboard:
game_state:
score: 1280
time: 42.5
enemies:
- name: "Drone-A"
health: 35
position: { x: 0.0, y: 5.0, z: 1.5 }
- name: "Drone-B"
health: 20
position: { x: -3.5, y: 2.25, z: 0.0 }Access nested maps in C++:
auto snapshot = blackboard->get<bt::AnyMap>("game_state");
if (snapshot) {
// Process nested structure
auto enemies = (*snapshot)["enemies"];
}Use ${key} to reference existing data:
Blackboard:
primary_enemy:
name: "Drone-A"
health: 35
selected_target: ${primary_enemy} # Copy entire structurePass parameters to subtrees explicitly:
BehaviorTree:
SubTree:
name: ProcessEnemy
reference: EngageEnemy
parameters:
target: ${primary_enemy} # Passed to child blackboardRemember: The builder automatically calls setBlackboard() for nodes created
from YAML, so your custom constructors can accept the blackboard as a parameter
and store it directly.
See doc/examples/GameState/GameState.yaml for a complete example of structured blackboard data with nested maps, arrays, and parameter passing.
./build/BehaviorTreeVisualizer --port 9090Inside your application:
#include "BehaviorTree/Visualizer.hpp"
bt::Visualizer viz(*tree, "127.0.0.1", 9090);
while (!viz.isConnected()) { std::this_thread::sleep_for(1s); }
while (running) {
viz.updateDebugInfo();
tree->tick();
}The UI colors nodes (green = success, red = failure, orange = running) and shows ports/blackboard values.