From unity
Unity 6 ECS/DOTS development guide. Use when working with Entity Component System, data-oriented design, Jobs system, or Burst Compiler. Covers Entities, IComponentData, ISystem, EntityManager, baking, Jobs, and Burst. Based on Unity 6.3 LTS documentation.
npx claudepluginhub cdata/aria-skills --plugin unityThis skill uses the workspace's default tool permissions.
> Based on Unity 6.3 LTS (6000.3) -- Entities 1.3, Burst 1.8, Jobs package
Provides Unity ECS patterns with DOTS, Jobs, and Burst for high-performance games, managing thousands of entities, data-oriented systems, and CPU optimization.
Provides production patterns for Unity DOTS including Entity Component System, Job System, and Burst Compiler. Use for high-performance games, efficient entity management, data-oriented systems, and CPU optimization.
Provides production patterns for Unity ECS, DOTS, Jobs, and Burst to build high-performance games with thousands of entities and CPU optimization.
Share bugs, ideas, or general feedback.
Based on Unity 6.3 LTS (6000.3) -- Entities 1.3, Burst 1.8, Jobs package
ECS (Entity Component System) is Unity's data-oriented framework. It replaces the GameObject/MonoBehaviour model with a cache-friendly, high-performance architecture.
| Aspect | MonoBehaviour | ECS |
|---|---|---|
| Identity | GameObject (heavy, managed) | Entity (lightweight int ID) |
| Data | Class fields on components | Unmanaged structs (IComponentData) |
| Logic | Methods on MonoBehaviour | Systems (ISystem / SystemBase) |
| Memory layout | Scattered across heap | Contiguous chunks by archetype |
| Threading | Main thread only | Jobs + Burst for parallel work |
Entities are lightweight identifiers (an index + version integer). They have no behavior and no data -- they are handles used to associate components.
Components are unmanaged structs with no methods. They hold only data.
using Unity.Entities;
public struct Speed : IComponentData
{
public float Value;
}
public struct Health : IComponentData
{
public float Current;
public float Max;
}
An archetype is a unique combination of component types. All entities with the same set of components share an archetype. Entities are stored in chunks (16 KB blocks) grouped by archetype, enabling cache-efficient iteration.
A World contains an EntityManager and a set of systems. The default world is created automatically. EntityManager is the primary API for creating/destroying entities and adding/removing components.
Baking converts authoring GameObjects into runtime entities. This is a one-way conversion that happens at build time or when a SubScene is loaded in the Editor.
using Unity.Entities;
// Authoring component (MonoBehaviour on GameObject)
public class SpeedAuthoring : MonoBehaviour
{
public float speed;
}
// Baker converts authoring data to ECS components
public class SpeedBaker : Baker<SpeedAuthoring>
{
public override void Bake(SpeedAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new Speed { Value = authoring.speed });
}
}
| Flag | Use |
|---|---|
Dynamic | Entity moves at runtime (gets LocalTransform, LocalToWorld) |
Renderable | Entity is rendered but not moved by code |
WorldSpace | Entity uses world-space transform only |
None | No transform components added |
Systems contain all logic. They iterate over entities that match a component query.
ISystem is the modern, unmanaged system type. It is Burst-compatible and should be preferred over SystemBase.
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
[BurstCompile]
public partial struct MovementSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<Speed>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
float deltaTime = SystemAPI.Time.DeltaTime;
foreach (var (transform, speed) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<Speed>>())
{
transform.ValueRW.Position +=
new float3(0, 0, speed.ValueRO.Value * deltaTime);
}
}
[BurstCompile]
public void OnDestroy(ref SystemState state) { }
}
Key points:
partial struct[BurstCompile] on the struct and each methodOnCreate, OnUpdate, OnDestroy receive ref SystemStatestate.RequireForUpdate<T>() -- system only runs when T existsSystemAPI.Query<T>() -- type-safe foreach iterationSystemBase is a managed class-based system. It supports managed code but cannot be Burst-compiled at the system level. Use ISystem for all new code. SystemBase uses Entities.ForEach() lambda syntax instead of SystemAPI.Query.
Systems are organized into groups that update in a defined order:
InitializationSystemGroup
-> BeginInitializationEntityCommandBufferSystem
SimulationSystemGroup (default group)
-> BeginSimulationEntityCommandBufferSystem
-> [Your systems go here by default]
-> EndSimulationEntityCommandBufferSystem
PresentationSystemGroup
-> BeginPresentationEntityCommandBufferSystem
Control ordering with attributes:
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(OtherSystem))]
[UpdateAfter(typeof(AnotherSystem))]
public partial struct MySystem : ISystem { }
SystemAPI provides static methods for safe, Burst-compatible access:
| Method | Purpose |
|---|---|
SystemAPI.Query<T1, T2>() | Iterate matching entities |
SystemAPI.Time | Access TimeData (DeltaTime, ElapsedTime) |
SystemAPI.GetSingleton<T>() | Get singleton component value |
SystemAPI.SetSingleton<T>(value) | Set singleton component value |
SystemAPI.GetComponent<T>(entity) | Read component from entity |
SystemAPI.SetComponent<T>(entity, value) | Write component on entity |
SystemAPI.HasComponent<T>(entity) | Check if entity has component |
SystemAPI.GetComponentLookup<T>() | Get random-access lookup |
SystemAPI.GetBuffer<T>(entity) | Get DynamicBuffer |
SystemAPI.GetAspect<T>(entity) | Get aspect for entity |
The fundamental component type. Must be an unmanaged struct (no reference types, no managed arrays).
Shared across entities -- entities with the same shared component value are grouped into the same chunk. Changing a shared component value is a structural change.
public struct TeamId : ISharedComponentData
{
public int Value;
}
Variable-length arrays attached to entities.
[InternalBufferCapacity(8)]
public struct DamageEvent : IBufferElementData
{
public float Value;
public Entity Source;
}
// Usage in a system
foreach (var (buffer, entity) in SystemAPI.Query<DynamicBuffer<DamageEvent>>()
.WithEntityAccess())
{
for (int i = 0; i < buffer.Length; i++)
totalDamage += buffer[i].Value;
buffer.Clear();
}
Persists after entity destruction. Used for cleanup logic (e.g., releasing native resources). The entity is not fully destroyed until all cleanup components are removed.
Components that can be toggled on/off without structural changes (no chunk moves).
public struct Stunned : IComponentData, IEnableableComponent { }
// Toggle in system
SystemAPI.SetComponentEnabled<Stunned>(entity, true);
bool isStunned = SystemAPI.IsComponentEnabled<Stunned>(entity);
Zero-size structs used purely for filtering queries. No fields -- zero memory cost per entity.
public struct IsPlayer : IComponentData { }
// Direct creation (structural change -- main thread only)
Entity e = entityManager.CreateEntity(typeof(Speed), typeof(Health));
// Preferred: use EntityCommandBuffer for deferred structural changes
var ecb = new EntityCommandBuffer(Allocator.TempJob);
Entity e2 = ecb.CreateEntity();
ecb.AddComponent(e2, new Speed { Value = 5f });
ecb.Playback(entityManager);
ecb.Dispose();
Structural changes (create/destroy entity, add/remove component) cannot happen during iteration. Use ECB to defer them. See references/ecs-api.md for full ECB API.
[BurstCompile]
public partial struct SpawnSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var ecbSingleton = SystemAPI.GetSingleton<
EndSimulationEntityCommandBufferSystem.Singleton>();
var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
// ecb is played back automatically -- no Playback/Dispose needed
foreach (var spawner in SystemAPI.Query<RefRW<Spawner>>())
{
if (spawner.ValueRO.Timer <= 0)
{
ecb.Instantiate(spawner.ValueRO.Prefab);
spawner.ValueRW.Timer = spawner.ValueRO.Interval;
}
}
}
}
Use when exactly one entity has a given component. Access via SystemAPI.GetSingleton<T>() / SystemAPI.SetSingleton<T>(value) / SystemAPI.GetSingletonRW<T>(). Useful for global config, game state, and similar one-of-a-kind data.
Aspects group related components into a single access wrapper with optional methods.
public readonly partial struct CharacterAspect : IAspect
{
public readonly RefRW<LocalTransform> Transform;
public readonly RefRO<Speed> Speed;
public readonly RefRW<Health> Health;
public void Move(float3 direction, float deltaTime)
{
Transform.ValueRW.Position +=
direction * Speed.ValueRO.Value * deltaTime;
}
}
// Use in system
foreach (var character in SystemAPI.Query<CharacterAspect>())
{
character.Move(new float3(1, 0, 0), deltaTime);
}
Automatically generates an EntityQuery from the Execute parameter types.
[BurstCompile]
public partial struct MoveJob : IJobEntity
{
public float DeltaTime;
void Execute(ref LocalTransform transform, in Speed speed)
{
transform.Position += new float3(0, 0, speed.Value * DeltaTime);
}
}
// Schedule from system
[BurstCompile]
public partial struct MoveSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
new MoveJob
{
DeltaTime = SystemAPI.Time.DeltaTime
}.ScheduleParallel();
}
}
Manual chunk iteration for maximum control. See references/jobs-burst.md for full examples.
| Method | Behavior |
|---|---|
Run() | Execute on main thread (no job scheduling) |
Schedule() | Single-threaded job on worker thread |
ScheduleParallel() | Multi-threaded across chunks |
[BurstCompile]
public partial struct DestroyDeadJob : IJobEntity
{
public EntityCommandBuffer.ParallelWriter ECB;
void Execute([ChunkIndexInQuery] int sortKey, in Health health, Entity entity)
{
if (health.Current <= 0)
ECB.DestroyEntity(sortKey, entity);
}
}
The Burst compiler translates IL/.NET bytecode into highly optimized native code using LLVM.
Add [BurstCompile] to ISystem structs (and each method) and IJobEntity structs. Burst compiles them to optimized native code via LLVM.
class, string, List<T>, managed arrays)new for reference types)SharedStatic<T> instead)NativeContainer types for collectionsBurst-optimized math library replacing UnityEngine.Mathf and Vector3:
using Unity.Mathematics;
float3 position = new float3(1, 2, 3);
quaternion rot = quaternion.Euler(0, math.PI, 0);
float dist = math.distance(a, b);
float3 dir = math.normalize(b - a);
float val = math.lerp(0f, 1f, 0.5f);
| UnityEngine | Unity.Mathematics |
|---|---|
Vector3 | float3 |
Vector2 | float2 |
Quaternion | quaternion |
Mathf.Lerp | math.lerp |
Mathf.Sin | math.sin |
Matrix4x4 | float4x4 |
| What | Why It's Wrong | Fix |
|---|---|---|
Using SystemBase when ISystem works | Cannot Burst-compile system, heap allocations | Use ISystem (partial struct) with [BurstCompile] |
| Structural changes during iteration | Invalidates iterators, causes exceptions | Use EntityCommandBuffer for deferred changes |
| Structural changes in parallel jobs without ECB | Race conditions, crashes | Use EntityCommandBuffer.ParallelWriter with sort key |
| Managed types in Burst-compiled code | Burst cannot compile managed types | Use unmanaged structs, FixedString, NativeContainer |
Missing [BurstCompile] on ISystem | System runs as managed code, loses performance | Add [BurstCompile] to struct and all methods |
Allocating NativeArray every frame without disposing | Memory leak | Allocate with Allocator.Temp or dispose in OnDestroy |
Using UnityEngine.Mathf in Burst code | Not Burst-optimized | Use Unity.Mathematics.math |
Forgetting state.RequireForUpdate<T>() | System runs even when no matching entities exist | Call in OnCreate to skip updates when unnecessary |
Modifying SharedComponentData frequently | Each change is a structural change (chunk move) | Use regular IComponentData for frequently changing data |
Using GetComponent in inner loops | Random access breaks cache coherency | Use SystemAPI.Query for linear iteration |
| Class / Struct | Key Members | Notes |
|---|---|---|
EntityManager | CreateEntity, DestroyEntity, AddComponent<T>, RemoveComponent<T>, GetComponentData<T>, SetComponentData<T> | Main-thread only; structural changes |
EntityCommandBuffer | CreateEntity, DestroyEntity, AddComponent, RemoveComponent, Instantiate, Playback | Deferred structural changes |
ECB.ParallelWriter | Same as ECB but with sortKey parameter | Thread-safe for parallel jobs |
SystemAPI | Query<T>, Time, GetSingleton<T>, GetComponent<T>, GetComponentLookup<T> | Static access from systems |
EntityQuery | ToEntityArray, ToComponentDataArray<T>, CalculateEntityCount | Bulk operations |
IComponentData | (marker interface) | Unmanaged struct components |
IBufferElementData | (marker interface) | Dynamic buffer elements |
DynamicBuffer<T> | Add, RemoveAt, Length, Clear, AsNativeArray | Variable-length per-entity arrays |
IJobEntity | Execute(ref T1, in T2, ...) | Auto-generated query from params |
IJobChunk | Execute(in ArchetypeChunk, ...) | Manual chunk iteration |
World | DefaultGameObjectInjectionWorld, EntityManager | Container for systems + entities |
RefRW<T> / RefRO<T> | ValueRW / ValueRO | Read-write / read-only component refs |