---
title: Build a Scalable Entity‑Component System in Unity using C#: A Complete Walkthrough
siteUrl: https://logzly.com/gamedevcodecraft
author: gamedevcodecraft (GameDev Codecraft)
date: 2026-06-25T02:05:57.294993
tags: [gamedev, unity, ecs]
url: https://logzly.com/gamedevcodecraft/build-a-scalable-entitycomponent-system-in-unity-using-c-a-complete-walkthrough
---


Ever felt your Unity project getting messy because you keep adding new scripts to GameObjects? It’s a common pain, especially when you’re trying to keep things tidy and fast. At GameDev Codecraft we’ve tried a lot of tricks, and one that really helped us is an Entity‑Component System (ECS). In this post I’ll walk you through a simple, scalable ECS you can drop into any Unity project. No fancy packages, just plain C# and a bit of good habits.

## Why an ECS?

Unity already uses a component model, but the built‑in one can become a tangled web when you start mixing logic, data, and Unity‑specific calls in the same script. An ECS separates **data** (the components) from **behaviour** (the systems). This makes it easier to:

* Add new features without touching old code  
* Run many objects with the same logic efficiently  
* Keep the project organized – a big win for anyone reading the code later

That’s why I keep talking about it on GameDev Codecraft. Let’s get into the steps.

## The Core Idea in Plain English

Think of an **Entity** as just an ID – a number that says “this is object #42”.  
A **Component** is a tiny bag of data, like `Position`, `Velocity`, or `Health`.  
A **System** looks at all entities that have the components it cares about and does something, like move them or apply damage.

No Unity `MonoBehaviour` is needed for the components themselves. The only `MonoBehaviour` we keep is a tiny manager that runs the systems each frame.

## Step 1: Set Up the Basic Types

Create a new folder called `ECS` in your project. Inside, add three C# files: `Entity.cs`, `IComponent.cs`, and `ComponentPool.cs`.

### Entity.cs

```csharp
using System;

namespace GameDevCodecraft.ECS
{
    public struct Entity
    {
        public int Id;
        public int Version;

        public bool IsValid => Id >= 0 && Version >= 0;
    }
}
```

We store an `Id` and a `Version` so we can detect stale references later. Simple enough for GameDev Codecraft readers.

### IComponent.cs

```csharp
namespace GameDev Codecraft.ECS
{
    public interface IComponent { }
}
```

All components will implement this empty interface – it’s just a marker.

### ComponentPool.cs

```csharp
using System.Collections.Generic;

namespace GameDev Codecraft.ECS
{
    public class ComponentPool<T> where T : IComponent, new()
    {
        private readonly Dictionary<int, T> _components = new();

        public void Add(int entityId, T component)
        {
            _components[entityId] = component;
        }

        public bool TryGet(int entityId, out T component)
        {
            return _components.TryGetValue(entityId, out component);
        }

        public void Remove(int entityId)
        {
            _components.Remove(entityId);
        }

        public IEnumerable<KeyValuePair<int, T>> All()
        {
            return _components;
        }
    }
}
```

The pool stores components by the entity’s Id. This keeps look‑ups fast.

## Step 2: Build a Simple World Manager

Create `World.cs`. This class creates entities, holds component pools, and runs systems.

```csharp
using System.Collections.Generic;
using UnityEngine;

namespace GameDev Codecraft.ECS
{
    public class World : MonoBehaviour
    {
        private int _nextEntityId = 0;
        private readonly List<ISystem> _systems = new();

        // Pools for each component type
        private readonly ComponentPool<Position> _positionPool = new();
        private readonly ComponentPool<Velocity> _velocityPool = new();

        public Entity CreateEntity()
        {
            var entity = new Entity { Id = _nextEntityId++, Version = 0 };
            return entity;
        }

        public void AddComponent<T>(Entity e, T component) where T : IComponent, new()
        {
            if (typeof(T) == typeof(Position))
                _positionPool.Add(e.Id, component as Position);
            else if (typeof(T) == typeof(Velocity))
                _velocityPool.Add(e.Id, component as Velocity);
            // Add more else‑ifs for new component types
        }

        public bool TryGetComponent<T>(Entity e, out T component) where T : IComponent, new()
        {
            if (typeof(T) == typeof(Position))
                return _positionPool.TryGet(e.Id, out component);
            if (typeof(T) == typeof(Velocity))
                return _velocityPool.TryGet(e.Id, out component);
            component = default;
            return false;
        }

        public void RegisterSystem(ISystem system)
        {
            _systems.Add(system);
        }

        private void Update()
        {
            foreach (var system in _systems)
                system.Update(this);
        }

        // Expose pools for systems that need them
        public ComponentPool<Position> PositionPool => _positionPool;
        public ComponentPool<Velocity> VelocityPool => _velocityPool;
    }
}
```

A few notes for GameDev Codecraft fans:

* The `World` itself is a `MonoBehaviour` so Unity can call `Update`.  
* Right now we only have two component pools. Adding more is just a copy‑paste of the `else if` block.  
* Systems are stored in a list and called each frame – that’s the heart of the ECS.

## Step 3: Define Some Basic Components

In the same folder, add `Position.cs` and `Velocity.cs`.

```csharp
namespace GameDev Codecraft.ECS
{
    public struct Position : IComponent
    {
        public float X;
        public float Y;
        public float Z;
    }
}
```

```csharp
namespace GameDev Codecraft.ECS
{
    public struct Velocity : IComponent
    {
        public float X;
        public float Y;
        public float Z;
    }
}
```

Structs keep memory usage low, which is why many ECS designs use them. No Unity `Transform` here – we’ll sync later.

## Step 4: Write a Simple System

Create `MovementSystem.cs`.

```csharp
using UnityEngine;

namespace GameDev Codecraft.ECS
{
    public class MovementSystem : ISystem
    {
        public void Update(World world)
        {
            foreach (var kvp in world.VelocityPool.All())
            {
                int entityId = kvp.Key;
                Velocity vel = kvp.Value;

                if (world.PositionPool.TryGet(entityId, out Position pos))
                {
                    pos.X += vel.X * Time.deltaTime;
                    pos.Y += vel.Y * Time.deltaTime;
                    pos.Z += vel.Z * Time.deltaTime;

                    world.PositionPool.Add(entityId, pos);
                }
            }
        }
    }
}
```

The system loops over every entity that has a `Velocity`. If that entity also has a `Position`, we move it. The math uses `Time.deltaTime` so movement stays smooth no matter the frame rate.

## Step 5: Hook Everything Up in a Scene

1. Open a new Unity scene.  
2. Create an empty GameObject called **ECSWorld**.  
3. Add the `World` component (the script we wrote) to it.  

Now we need a tiny script to create a few test entities.

```csharp
using UnityEngine;

namespace GameDev Codecraft.ECS
{
    public class DemoSetup : MonoBehaviour
    {
        private void Start()
        {
            var world = FindObjectOfType<World>();

            // Register the movement system
            world.RegisterSystem(new MovementSystem());

            // Create 5 moving cubes
            for (int i = 0; i < 5; i++)
            {
                var e = world.CreateEntity();

                var pos = new Position { X = i * 2f, Y = 0f, Z = 0f };
                var vel = new Velocity { X = 0f, Y = 0f, Z = 1f + i * 0.5f };

                world.AddComponent(e, pos);
                world.AddComponent(e, vel);
            }
        }
    }
}
```

Add `DemoSetup` to any GameObject in the scene (I usually drop it on the same **ECSWorld** object). When you hit Play, you’ll see the positions change in the console (or you can add a simple visualizer later).

## Step 6: Keep It Scalable

Now that the basics work, you can grow the system without rewriting old code:

* **Add new components** – just create a struct that implements `IComponent` and add a pool in `World`.  
* **Add new systems** – implement `ISystem` and register it in `Start`.  
* **Separate rendering** – make a `RenderSystem` that reads `Position` and moves Unity `Transform`s. This keeps the ECS pure and lets you swap rendering pipelines later.

A quick tip from GameDev Codecraft: keep component structs tiny. If you need a big chunk of data, split it into several components. This makes it easier for systems to pick only what they need.

## Step 7: Debugging Made Easy

Because everything is just plain C#, you can use the normal Unity debugger. Set a breakpoint inside a system’s `Update` method and watch the component pools. If something feels slow, check the number of entities in each pool – that’s the main thing that can affect performance.

I once spent an afternoon hunting a bug where an entity’s `Version` got out of sync. The fix was simply to bump the version each time we removed an entity. A tiny detail, but it saved me a lot of head‑scratching. That’s the kind of story I love sharing on GameDev Codecraft.

## Wrap‑Up

Building a scalable ECS in Unity doesn’t have to be a mountain climb. With a few small files and a clear separation of data and logic, you get a system that stays clean as your game grows. At GameDev Codecraft we’ve used this pattern for everything from simple 2D shooters to larger 3D experiments, and it always feels like a breath of fresh air compared to a monolithic mess of `MonoBehaviour`s.

Give it a try in your next project. Start with the code above, add a couple of new components, and watch how easy it becomes to plug in fresh behaviour. If you run into a snag, remember the core ideas: entities are just IDs, components are tiny data bags, and systems do the work. Keep those three in mind and you’ll be on the right track.