gamedev
unityecsdotsjobsburstgame-devperformancecsharp
unity-ecs
Build scalable Unity games with Entity Component System (ECS): systems, queries, jobs, burst compilation, and DOTS patterns. Use for high-performance Unity.
Models: sonnetopusgpt-6gemini-3glm-5
Unity ECS
Building high-performance Unity games with Entity Component System and DOTS.
Quick Start
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
// Define a component
public struct Health : IComponentData
{
public float Current;
public float Max;
}
// Define a system
public partial class DamageSystem : SystemBase
{
protected override void OnUpdate()
{
float dt = SystemAPI.Time.DeltaTime;
foreach (var (health, transform) in
SystemAPI.Query<RefRW<Health>, RefRO<LocalTransform>>())
{
if (health.ValueRO.Current <= 0)
continue;
// Example: regenerate health over time
health.ValueRW.Current = math.min(
health.ValueRO.Current + dt * 5f,
health.ValueRO.Max
);
}
}
}When to Use
- Building games with thousands of similar entities (bullets, particles, NPCs)
- When MonoBehaviour performance is not enough
- Need for data-oriented design and cache-friendly code
- Large-scale simulations or strategy games
Step-by-Step
1. Design Components (Data)
Keep components small and focused:
public struct MovementSpeed : IComponentData { public float Value; }
public struct TargetPosition : IComponentData { public float3 Value; }
public struct DamageOnContact : IComponentData { public float Amount; }2. Create Systems (Logic)
Systems operate on entities with specific component combinations:
public partial class MovementSystem : SystemBase
{
protected override void OnUpdate()
{
float dt = SystemAPI.Time.DeltaTime;
foreach (var (transform, speed) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<MovementSpeed>>())
{
var pos = transform.ValueRO.Position;
pos.z += speed.ValueRO.Value * dt;
transform.ValueRW.Position = pos;
}
}
}3. Use Jobs for Parallelism
[BurstCompile]
public partial struct CalculateDamageJob : IJobEntity
{
public float DeltaTime;
void Execute(ref Health health, in DamageOverTime dot)
{
health.Current -= dot.DamagePerSecond * DeltaTime;
health.Current = math.max(0, health.Current);
}
}
public partial class DamageOverTimeSystem : SystemBase
{
protected override void OnUpdate()
{
var job = new CalculateDamageJob
{
DeltaTime = SystemAPI.Time.DeltaTime
};
job.ScheduleParallel();
}
}4. Entity Queries
partial class TargetingSystem : SystemBase
{
private EntityQuery _enemyQuery;
protected override void OnCreate()
{
_enemyQuery = new EntityQueryBuilder(Allocator.Temp)
.WithAll<Health, EnemyTag>()
.WithNone<DeadTag>()
.Build(this);
}
protected override void OnUpdate()
{
var enemies = _enemyQuery.ToEntityArray(Allocator.Temp);
// Process enemies...
}
}5. Shared Components (Rendering)
public struct SpriteRenderer : ISharedComponentData
{
public Texture2D Texture;
public Material Material;
}
// Entities with same shared component are grouped togetherBest Practices
- **Keep components small** — single responsibility
- **Avoid managed references** in components (use Entity references)
- **Use Burst** for math-heavy jobs
- **Batch entities** by shared components for rendering
- **Profile with Unity Profiler** — measure before optimizing
Common Pitfalls
- Forgetting to mark jobs with `[BurstCompile]`
- Using `foreach` instead of `IJobEntity` for large datasets
- Creating too many systems — group related logic
- Not disposing NativeArrays and other native containers
Examples
Spawning Entities
public partial class SpawnerSystem : SystemBase
{
protected override void OnUpdate()
{
var ecb = new EntityCommandBuffer(Allocator.TempJob);
foreach (var (spawner, transform) in
SystemAPI.Query<RefRO<Spawner>, RefRO<LocalTransform>>())
{
if (spawner.ValueRO.CanSpawn)
{
var entity = ecb.Instantiate(spawner.ValueRO.Prefab);
ecb.SetComponent(entity, new LocalTransform
{
Position = transform.ValueRO.Position
});
}
}
ecb.Playback(SystemAPI.GetSingleton<EntityManager>());
ecb.Dispose();
}
}Health and Death
public partial class DeathSystem : SystemBase
{
protected override void OnUpdate()
{
var ecb = new EntityCommandBuffer(Allocator.TempJob);
foreach (var (health, entity) in
SystemAPI.Query<RefRO<Health>>().WithEntityAccess())
{
if (health.ValueRO.Current <= 0)
{
ecb.AddComponent<DeadTag>(entity);
ecb.RemoveComponent<Health>(entity);
}
}
ecb.Playback(SystemAPI.GetSingleton<EntityManager>());
ecb.Dispose();
}
}Validation
[Test]
public void MovementSystem_UpdatesPosition()
{
using var world = new World("Test");
var system = world.GetOrCreateSystemManaged<MovementSystem>();
var entity = world.EntityManager.CreateEntity();
world.EntityManager.AddComponentData(entity, new LocalTransform
{
Position = new float3(0, 0, 0)
});
world.EntityManager.AddComponentData(entity, new MovementSpeed { Value = 10f });
system.Update();
var transform = world.EntityManager.GetComponentData<LocalTransform>(entity);
Assert.Greater(transform.Position.z, 0);
}