Performance
Behavior Designer Pro runs tree traversal as Burst-compiled jobs over Entities. This page covers what that architecture costs, which settings bound the per-tick work, and how to measure a result that reflects a player build rather than the Editor.
How a tree is executed
Traversal is not performed by the Behavior Tree component. The component owns the graph data and an entity; the traversal systems evaluate that entity as part of the DOTS system groups.
| Stage | What runs | Threading |
|---|---|---|
| Before traversal | Reevaluation of Conditionals marked by a conditional abort | Burst-compiled jobs |
| Traversal | Branch and Task status evaluation | Burst-compiled IJobEntity scheduled with ScheduleParallel |
| Task execution | GameObject Task logic | Managed code on the main thread |
| Cleanup | Entity command buffer playback and flag removal | Burst-compiled jobs |
Trees are evaluated in parallel across entities, so cost scales with the number of Tasks actually evaluated rather than with the number of agents alone. An agent whose tree is parked on a single Running Task is far cheaper than an agent reevaluating a wide branch each tick.
Measure in the Editor and confirm in a build
The traversal, before-traversal, and cleanup systems are Burst-compiled in the Editor as well as in a player build, so Editor timings reflect the same compiled traversal code that ships.
The Editor still carries overhead that a build does not: collections and job safety checks, Entities journaling when enabled, and the graph window’s own runtime rendering while a tree is open. Close the Behavior Designer window when taking measurements, and confirm final numbers in a development player build.
If traversal timings look unexpectedly slow, check that Jobs > Burst > Enable Compilation is on. Disabling it makes every Burst job fall back to managed execution.
Bound the work with Evaluation Type
Evaluation Type on the Behavior Tree component determines how much of a tree may be traversed in one tick.
| Evaluation Type | Behavior | Use it when |
|---|---|---|
| Entire Tree | Traversal continues until every branch returns Running or the tree completes. | The tree is small, or a decision must resolve within the same frame. |
| Count | Traversal stops after Max Evaluation Count Tasks have been evaluated and resumes on the next tick. | A large tree would otherwise produce a spike, or per-frame cost must stay bounded across many agents. |
Max Evaluation Count accepts 1 through 65535. Setting Count mode spreads a deep traversal across several frames, which trades decision latency for a flatter frame time. Evaluation always ends early once all branches return Running, so Count mode adds no cost to a tree that is already parked.
Control when a tree ticks
Update Mode selects between the two runtime cadences:
- Every Frame evaluates the tree on each system-group update.
- Manual evaluates only when project code calls
Tick(). CallingTick()while Update Mode is not Manual logs a warning and does nothing.
Manual mode is the most direct way to reduce cost for agents that do not need frame-rate decisions. Distant or inactive agents can be ticked on a timer, on a staggered schedule, or in response to a gameplay event rather than every frame.
GameObject Tasks and Entity Tasks
Both routes use the same Burst-compiled traversal. They differ in what happens when a Task’s own logic runs.
| GameObject Task | Entity Task | |
|---|---|---|
| Task logic | Managed C# on the main thread | Burst-compiled job code |
| Shared Variable access | Values are synchronized between ECS and managed storage around execution | Read and written directly as component data |
| Best suited to | Component-based gameplay, moderate agent counts | Large agent counts and data-oriented gameplay |
The synchronization step is the main additional cost of a GameObject Task: ECS-backed Shared Variables are copied to their managed values before the Task runs and flushed back afterward. The sync happens once per entity per pass rather than once per Task, so a tree with several GameObject Tasks does not pay the cost repeatedly in the same pass.
This does not make GameObject Tasks a bad default. Choose them for ordinary component-based gameplay and move a behavior to Entity Tasks when profiling a build shows that its Task execution, not its traversal, is the bottleneck. GameObject or Entity Tasks covers the authoring differences.
Keep conditional aborts scoped
A conditional abort keeps its Conditional live: a Conditional under a Lower Priority abort is reevaluated on every tick that any branch to its right is active, and Both widens that to the branch itself. This is the intended cost of a reactive tree, but it is easy to apply more broadly than needed.
- Prefer the narrowest abort type that produces the required reaction. Self is cheaper than Both when lower-priority preemption is not needed.
- Put expensive Conditionals — raycasts, physics queries, large searches — behind a cheaper Conditional so the expensive one is reached less often.
- Use None on branches that should complete once entered.
Conditional Aborts explains the reaction semantics that these choices control.
Tree size and memory layout
Each tree entity stores its evaluation state as a bitmask sized from the Task count when the tree starts. The runtime selects one of five fixed-capacity components, so a tree crossing a size boundary moves to a larger archetype and fits fewer entities per chunk.
The smallest tier covers trees up to 191 Tasks, which is comfortably above a typical authored tree. Count mode reserves one additional element for the execution count, lowering that tier’s ceiling to 127 Tasks. The larger tiers extend well beyond any practical tree size.
For a large behavior library, prefer several focused trees or Subtrees over one very large graph. That keeps the common agents in the smallest tier and makes the graphs easier to reason about.
What to profile
- Close the Behavior Designer window, then profile in the Editor for iteration and in a development player build for final numbers.
- In the Profiler’s Timeline view, confirm that the traversal jobs are spread across worker threads. A single-threaded traversal usually means only one tree is being evaluated.
- Use the Entities Systems window to identify which Behavior Designer system is consuming time, and confirm that trees which should be idle are not scheduling work.
- Check main-thread time separately. GameObject Task logic and the Shared Variable sync run there, so a main-thread spike points at Task implementations rather than traversal.
- Compare a representative agent count rather than one agent. Parallel traversal shows its benefit only across many entities.