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.

Quality 100% Code blocks 9 Sections 7 Lines 250 RU translation yes
view source
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 together

Best 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);
}