Entity bindings
Read and write ECS component data from a graph that ticks inside a Burst job, and know which member kinds that path offers.
On this page
Horde Survival’s enemies are entities, and the graph that decides for them ticks in Burst jobs of its own, scheduled beside your simulation’s systems: it reads and writes your component fields where they live.
The problem
Section titled “The problem”Horde Survival’s enemies are entities. Their health, attack cooldown, and movement intent are IComponentData, and their systems run in jobs. A graph that wanted a MonoBehaviour in the middle would undo the reason that data is in components.
EnemyBrain.bqbehavior binds the components directly. Health.Current and ContactAttack.CooldownRemaining are field reads. AttackIntent.Ready is a two-way bool. MovementIntent.Mode and TargetPosition are writes the movement system consumes. DeadFade is watched through its enabled state, SurvivorTarget is read as a singleton, and the distance itself comes from Unity.Mathematics.math.distancesq, called as a static member.
When to reach for it
Section titled “When to reach for it”- The decision inputs are already component fields, and you want the graph reading them where they live.
- One shared entity holds the answer for everybody, which is what a singleton target is for.
- The math you need is a public static function, in your code or in
Unity.Mathematics.
See it
Section titled “See it”
-
Set the graph’s Runtime path to Entity. The Target card’s Mode control changes to the entity modes.
-
Choose the mode: Self for the entity this graph instance runs on, Handle for exactly one hop to another entity, Singleton for the one entity carrying a component, Static for a declaring type with no entity at all. The three cards below are
EnemyBrain.bqbehavior’s own. -
Pick the member. A component offers its fields, its direct properties, and its enabled state; a static type offers its static methods and fields.
-
Set the direction or the result as usual, and read Validation.
-
Reimport the graph file so the consumer assembly regenerates its binding set, then let Unity compile.
-
Register the graph with the generated binding set and its scheduler together, then run an entity carrying the components you bound.

Enemy Alive reads Health.Current off the enemy itself. Self has one row, Component, because there is nothing to look up.
Has Target watches the survivor through Singleton. The In the world row states the contract: exactly one entity carries this component, and zero or several is a failure on that evaluation, never a guess.
Distance Squared calls math.distancesq through Static: the declaring type alone, on the same card the GameObject path shows.Handle adds a Read from control with two choices: Blackboard, which reads a surfaced entity value, or Field on self, which reads an Entity field off one of this entity’s own components. Either way the entry names one entity and the Component row names what to read on it. One hop, never a chain.
What each target offers
Section titled “What each target offers”| Target | Resolves to |
|---|---|
| Self | The entity that owns this graph instance |
| Handle | Exactly one Entity hop, from a surfaced entity variable or an entity-typed field on Self |
| Singleton | The one entity carrying the chosen component, with exactly one required |
| Static | The static declaring type, with no entity lookup |
A handle takes one hop and never chains from another handle. A destroyed handle, a missing component, the wrong singleton cardinality, or a missing buffer entry fails that one evaluation and performs no access, so a stale entity index is never read.
Which member kinds each target offers
Section titled “Which member kinds each target offers”| Container | Available | Refused |
|---|---|---|
| Component | Field, direct property, enabled state, member condition on those | Method and message, because a component has no call site |
| Buffer element | Field on one addressed entry | Property, method, message |
| Static type | Method, message, field | Property |
| Managed component | Nothing | Everything, for lack of entity storage |
An event condition on this path observes an enableable component’s state or one retained event-buffer entry. A C# event, a UnityEvent, and a variable-change notification are refused, because there is no managed subscription inside a job.
A static method’s parameters pass by value or by ref. A ref parameter gets the same Read-write parameters row the GameObject path shows, with the same read-before, write-after rule, including on a void method bound as a member condition. An out, in, or params parameter is refused, and the refusal names that modifier.
A coroutine or awaitable result is refused too, with the reason stated plainly: waiting on a call needs a managed continuation this path does not have. When a member is unavailable the strip reads no entity call site and validation tags it, offering two ways out: pick another member, or run this graph on the GameObject path.
Address one buffer entry
Section titled “Address one buffer entry”Choose an entry that already exists, By index with a non-negative constant, or By field with one exact identity field and the value to match. The accessor never adds, removes, resizes, or reorders the buffer, and an out-of-range index, no match, or several matches fails that evaluation without touching anything.
How it decides
Section titled “How it decides”Result
- Success
- The access or the static call completed and its mapped outcome was succeeded.
- Failure
- The mapped outcome was failed, or a live target failure ended that evaluation.
- Running
- An authored outcome maps to running, or an entity event condition has not observed its occurrence yet.
- Carried values are exact:
bool, the signed and unsigned integrals,char,float,double, enums with those underlyings,float2,float3,float4, plus the entity handle and request identifier storage. Every one fits in sixteen bytes. - Conversions are the ordinary accepted set narrowed to those kinds, with reference assignability dropped.
- Generated access is a struct the job can carry: typed component and buffer handles, typed lookups, and a table of access rows recording read-only or read-write intent per component. Nothing about it is a managed reference.
- Member calls run inside the tick, on the job thread, through a constrained generic call, and every request completes in the same evaluation that raised it. Results are copied into the surfaced blackboard slot as bytes, and
refvalues commit before the result is chosen. - Reads that feed the blackboard sample before the core tick and writes push after it, as extra stages of the same Burst carrier.
- The event condition is the one entity family that waits: it reports Running until the occurrence is observed, consumes it once, and reports the authored result. It rearms only after a tick sees the opposite state or the entry gone.
When it goes wrong
Section titled “When it goes wrong”When it goes wrong
| Symptom | Check | Fix |
|---|---|---|
| Your component method is missing from the picker. | Read the refusal: a component has no entity call site for a method. | Move the logic into a public static function and bind that, or keep the data in fields the graph reads. |
| An event you rely on cannot be bound. | C# events are a GameObject-path shape. | Observe an enableable component, or write a retained event buffer entry the graph can watch. |
| The graph refuses to register at runtime. | A binding set without its scheduler, or the reverse, is rejected. | Supply the generated pair together, from the generated factory. |
| Validation reports the singleton cardinality. | Zero or several entities carry that component. | Make it genuinely single, or bind through Self or a Handle. |
| A buffer read fails on some entities. | The addressed entry has to exist already. | Have your producer retain the entry with a stable identity field, then address it by field. |
| The build fails on a stale generated binding set. | The compiled fingerprint differs from the expected one. | Reimport the graph file and let the consumer assembly recompile. A player build never generates it for the first time. |
- Entity variables: the blackboard side of the same contract.
- Entity runtime: registration, the job, and the tick.
- AOT and IL2CPP: what generation emits, and when to re-run it.