Back to Gaming

Unity Game Engine Series Part 10: Data-Oriented Tech Stack (DOTS)

March 31, 2026 Wasil Zafar 42 min read

Embrace the paradigm shift from object-oriented to data-oriented design. Master the Entity Component System for cache-friendly data layout, the C# Jobs System for safe multithreading, and the Burst Compiler for native-speed performance — enabling you to simulate 100,000+ entities at a rock-solid 60fps.

Table of Contents

  1. Why DOTS?
  2. Entity Component System
  3. Jobs System
  4. Burst Compiler
  5. Migration from MonoBehaviour
  6. Real-World Performance
  7. History & Evolution
  8. Exercises & Self-Assessment
  9. DOTS Architecture Doc Generator
  10. Conclusion & Next Steps

Introduction: The Performance Revolution

Series Overview: This is Part 10 of our 16-part Unity Game Engine Series. We tackle Unity's most significant architectural evolution — DOTS — which fundamentally changes how games process data, leverage modern CPUs, and achieve performance levels impossible with traditional MonoBehaviour approaches.

DOTS (Data-Oriented Technology Stack) is Unity's answer to a fundamental problem: modern CPUs have gotten dramatically faster at processing sequential data in memory, but traditional object-oriented game code scatters data across the heap, causing constant cache misses that waste 90%+ of the CPU's potential throughput.

DOTS consists of three pillars: the Entity Component System (ECS) for cache-friendly data layout, the C# Jobs System for safe multithreading across all CPU cores, and the Burst Compiler for compiling C# to highly optimized native code with SIMD vectorization. Together, they enable performance improvements of 10-100x over equivalent MonoBehaviour code.

Key Insight: DOTS is not a replacement for MonoBehaviour in all cases. It's a specialized tool for performance-critical systems: massive entity counts (crowds, particles, bullets), simulation-heavy games (city builders, RTS), and any scenario where you're CPU-bound. Most games use a hybrid approach — DOTS for performance-critical systems, MonoBehaviour for gameplay logic, UI, and game flow.

1. Why DOTS?

1.1 OOP Limitations for Games

Traditional Unity code using MonoBehaviour and GameObjects has inherent performance limitations rooted in how object-oriented programming interacts with modern hardware:

Problem OOP (MonoBehaviour) DOTS (ECS)
Cache Misses Objects scattered across heap. Iterating 10,000 enemies = 10,000 cache misses (each object at random memory address) Components stored contiguously in memory chunks. Iterating 10,000 entities = sequential memory access, 0 cache misses
GC Pressure Every MonoBehaviour is a managed object on the GC heap. Creating/destroying causes garbage collection stalls (10-50ms spikes) Entities and components use unmanaged memory. No GC involvement. Smooth, predictable frame times
Single-Threaded Update() runs on main thread only. 8-core CPU? You're using 1 core for gameplay logic Jobs System distributes work across ALL CPU cores automatically. 8-core CPU = 8x throughput potential
Virtual Calls MonoBehaviour methods use virtual dispatch — CPU can't predict branch targets, pipeline stalls Systems process data directly — no virtual calls, CPU pipeline stays full
Data Layout AoS (Array of Structs): each object contains ALL its data together, even if you only need position SoA (Struct of Arrays): components of the same type packed together. Read only what you need

1.2 The Data-Oriented Paradigm

Analogy: Imagine you're a librarian who needs to check the publication year of 10,000 books. In the OOP approach, each book is in a different room of a massive building — you walk to room 1, open the book, note the year, walk to room 2, and so on. Each trip takes 100 steps. In the DOTS approach, all the publication years are written on a single continuous scroll — you stand in one place and read them sequentially at the speed your eyes can move. Same data, same result, but 1,000x faster access.

The Numbers: A modern CPU can access L1 cache in ~1ns (nanosecond), L2 in ~3ns, L3 in ~10ns, but main RAM in ~100ns. That's a 100x penalty for cache misses. When you iterate over MonoBehaviours scattered across the heap, nearly every access is a cache miss. ECS organizes data so that sequential iteration stays in L1/L2 cache — making the CPU 10-100x more efficient for the same operations.

2. Entity Component System (ECS)

2.1 Entities & Components

In ECS, the three concepts have precise, distinct roles:

Concept What It Is OOP Equivalent Key Properties
Entity A unique ID (integer) — nothing more GameObject (but with zero overhead) No data, no behavior. Just an identifier that links components
Component A pure data struct (IComponentData) MonoBehaviour fields (data only, no methods) Blittable structs, no references, no methods, unmanaged memory
System Behavior processor — reads/writes components MonoBehaviour Update() methods Processes all entities matching a query, runs on main thread or scheduled as Jobs
// ECS Components — pure data structs, no behavior
using Unity.Entities;
using Unity.Mathematics;

// Position data for every entity in the world
public struct Position : IComponentData
{
    public float3 Value;
}

// Velocity for moving entities
public struct Velocity : IComponentData
{
    public float3 Value;
}

// Health for damageable entities
public struct Health : IComponentData
{
    public float Current;
    public float Max;
}

// Tag component — zero-size, used for filtering
// "This entity is an enemy" — no data needed, just the tag
public struct EnemyTag : IComponentData { }

// Shared component — entities with same value share memory
// Good for faction/team grouping
public struct FactionShared : ISharedComponentData
{
    public int FactionId;
}

// Buffer element — variable-length data per entity
// Like a List<T> but ECS-compatible
[InternalBufferCapacity(8)]
public struct DamageBufferElement : IBufferElementData
{
    public float Amount;
    public Entity Source;
}

2.2 Systems & Entity Queries

// ECS System — processes all entities with matching components
using Unity.Entities;
using Unity.Mathematics;
using Unity.Burst;
using Unity.Transforms;

// ISystem (unmanaged, Burst-compatible, preferred)
[BurstCompile]
public partial struct MovementSystem : ISystem
{
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        // Require these components to exist before system runs
        state.RequireForUpdate<Position>();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        float deltaTime = SystemAPI.Time.DeltaTime;

        // Process every entity that has both Position and Velocity
        // This is automatically parallelized across CPU cores!
        foreach (var (transform, velocity) in
            SystemAPI.Query<RefRW<LocalTransform>, RefRO<Velocity>>())
        {
            transform.ValueRW.Position += velocity.ValueRO.Value * deltaTime;
        }
    }
}

// System that processes damage buffers
[BurstCompile]
public partial struct DamageProcessingSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        // EntityCommandBuffer for deferred structural changes
        var ecb = new EntityCommandBuffer(Unity.Collections.Allocator.Temp);

        foreach (var (health, damageBuffer, entity) in
            SystemAPI.Query<RefRW<Health>, DynamicBuffer<DamageBufferElement>>()
                .WithEntityAccess())
        {
            // Sum all damage this frame
            float totalDamage = 0;
            foreach (var dmg in damageBuffer)
            {
                totalDamage += dmg.Amount;
            }

            // Apply damage
            health.ValueRW.Current -= totalDamage;

            // Clear the buffer for next frame
            damageBuffer.Clear();

            // Destroy entity if dead (deferred — safe during iteration)
            if (health.ValueRO.Current <= 0)
            {
                ecb.DestroyEntity(entity);
            }
        }

        ecb.Playback(state.EntityManager);
        ecb.Dispose();
    }
}

2.3 Archetypes & Chunks — The Memory Model

Understanding archetypes and chunks is key to understanding why ECS is fast:

Concept Description Analogy
Archetype A unique combination of component types. All entities with [Position, Velocity, Health] share one archetype A filing cabinet label: "Employees with Name + Salary + Department"
Chunk A 16KB block of contiguous memory holding entities of the same archetype One drawer in the filing cabinet, holding as many employee records as fit
Structural Change Adding/removing components changes an entity's archetype, requiring it to move to a different chunk Moving an employee's file from one cabinet to another when they change departments
Performance Warning: Structural changes (adding/removing components, creating/destroying entities) are expensive because they move data between chunks. Avoid them during hot loops. Instead, use EntityCommandBuffer to batch structural changes and apply them all at once at a sync point. Use enable/disable components (IEnableableComponent) instead of add/remove when toggling behavior.

3. Jobs System

3.1 Job Types & Scheduling

The C# Jobs System enables safe multithreading without the typical dangers of shared memory concurrency (race conditions, deadlocks). Unity's job safety system validates at compile time that no two jobs access the same data simultaneously.

Job Type Parallelism Use Case
IJob Single worker thread One task that must run off main thread (file I/O, complex calculation)
IJobParallelFor Work split across all cores Process large arrays — each element independently (positions, velocities)
IJobEntity Parallel across entities ECS integration — process matching entities across all chunks and cores
IJobChunk Parallel across chunks Low-level chunk access — maximum control over iteration
// IJobParallelFor — process a large array across all CPU cores
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Burst;
using UnityEngine;

public class ParticleSimulation : MonoBehaviour
{
    private NativeArray<float3> positions;
    private NativeArray<float3> velocities;
    private const int PARTICLE_COUNT = 100000;

    private void Start()
    {
        positions = new NativeArray<float3>(PARTICLE_COUNT, Allocator.Persistent);
        velocities = new NativeArray<float3>(PARTICLE_COUNT, Allocator.Persistent);

        // Initialize particles
        var random = new Unity.Mathematics.Random(42);
        for (int i = 0; i < PARTICLE_COUNT; i++)
        {
            positions[i] = random.NextFloat3(-50, 50);
            velocities[i] = random.NextFloat3Direction() * random.NextFloat(1, 5);
        }
    }

    private void Update()
    {
        // Create the job
        var moveJob = new MoveParticlesJob
        {
            Positions = positions,
            Velocities = velocities,
            DeltaTime = Time.deltaTime,
            Bounds = 50f
        };

        // Schedule it — splits 100,000 items across all cores
        // innerLoopBatchCount of 256 = each thread processes 256 particles at a time
        JobHandle handle = moveJob.Schedule(PARTICLE_COUNT, 256);

        // Complete before we read results (or pass handle to dependent job)
        handle.Complete();
    }

    private void OnDestroy()
    {
        if (positions.IsCreated) positions.Dispose();
        if (velocities.IsCreated) velocities.Dispose();
    }
}

[BurstCompile]
struct MoveParticlesJob : IJobParallelFor
{
    public NativeArray<float3> Positions;
    [ReadOnly] public NativeArray<float3> Velocities;
    public float DeltaTime;
    public float Bounds;

    public void Execute(int index)
    {
        float3 pos = Positions[index];
        pos += Velocities[index] * DeltaTime;

        // Wrap around bounds
        pos = math.fmod(pos + Bounds, Bounds * 2) - Bounds;
        Positions[index] = pos;
    }
}
// IJobEntity — ECS + Jobs integration
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Burst;

[BurstCompile]
public partial struct EnemyMovementJob : IJobEntity
{
    public float DeltaTime;
    public float3 PlayerPosition;

    // Execute runs for each entity matching the query
    // Query is inferred from parameters: needs LocalTransform, Velocity, and EnemyTag
    void Execute(ref LocalTransform transform, in Velocity velocity, in EnemyTag tag)
    {
        // Move toward player
        float3 direction = math.normalize(PlayerPosition - transform.Position);
        transform.Position += direction * math.length(velocity.Value) * DeltaTime;
    }
}

// Schedule the job from a system
[BurstCompile]
public partial struct EnemyMovementSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        // Get player position (simplified — assumes single player entity)
        float3 playerPos = float3.zero;
        foreach (var (transform, tag) in
            SystemAPI.Query<RefRO<LocalTransform>, RefRO<PlayerTag>>())
        {
            playerPos = transform.ValueRO.Position;
        }

        var job = new EnemyMovementJob
        {
            DeltaTime = SystemAPI.Time.DeltaTime,
            PlayerPosition = playerPos
        };

        // ScheduleParallel distributes entities across all CPU cores
        job.ScheduleParallel();
    }
}

public struct PlayerTag : IComponentData { }

3.2 NativeContainer Types

NativeContainers are the thread-safe, unmanaged-memory collections that Jobs and ECS use instead of managed C# collections:

Container C# Equivalent Thread Safety Use Case
NativeArray<T> T[] ReadOnly parallel, ReadWrite single Fixed-size data: positions, velocities, results
NativeList<T> List<T> ReadOnly parallel, ReadWrite single Dynamic-size data: collected results, filtered entities
NativeHashMap<K,V> Dictionary<K,V> ParallelWriter for parallel add Spatial hashing, entity lookup tables, caching
NativeQueue<T> Queue<T> ParallelWriter for parallel enqueue Event queues, work items, producer-consumer patterns
NativeMultiHashMap<K,V> Dictionary<K, List<V>> ParallelWriter for parallel add Spatial partitioning, grouping entities by cell/region
Allocator Choices: Every NativeContainer requires an Allocator: Temp (one frame, auto-disposed), TempJob (4 frames, must dispose — use for jobs that complete same frame), Persistent (lives forever, must manually dispose — use for long-lived data). Using the wrong allocator causes memory leaks (Persistent not disposed) or use-after-free crashes (Temp used across frames).

4. Burst Compiler

4.1 Burst Basics & Benchmarks

The Burst Compiler translates C# Job code into highly optimized native machine code using LLVM. It's the "secret sauce" that makes DOTS performance possible — without Burst, the Jobs System is merely multithreaded. With Burst, it's multithreaded and running at C/C++ speeds.

Operation Managed C# Burst-Compiled Speedup
Vector3 addition (1M ops) ~8ms ~0.08ms 100x
Matrix multiplication (100K) ~12ms ~0.5ms 24x
Array sum (10M floats) ~15ms ~0.3ms 50x
Particle simulation (100K) ~45ms (single-thread) ~0.6ms (Burst + parallel) 75x
Pathfinding (A*, 10K agents) ~200ms ~5ms (Burst + parallel) 40x
// Burst compilation — just add the attribute!
using Unity.Burst;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;

[BurstCompile(CompileSynchronously = true)] // Compile immediately, not lazy
struct GravitySimulationJob : IJobParallelFor
{
    [ReadOnly] public NativeArray<float3> Positions;
    [ReadOnly] public NativeArray<float> Masses;
    public NativeArray<float3> Forces;
    public float GravitationalConstant;

    public void Execute(int i)
    {
        float3 totalForce = float3.zero;
        float3 posI = Positions[i];
        float massI = Masses[i];

        for (int j = 0; j < Positions.Length; j++)
        {
            if (i == j) continue;

            float3 direction = Positions[j] - posI;
            float distSq = math.max(math.lengthsq(direction), 0.01f);
            float forceMagnitude = GravitationalConstant * massI * Masses[j] / distSq;

            totalForce += math.normalize(direction) * forceMagnitude;
        }

        Forces[i] = totalForce;
    }
}

// Burst restrictions:
// - No managed types (string, class, delegates)
// - No try/catch (exception handling is managed)
// - No virtual method calls
// - No LINQ
// - Must use Unity.Mathematics instead of UnityEngine.Mathf
// In return: 10-100x performance

4.2 Mathematics Library & SIMD

Burst uses the Unity.Mathematics library instead of UnityEngine.Mathf. This library is designed for SIMD (Single Instruction, Multiple Data) vectorization — processing 4 or 8 floats in a single CPU instruction:

UnityEngine Unity.Mathematics Why
Vector3 float3 Value type, SIMD-friendly, no heap allocation
Quaternion quaternion Same math, Burst-compatible
Matrix4x4 float4x4 SIMD matrix operations, column-major
Mathf.Sin() math.sin() Burst intrinsic — compiles to hardware sin instruction
Random.Range() Random.NextFloat() Deterministic, thread-safe, seedable
// SIMD vectorization example with Burst
using Unity.Burst;
using Unity.Mathematics;

[BurstCompile]
public static class MathHelpers
{
    // Burst will auto-vectorize this to process 4 floats at once (SSE)
    // or 8 floats at once (AVX2) depending on CPU
    [BurstCompile]
    public static void NormalizeArray(ref NativeArray<float3> vectors, int count)
    {
        for (int i = 0; i < count; i++)
        {
            // math.normalize compiles to SIMD rsqrt + multiply
            vectors[i] = math.normalize(vectors[i]);
        }
        // On AVX2: processes 8 components per cycle instead of 1
        // Result: ~8x speedup from vectorization alone, on top of Burst's other optimizations
    }
}

5. Migration from MonoBehaviour to DOTS

5.1 Hybrid Approach & SubScenes

You don't have to rewrite your entire game in ECS. The hybrid approach lets MonoBehaviour and ECS coexist in the same project:

System Type Use MonoBehaviour Use ECS
Game Flow Menu navigation, game states, cutscenes
UI Canvas, UI Toolkit, menus
Player Controller Input handling, camera control (1 entity)
Enemies/NPCs Movement, AI, pathfinding for 1000+ entities
Projectiles Bullets, particles, effects (high entity count)
World Simulation Terrain, vegetation, physics, weather systems

SubScenes are the bridge between the two worlds. A SubScene converts GameObjects (with authoring components) into ECS entities at build time or load time:

// SubScene workflow: Author in GameObjects, run in ECS
// 1. Create a SubScene (right-click in Hierarchy > New Sub Scene)
// 2. Place GameObjects inside the SubScene
// 3. Attach Baker components that convert to ECS at bake time

// Step 1: Define your ECS component
public struct EnemyData : IComponentData
{
    public float Speed;
    public float DetectionRange;
    public int FactionId;
}

// Step 2: Create an authoring component (MonoBehaviour, for editor)
public class EnemyAuthoring : MonoBehaviour
{
    public float Speed = 5f;
    public float DetectionRange = 20f;
    public int FactionId = 1;
}

// Step 3: Create a Baker that converts authoring to ECS
public class EnemyBaker : Baker<EnemyAuthoring>
{
    public override void Bake(EnemyAuthoring authoring)
    {
        var entity = GetEntity(TransformUsageFlags.Dynamic);

        AddComponent(entity, new EnemyData
        {
            Speed = authoring.Speed,
            DetectionRange = authoring.DetectionRange,
            FactionId = authoring.FactionId
        });

        AddComponent(entity, new EnemyTag());
    }
}

// Now: Place EnemyAuthoring on GameObjects in a SubScene.
// At bake time, Unity converts them to ECS entities with EnemyData + EnemyTag.
// Systems process them with full DOTS performance.

5.2 Baking: Authoring to Runtime

The Baking Pipeline: Baking is the process of converting "authoring" GameObjects (what you see in the editor) into "runtime" ECS entities (what runs in the game). This happens automatically when you open/build a SubScene. The Baker class is your converter — it reads MonoBehaviour data and creates IComponentData equivalents. This separation means you can use Unity's familiar editor workflow while getting ECS performance at runtime.

6. Real-World Performance

6.1 Handling 100K+ Entities

DOTS enables entity counts that are impossible with MonoBehaviour. Here's a practical comparison:

Entity Count MonoBehaviour (Update) DOTS (ECS + Jobs + Burst)
1,000 ~2ms (fine for most games) ~0.02ms
10,000 ~20ms (struggling at 60fps) ~0.2ms
50,000 ~100ms (unplayable) ~1.0ms
100,000 ~200ms (slideshow) ~2.0ms (smooth 60fps)
500,000 Not feasible ~10ms (playable at 60fps)

6.2 Spatial Partitioning & LOD with DOTS

// Spatial hashing for efficient neighbor queries in DOTS
using Unity.Entities;
using Unity.Mathematics;
using Unity.Collections;
using Unity.Burst;

[BurstCompile]
public partial struct SpatialHashingSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        int entityCount = SystemAPI.QueryBuilder()
            .WithAll<LocalTransform, EnemyTag>().Build().CalculateEntityCount();

        // Create spatial hash map: cell -> entities in that cell
        var spatialMap = new NativeMultiHashMap<int2, Entity>(
            entityCount, Allocator.TempJob);

        float cellSize = 10f; // 10 unit grid cells

        // Phase 1: Build spatial hash (parallel)
        var buildJob = new BuildSpatialHashJob
        {
            SpatialMap = spatialMap.AsParallelWriter(),
            CellSize = cellSize
        };
        buildJob.ScheduleParallel(state.Dependency).Complete();

        // Phase 2: Query neighbors for each entity
        // Example: find all enemies within 15 units of the player
        // Hash the player position, check surrounding cells (3x3 grid)
        // This turns O(n^2) distance checks into O(n) lookups!

        spatialMap.Dispose();
    }
}

[BurstCompile]
partial struct BuildSpatialHashJob : IJobEntity
{
    public NativeMultiHashMap<int2, Entity>.ParallelWriter SpatialMap;
    public float CellSize;

    void Execute(Entity entity, in LocalTransform transform, in EnemyTag tag)
    {
        int2 cell = new int2(
            (int)math.floor(transform.Position.x / CellSize),
            (int)math.floor(transform.Position.z / CellSize)
        );
        SpatialMap.Add(cell, entity);
    }
}
Case Study

Megacity Demo — 4.5 Million Mesh Renderers

Unity's Megacity demo is the flagship DOTS showcase. It renders a sprawling futuristic cityscape with:

  • 4.5 million mesh renderers — buildings, vehicles, infrastructure, all as ECS entities
  • 200,000 dynamic objects — flying cars with streaming audio, lights, and navigation
  • 100,000 audio sources — spatialized ambient sound from each vehicle and environment element
  • Running at 60fps on mid-range hardware using ECS + Jobs + Burst + GPU instancing

Without DOTS, this scene would be impossible — MonoBehaviour would run at <1fps with this entity count. Megacity demonstrates that DOTS isn't just an optimization; it enables entirely new categories of games.

4.5M Renderers 200K Dynamic 60fps Open World
Case Study

Latios Framework — Community-Driven DOTS Ecosystem

The Latios Framework is an open-source collection of DOTS packages that extends Unity's ECS with production-ready features the core package doesn't provide:

  • Kinemation — skeletal animation system for ECS (thousands of animated characters)
  • Psyshock — DOTS-native spatial query and collision detection system
  • Myri — ECS audio system supporting thousands of simultaneous sources
  • Demonstrates that the DOTS community is actively building the ecosystem needed for production use
Open Source Community Production-Ready Animation + Audio

7. History: DOTS Preview to ECS 1.0

Year Milestone Impact
2018 DOTS announced at GDC, ECS preview packages Paradigm shift announcement. Early adopters experiment with unstable APIs. "Pure ECS" vs "Hybrid ECS" confusion
2019 Burst Compiler 1.0 stable, Jobs System stable First production-ready DOTS component. Jobs + Burst usable without full ECS commitment
2020-2021 Major ECS API rewrites, SubScene workflow API instability frustrates early adopters. Baking pipeline replaces conversion workflow. Community patience tested
2022 Entities 1.0 release candidate ISystem (unmanaged) replaces SystemBase as preferred approach. API stabilizes significantly
2023 ECS 1.0 stable with Unity 2022 LTS First production-stable ECS release. Teams begin adopting for shipped titles. Megacity multiplayer demo
2024-2026 Unity 6, expanded DOTS ecosystem DOTS Physics, DOTS Animation improvements, NetCode for Entities, growing asset store support. DOTS moves from experimental to standard practice for performance-critical systems

Exercises & Self-Assessment

Exercise 1

ECS Fundamentals Lab

Build a basic ECS simulation from scratch:

  1. Create a new Unity project and install the Entities package (com.unity.entities)
  2. Define components: Position, Velocity, LifeTime (float, decrements each frame)
  3. Create a spawner system that creates 10,000 entities with random positions and velocities
  4. Create a MovementSystem that updates positions based on velocities
  5. Create a LifeTimeSystem that destroys entities when their lifetime reaches zero (use EntityCommandBuffer)
  6. Profile: how many ms does the update take for 10K, 50K, and 100K entities?
Exercise 2

Jobs + Burst Performance Comparison

Measure the real impact of Jobs and Burst:

  1. Create an array of 1,000,000 float3 values representing particle positions
  2. Implement a gravity simulation that updates all positions each frame
  3. Version 1: Regular C# for loop on main thread — measure time
  4. Version 2: IJobParallelFor without Burst — measure time
  5. Version 3: IJobParallelFor with [BurstCompile] — measure time
  6. Create a comparison table showing the speedup at each stage
Exercise 3

Hybrid Migration Exercise

Practice migrating a MonoBehaviour system to ECS:

  1. Start with a MonoBehaviour "BoidSimulation" — 500 boids with alignment, cohesion, and separation
  2. Profile the MonoBehaviour version — note frame time
  3. Migrate to ECS: create BoidData component, BoidSystem, and BoidAuthoring + Baker
  4. Use a SubScene for the boid entities, keep the camera controller as a MonoBehaviour
  5. Scale up to 10,000 boids — compare frame times between the two versions
Exercise 4

Reflective Questions

  1. Explain why cache locality matters more than raw computational speed for modern game performance. What hardware trend makes this increasingly important?
  2. You have a city builder game with 50,000 citizens. Each citizen has health, hunger, job, home, and path data. Design the ECS component layout — what are the archetypes, and why would you split data into separate components vs combining them?
  3. What happens if two parallel jobs try to write to the same NativeArray element? How does Unity's job safety system prevent this, and what's the performance implication?
  4. Burst cannot compile code that uses managed types (strings, classes, delegates). Explain why this restriction exists and how it enables the performance gains Burst provides.
  5. A game uses MonoBehaviour for the player, UI, and game flow, but ECS for 20,000 enemies and 50,000 projectiles. How would you communicate between the two worlds (e.g., player takes damage from an ECS projectile)?

DOTS Architecture Document Generator

Generate a professional DOTS architecture document for your ECS systems. Download as Word, Excel, PDF, or PowerPoint.

Draft auto-saved

All data stays in your browser. Nothing is sent to or stored on any server.

Conclusion & Next Steps

You now understand Unity's most powerful performance framework. Here are the key takeaways from Part 10:

  • DOTS solves fundamental hardware problems — cache locality, multithreading, and native code compilation. These aren't theoretical benefits; they deliver 10-100x real-world speedups
  • ECS separates data from behavior — entities are IDs, components are pure data structs, systems process matching entities. This layout maximizes cache coherence
  • The Jobs System provides safe multithreading — schedule work across all CPU cores without race conditions or deadlocks. The safety system catches errors at compile time
  • Burst Compiler is the performance multiplier — it compiles C# to native code with SIMD vectorization. Always add [BurstCompile] to Jobs and Systems
  • Use NativeContainers (NativeArray, NativeList, NativeHashMap) instead of managed collections for zero-GC, thread-safe data access
  • Hybrid approach is practical — use MonoBehaviour for player, UI, and game flow. Use DOTS for performance-critical mass-entity systems
  • Baking (SubScenes) bridges editor authoring and runtime performance — design with GameObjects, run with entities

Next in the Series

In Part 11: AI & Gameplay Systems, we build intelligent game agents using NavMesh pathfinding, finite state machines, behavior trees, and procedural generation — the gameplay systems that make your game world feel alive and responsive.

Gaming