Skip to content
Bit Quirky Behavior Trees and
State Machines

Search documentation

All topics and APIs

Close returns focus to Search.

What are you looking for?

Try a common topic or enter a complete API identifier.

Browse the reference instead

Esc closes · Tab reaches results · Enter opens the focused link

v0.6.0
Menu

Entity agents

Register one graph against an entity query and let a system advance every matching entity, with no GameObject in the loop.

Horde Survival’s enemies have no GameObject, no MonoBehaviour, and no per-enemy component to hang a graph on. One registration gives every one of them the same graph, and the generated Burst carrier advances every matching entity together through ordered sample, tick, and push stages.

A survivor game spawns enemies faster than a GameObject per enemy can carry. Horde Survival’s enemies are entities: a position, a velocity, a health value, an attack intent. Their decision is still a decision though. Chase the survivor, hold when there is no target, attack when contact is permitted.

EnemyBrain.bqbehavior is that decision, and its header puts it on the entity vocabulary: %% runtime-path: entity. GraphEnemyBrainComposer registers the graph once for the whole swarm, and each entity gets its own blackboard and its own execution state inside package-owned buffers.

  • Hundreds or thousands of agents share one decision and you cannot afford a component each.
  • Every fact the decision needs already lives in component data, and every effect it causes is a component write or a spawn your systems own.
  • The tick has to fit in a frame budget with the rest of the simulation, which puts it in a Burst job. See Burst and jobs.
The entity Live view watching one enemy while the rest of the horde runs. The lock in the toolbar says it plainly: entity Live reports, it does not take edits.
The entity Live view watching one enemy while the rest of the horde runs. The lock in the toolbar says it plainly: entity Live reports, it does not take edits.

Horde Survival’s GraphEnemyBrainComposer is the shape to copy: build the query, create the runtime, register the graph, hand the handle to a system, and dispose everything the system owned when it stops.

  1. Build the query that selects your agents. Horde asks for the enemy components that prove an entity is a live enemy, and keeps that EntityQuery for the lifetime of the system.

  2. Create one runtime for the World you already have. new BehaviorEntityRuntime(world) takes the World explicitly, and nothing looks for a default World.

  3. Register the graph.

    C#
    BehaviorEntityGraphHandle graph = runtime.RegisterGraph(
    source,
    candidates,
    inputBinder,
    commandHandlers);

    A graph whose whole gameplay surface is generated entity member bindings uses the other overload, RegisterGraph(source, candidates, memberBindings, memberScheduler), which is what Horde’s enemies use. The binding set and its scheduler are supplied together or neither is supplied.

  4. Tick it from the system that owns the runtime, inside the group where its results belong.

    C#
    runtime.TickGraph(graph, frameNumber, SystemAPI.Time.DeltaTime, Dependency);

    Horde’s GraphEnemyBrainSystem sits in SimulationSystemGroup and ticks only while the run is playing.

  5. Dispose in OnDestroy. Disposal completes tracked jobs, publishes cancellations, releases graph artifacts and queries, and removes the runtime’s owner entity.

  6. Press Play, then select one of those entity rows in Unity’s Hierarchy under the live World. The graph opens in Live and reports what that single entity is doing.

  • TickGraph completes the dependency you passed and attaches entities that newly match the query. A graph without generated member stages schedules the input binder when there is one, runs one graph job across the registration’s agents, completes it, drains member readings, then dispatches commands. A graph with generated member bindings runs sample, input, tick, command dispatch, then push inside that generated entry point, and TickGraph drains member readings afterwards.
  • TickAll attaches candidates for every registration, then processes registrations in registration order. One registration is one parallel batch; several registrations are not fused into one.
  • Both calls complete their work before returning. The JobHandle parameter orders your work against the tick; it is not an outstanding handle you can carry forward.
  • A handle carries the World sequence and a runtime-local graph id, so it is usable with the runtime and World that created it.
  • ResetGraph(graph, frameNumber) cancels outstanding work for every attached agent, restores execution state and native blackboard defaults, and drains the resulting cancellations. Event occurrence state survives reset deliberately, so a continuously retained occurrence needs an opposite observation to rearm.
  • GetAgentQuery(graph) hands back the package-owned query of attached agents when your systems need to iterate them.
  • One source asset registers once on one runtime, and two registrations may not claim overlapping candidate queries. Each attempt is refused by name instead of being merged.
  • Managed values, coroutines, awaitables, C# events, and UnityEvents belong to the GameObject host. An entity graph has no managed fallback for them.
  • Entity member bindings need Burst AOT and their generated accessors. Nothing is discovered by name while the game runs.
  • A probability graph needs BehaviorEntityRandomSeed present on every candidate entity before it attaches.
  • Live attachment observes. Breakpoints and per-agent pause belong to the GameObject host, which entity debugging covers.

When it goes wrong

SymptomCheckFix
No entity ever attaches.Run the query yourself and count the matches.Fix the query, or add the components your query asks for to the spawned entities.
Registration throws about a binding set.Confirm the generated binding set and the generated scheduler are both supplied.Pass both, from the generated pair for that graph and consuming assembly.
A target mode is refused at validation.Read the graph header for the runtime path and the target mode on the node.Entity graphs address Self, one Entity handle, a singleton, or a static member.
The graph ticks but nothing in the world changes.Read whether your command handlers are registered for the tokens the graph emits.Handle each emitted token in a game-owned handler that writes the component or spawns the thing.
Editor complains about jobs after a scene change.Confirm Dispose runs exactly once from the owning system.Dispose in OnDestroy, and never reuse a handle from a disposed runtime.
Full-size image