User Guide#

Goal: Author behavior trees, attach them to USD prims, and debug their execution at runtime.

Prerequisites#

  • Omniverse Kit 110+

  • Enable omni.behavior.tree.bundle (pulls in omni.behavior.tree.ui, omni.behavior.tree.core, and omni.behavior.tree.schema).

For Behavior Simulation nodes (Humans, Props), also enable omni.anim.behavior.bundle.

Authoring Behavior Trees#

Opening the Editor#

Open the Behavior Tree window from Window > Behavior > Behavior Tree.

Edit or New Behavior Tree

Click Edit Behavior Tree to open an existing .json behavior tree file, or New Behavior Tree to create one (you will be prompted to choose a save location).

Tip

Save your USD stage before creating a new behavior tree. Keeping .json files near the stage they belong to makes assets easier to share.

Once a tree is open, the full editor appears:

Behavior Tree Window

Toolbar#

The toolbar at the top of the window provides some controls:

  • Save / Save As / Discard changes — write the tree to disk, save under a new name, or revert the current edits to the on-disk version. Discard changes prompts before throwing away unsaved edits.

  • + Add to Prim — applies the current tree to a selected prim on the stage.

  • Constraints — shows the USD schema constraints required by the tree’s node libraries.

Pressing Play stages the editor’s working copy with the runtime, so the running tree always matches what you see in the editor — unsaved edits included. Save when you want the file on disk to match the editor (Ctrl+S or the Save button); the staging then clears so the next Play ticks the canonical file content. Discard changes also clears the staging.

If you switch to a different tree, close the editor window, or click Discard changes while there are unsaved edits, the editor prompts you to Save, Discard, or Cancel before proceeding.

Tree Dropdown#

Above the canvas, a dropdown shows the name of the currently open behavior tree file. When the stage contains multiple prims with BehaviorTreeAPI, the dropdown lists all referenced tree files so you can switch between trees quickly.

Tree dropdown for switching between behavior tree files

Building the Tree#

The editor has two panes:

  • Node Catalog (left) — lists all available node types from registered node libraries.

  • Tree Editor (center) — shows the tree structure. Drag nodes from the catalog, or right-click a node to add children or attach modifiers.

There are three kinds of nodes:

Node types
  • Composite — control flow (Sequence, Selector, Parallel).

  • Action — leaf nodes that do work (Wait, SetBlackboard, LogMessage, etc.).

  • Modifier — conditions and decorators that wrap a node (Repeat, Timeout, CheckBlackboard, etc.).

Drag nodes from the catalog into the tree editor, or right-click a node to add children or modifiers.

Drag and drop nodes

Node Status#

Every node has a status that drives how the tree executes:

Status

Meaning

Idle

The node has not been ticked yet (or was reset after completing).

Running

The node is still working. The tree will tick it again next frame.

Success

The node finished successfully.

Failure

The node finished unsuccessfully. This is normal control flow — composites like Selector use child failure to decide which branch to try next.

The lifecycle is: Idle → Running → Success / Failure → (reset) → Idle. A node may skip Running and go directly from Idle to Success or Failure if it completes in a single tick. When a node finishes (Success or Failure), its parent composite reads the result and decides what to do next. The node returns to Idle on reset — either when the tree stops, the parent resets it, or a modifier aborts it.

Node Libraries#

The node catalog lists all types from registered node libraries. The two main libraries are:

  • Built-in Node Library — composites (Sequence, Selector, Parallel), utility actions (Wait, SetBlackboard, LogMessage), and general-purpose modifiers (Repeat, Timeout, CheckBlackboard, etc.) from omni.behavior.tree.core.

  • Behavior Simulation Node Library — action and modifier nodes for driving behavior agents from omni.anim.behavior.tree.

Configuring Ports#

Select a node or modifier in the tree to open its details in the Node Inspector panel on the right.

Node Inspector

In this example, a MoveTo node has a RandomNavMeshPoint modifier attached. The modifier’s point output port is wired into MoveTo’s target input port, so the agent will walk to a random point on the NavMesh. The sampled point is cached until the modifier resets (e.g. when the owning node completes or is aborted).

Each port row in the inspector is laid out as:

Port Name

Accepted Type

Port Type

Port Widget

The port’s name (e.g. target).

The variant type the port accepts (e.g. Float3, Boolean). Click the dropdown to change the type when a port accepts multiple types.

How the port gets its value — one of the three port types described below.

A widget appropriate for the accepted type and port type (text field, checkbox, blackboard picker, etc.).

Ports are either input or output. Input ports provide data to the node and are configurable in the inspector. Output ports are produced only by modifiers and can be wired into input ports of the owning node (see Modifier Output below).

Port types — for input ports, the selector between the accepted-type dropdown and the port widget determines where the port reads its value at runtime:

  • Constant Value — a literal value saved in the behavior tree file (number, string, bool, etc.).

  • Blackboard Reference — reads a key from a blackboard at runtime. The widget pairs a scope dropdown with a key picker:

    • External — the tree’s externally-provided main blackboard.

    • Local — the tree’s per-instance hidden local blackboard (the same store edited via the Variables panel; the terms Variables and local blackboard are interchangeable).

    It’s the user’s responsibility to ensure the data type stored in the blackboard is compatible with the port at runtime.

  • Modifier Output — wires a modifier’s output port into this input. The dropdown shows available modifiers attached to this node and their output ports. The port widget will automatically detect the potentially compatible modifier outputs.

Output ports (modifier outputs and any node port declared with direction: "output") offer Blackboard binding (with the same scope dropdown) that routes the port’s writes to the chosen blackboard. Unbound output is shown as / (discarded) — the value is dropped unless something downstream reads it.

When a modifier is selected in the tree, the inspector shows its own ports below the owner node’s ports, along with the Abort mode dropdown.

Changes are saved with Ctrl+S or the Save button in the toolbar. Pressing Play runs the in-memory tree whether or not it has been saved.

Attaching Modifiers#

Right-click any node and choose Add Modifier to attach a modifier. Modifiers appear as tags on the node. Each modifier has its own ports, configurable in the inspector when selected.

Modifiers attached to a node

Modifier Types#

Modifiers generally fall into two categories:

  • Condition modifiers (e.g. CheckBlackboard, CheckEvent, CheckAgentDistance) — gate whether the owning node is allowed to run. If the condition fails, the node fails or is aborted without ever ticking. These are the modifiers that interact with abort modes.

  • Behavior modifiers (e.g. Repeat, Timeout, Delay, RandomChoice, RandomUniformFloat) — wrap the node’s execution to add looping, timing, or value sampling. They do not gate; they always let the node tick.

A single modifier can do both, but in practice most fall cleanly into one camp.

Modifier Ordering#

Modifier order is left-to-right as shown on the node tag. You can reorder modifiers by right-clicking a tag and choosing Move Left or Move Right.

All condition checks run before any modifier ticks. This means a behavior modifier placed before a condition modifier in the tag order will still only tick after all conditions pass. Within the condition checks and within the ticks, evaluation follows the left-to-right tag order.

In the example above, RandomChoice is listed before Repeat. When the node ticks, RandomChoice samples a value first, then Repeat wraps the node in a loop — so the node runs repeatedly with the sampled value. If the order were reversed, Repeat would be the outermost wrapper: it would loop the entire execution including RandomChoice, causing a fresh sample on each repetition instead of reusing the same value.

Child Priority#

Within a composite node, child priority is determined by index: the first child has the highest priority, and later children have progressively lower priority. This ordering is significant for abort modes — a condition modifier with Lower Priority abort on a high-priority child can interrupt any currently running lower-priority sibling.

Abort Modes#

Condition modifiers have an Abort dropdown in the inspector that controls reactive re-evaluation of their condition while the tree is running. Behavior modifiers do not have conditions, so the abort mode setting has no effect on them.

Abort mode dropdown and Selector with CheckBlackboard using Lower Priority

Mode

Behavior

None

The condition is checked once when the node first enters. No re-evaluation while running.

Self

Re-evaluates every tick. If the condition becomes false, the owning node is aborted and reset.

Lower Priority

Re-evaluates every tick. If the condition becomes true, lower-priority siblings (children that come after this branch in the parent composite) are aborted so this branch can run instead.

Both

Combines Self and Lower Priority.

The image above shows a common reactive pattern: a Selector with two Sequence branches. The first Sequence has a CheckBlackboard modifier with Lower Priority abort. When its condition becomes true, any running lower-priority branch (the second Sequence) is interrupted and the first branch takes over. When the condition is false, the Selector falls through to the second branch as a default. The debug overlay below shows this tree in action.

Adding Trees to Prims#

To execute a behavior tree on a prim:

  1. Select a prim in the stage.

  2. In the tree editor toolbar, click Add To Prim and choose the target prim.

This applies BehaviorTreeAPI to the prim and links it to the currently open behavior tree file. The command validates that the prim satisfies any constraints declared by the tree’s node libraries before applying.

You can also do this from Python using Kit commands (undoable):

import omni.kit.commands
from pxr import Sdf

omni.kit.commands.execute(
    "ApplyBehaviorTreeAPICommand",
    prim_path=Sdf.Path("/World/MyRobot"),
    tree_file_path=Sdf.AssetPath("path/to/tree.json"),
)

Additional commands:

  • CreateBlackboardCommand(prim_path=None) — creates a BehaviorTreeBlackboard prim.

  • CreateNodeLibraryCommand(prim_path=None) — creates a BehaviorTreeNodeLibrary prim.

  • RemoveBehaviorTreeAPICommand(prim_path) — removes BehaviorTreeAPI from a prim.

Property Inspector#

When a prim with BehaviorTreeAPI or a BehaviorTreeBlackboard prim is selected, the Properties panel shows a dedicated widget for editing behavior tree settings.

Behavior Tree Properties#

Behavior Tree property inspector

Selecting a prim with BehaviorTreeAPI applied shows the Behavior Tree section with:

  • Behavior Tree — the path to the .json behavior tree file. Use the tree editor to modify the tree structure. Click Edit Behavior Tree in the header to open the editor for this prim’s tree.

  • Blackboard — relationship to a BehaviorTreeBlackboard prim. Change this to point the tree at a different blackboard.

  • Enabled — enable or disable automatic execution of this tree. The tree can still be ticked manually if needed.

  • Auto Restart — when checked, the tree automatically restarts after reaching a terminal status (success or failure).

  • Instance Override — per-prim port value overrides, described below.

Instance Overrides#

Multiple prims can share the same behavior tree file but need different port values — for example, two agents using the same patrol tree but with different wait durations or target positions. Instance overrides let you change port values on a specific prim without editing the shared tree file.

Click the + button on the Instance Override row to add an override. Each override row shows the node path and port name (e.g. /Root/Wait:duration means the duration port of the /Root/Wait node), the port type selector, and a value field. Modifier ports are also overridable: in the override menu, expand a node to find a submenu per attached modifier listing its input ports. Overrides are applied at tree creation time.

The top of the + menu also exposes a Variables submenu listing every key in the tree’s Variables panel (a.k.a. local blackboard). Picking one adds a row that overlays that variable’s seed value for this prim only — useful for per-prim tuning of shared trees. Variable overrides serialize as localBlackboardOverrides in the override JSON and are always constant values (no binding modes).

Instance Override rows including Variables overrides

Blackboard Properties#

Behavior Tree Blackboard property editor

Selecting a BehaviorTreeBlackboard prim shows the Behavior Tree Blackboard section. The blackboard is a weakly typed key-value store: any key can hold any type, and the user is responsible for ensuring that the type stored in a key is compatible with the port that will read it at runtime.

Use + to add a new entry and - to remove the selected entry. Each row has:

  • Key — the entry name that nodes reference through Blackboard Reference ports.

  • Type — the value type (Integer, Float, Boolean, String, Float2, Float3, etc.). Changing the type resets the value to the new type’s default.

  • Array — toggle to make the entry an array of the selected type.

  • Value — the initial value. Nodes can read and write this at runtime.

To use a blackboard, create a BehaviorTreeBlackboard prim (via the Create Blackboard command or the UI) and link it to the tree prim through the omni:behavior:tree:blackboard relationship.

Nodes reference blackboard keys through Blackboard Reference ports. For example, a SetBlackboard node can write a value, and a CheckBlackboard modifier can read it to control flow.

Variables (Tree-Local Blackboard)#

Every tree carries a hidden, per-instance key-value store separate from the externally-provided main blackboard. The editor calls this the Variables panel; the code, schema, and rest of these docs call it the local blackboard. The two terms refer to the same thing and are used interchangeably.

Authors seed it on the tree descriptor (it serializes as the localBlackboard field in the tree JSON), and the runtime materializes a fresh copy per tree instance — so two prims attached to the same tree file get independent local state without any per-prim authoring.

Editing Variables in the editor#

Variables panel in the tree editor

Open the Variables tab in the editor’s inspector pane (next to Node Inspector). The panel works like the blackboard editor: + adds an entry, - removes the selected one, and each row has Key, Type, Array, and Value columns. Edits write to the tree descriptor’s seed, so every prim referencing the tree picks up the new variable at the next tree creation.

While the timeline is playing the panel switches to Variables (live) and shows the current per-instance values read-only.

Binding ports to Variables#

Bind a port to a variable by setting Blackboard Reference mode and choosing Local in the scope dropdown. BlackboardRef("local", k) resolves to the per-tree local blackboard at runtime; the same key written via a setOutput (with the same scope) can be read back through any port bound to it.

Local vs. External#

Scope

What it is

When to use

External

The BehaviorTreeBlackboard prim linked through omni:behavior:tree:blackboard. Shared with anything else that points at the same prim.

Data shared across prims, scenes, or external callers (e.g. perception writing to a blackboard read by several trees).

Local (Variables)

Per-tree-instance store seeded from the descriptor. Not addressable from outside the tree.

Per-instance state that should not leak (counters, sampled goals, cached choices) — the default when you just need scratch storage scoped to one tree.

Pattern: Variables as tree inputs#

A tree’s Variables panel doubles as its parameter interface. Define each tunable knob as a Variable, bind ports inside the tree to those Variables, then override per-prim from the property inspector. The shared tree file stays unchanged; per-prim differences live entirely in instance overrides.

For example, a wander tree might expose:

Variables panel exposing per-tree inputs (Point_m, Point_stage, Speed, WanderRadius)

Inside the tree, the movement node’s target port binds to Point_m (Local scope), the wander sampler’s radius port binds to WanderRadius, and the locomotion node’s speed port binds to Speed. Per-prim tuning then happens via Instance Override, without touching the tree file:

Instance Override rows tuning WanderRadius and Speed for one prim

Two prims that share the same wander tree can differ entirely in their wander radius and speed by overriding Variables:WanderRadius / Variables:Speed on each prim.

Pattern: Sharing modifier output across the tree#

A modifier’s output port is normally readable only by the node the modifier is attached to (via the Modifier Output binding mode on that node’s input ports). To make a modifier’s output reachable from any node in the tree, set the modifier output’s binding to Blackboard with Local scope and pick a Variable key — the modifier now writes into that Variable each tick, and any port anywhere in the tree can read it via Local-scope Blackboard Reference.

Example using PopQueue: suppose a planner branch fills a Variable queue targets with destinations (via PushQueue writing to Variables:targets). A worker branch elsewhere in the tree has a “do-work” action with a PopQueue modifier on it; PopQueue reads from Variables:targets and writes the popped value to its item output. By routing that item output to Variables:current_target, any other node in the tree — even outside the worker subtree — can read the current target via a Local-scope blackboard reference on its own input port. The Variable acts as a tree-internal mailbox; the modifier-owner relationship no longer gates who can see the value.

Runtime Debugging#

Attaching a Debug Target#

While the tree editor is open and the timeline is playing:

  1. Click the Debug Target dropdown in the toolbar.

  2. A list shows all prims on the stage that reference the currently open behavior tree file.

  3. Select a prim to attach the debug overlay to that running tree instance.

Debug Target dropdown listing prims with behavior trees

If you select a debug target before pressing Play, the overlay automatically attaches when playback begins.

Execution Overlay#

Once a debug target is attached, the tree editor shows a live overlay:

Debug overlay showing node execution status

In this example, the Selector’s first Sequence node shows a red X — its CheckBlackboard condition failed, so the Sequence itself failed without ticking its children. The Selector falls through to the second branch, which is actively running (green checkmarks).

  • Node status is shown as an icon beside the node: green checkmark for success, red X for failure. A light blue tint on the node background means it is currently executing.

  • Modifier status is shown purely by the tag color: blue means the modifier is executing, red means failure.

Terminal status icons (success/failure) hold briefly before clearing, so rapid transitions remain visible.

Live Port Values#

While a debug target is attached, the Node Inspector switches to a read-only view showing live runtime values for the selected node’s ports. Select any node or modifier in the tree to see its current input values. Values refresh automatically during playback.

Timeline Integration#

Trees managed by the USD runtime tick automatically during Play:

  • Trees start when the timeline begins playing.

  • Trees tick each frame with the simulation delta time.

  • Trees stop and reset when the timeline stops.

Set omni:behavior:tree:autoRestart to true on the prim to automatically restart trees that reach a terminal status (success or failure).

Next Steps#