Entity variables
What a graph's memory looks like when thousands of entities each own a copy of it, and which declarations the unmanaged path refuses.
On this page
A thousand Horde enemies each need their own memory, and none of them can afford a heap object to hold it. An entity graph keeps its variables in bytes each entity owns, so the Burst tick reads them without touching a managed object.
The problem
Section titled “The problem”Horde Survival runs its enemies as entities and ticks them in a job. A managed blackboard would put a heap object behind every variable read, which is the one thing that path cannot afford. So the entity blackboard stores fixed-size values, and everything about authoring it follows from that.
EnemyBrain.bqbehavior declares its runtime path as entity and keeps no managed data at all. Its values come from ECS component fields through bindings: enemy_position and attack_target_position are float3 reads, distance_sq is the float result of a static math call, attack_permission is a two-way bool on AttackIntent.Ready.
When to reach for the entity path
Section titled “When to reach for the entity path”- The decision data already lives in
IComponentData, as Horde’sHealth,ContactAttack, andMovementIntentdo. - You need thousands of agents ticking in a job rather than thousands of MonoBehaviours.
- Every value the behavior touches fits in fixed-size storage.
Stay on the GameObject path when the behavior needs a managed reference, a C# event, a coroutine, an awaitable, or a dictionary. There is no managed fallback inside an entity graph: the path is a property of the graph, and changing it is a deliberate edit.
See it
Section titled “See it”
-
Set the graph’s Runtime path to Entity. The graph file records that choice in its header.
-
Declare the value the behavior carries. A
float, anint, abool, an enum, or afloat2/float3/float4is carried; astringor a project class is not. -
Bind the component field that supplies or receives it. Entity bindings covers the target modes.
-
Read Validation before you run. An entity graph holding managed data is refused there, with the graph, the variable, its complete declared type, the line it sits on, and the reason.
-
Register the graph with its generated binding set and scheduler, then run one entity.
What differs from the GameObject blackboard
Section titled “What differs from the GameObject blackboard”| GameObject blackboard | Entity blackboard |
|---|---|
string, project classes, asset and interface references, component slots |
Refused; keep that data in your own systems |
| Dictionaries, nested collections | Refused; a list or array of a carried element type is accepted |
| Managed values kept in a sidecar beside the native bytes | Every value is fixed-size bytes, at sixteen bytes per slot |
| A component slot filled at runtime | An Entity handle surfaced by a binding, never authored as a literal |
| Live values readable and some rows writable | Live values readable |
The carried set is exact: bool, sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, enums with those integral underlyings, Unity.Mathematics.float2, float3, float4, plus the entity handle and request identifier storage the product owns.
How it decides
Section titled “How it decides”- A declaration the entity path cannot carry blocks the graph before its first tick. The diagnostic names the runtime path and the reason; nothing degrades into a managed object silently.
- A
List<T>orT[]is accepted whenTis itself carried. Its authored elements are copied into per-entity buffer storage. Collections are blackboard storage on this path, so they are never passed as a member argument or returned as a member result. - An
Entityvalue can arrive from a binding and be used as a handle target. It cannot be typed in, because an entity index and version only mean something inside the World that issued them. - Conversions on this path are the ordinary conversion set restricted to carried kinds, with reference assignability dropped, since there are no references to assign.
BehaviorEntityRuntime.ResetGraphrestores native defaults and declared collection contents for every attached agent in that registration, and deliberately keeps entity event-occurrence state so a continuously matched retained event does not rearm.- Input binders are narrower than the authorable set. An entity binder resolves typed handles for
bool,int,float,float2,float3,float4,UnityEngine.EntityId, andUnity.Entities.Entity, so a broader declared type is fine for the graph and still unavailable to a binder handle.
When it goes wrong
Section titled “When it goes wrong”When it goes wrong
| Symptom | Check | Fix |
|---|---|---|
| The graph refuses to bake after you set the path to Entity. | Read Validation: it names the declaration and the type that cannot be carried. | Replace the managed declaration with a carried type, or keep this graph on the GameObject path. |
| A dictionary you authored has disappeared from the options. | Dictionaries and nested collections are GameObject-path shapes. | Flatten the lookup into carried values, or hold it in a system the graph reads through a binding. |
| You cannot type an Entity value into a variable. | The row is listed and unpickable by design. | Surface the handle from a binding, then use it as a Handle target. |
| Every entity shows the same value. | Look at the binding target: a Static target reads one CLR declaration for the whole World. | Read the value from the entity you are ticking, through Self or a Handle. |
| A list element type is rejected while the list itself looks fine. | The element type has to be carried on its own. | Use a carried element type, or move the collection into your own component. |
- Entity bindings: the four entity target modes and the member kinds each one offers.
- Entity runtime: registration, Burst, and the tick itself.
- Variables and types: the full authorable set on the GameObject path.
- Horde Survival: the enemies whose brain this page describes.