A simple hybrid Entity Component System for Unity.
ChocolateECS is a lightweight ECS framework that bridges Unity's traditional MonoBehaviour workflow with Entity Component System patterns. It provides the benefits of ECS architecture—data-driven design, separation of concerns, and efficient component queries—while staying compatible with Unity's editor and existing workflows.
Hybrid approach: Components can be both IComponent and MonoBehaviour, letting you use the Unity Inspector while benefiting from ECS-style systems.
- Simple API — Just
IComponent,ECSSystem, andBootstrapper - Unity lifecycle integration — Systems hook into Awake, Start, Update, LateUpdate, FixedUpdate
- Component queries — Get all components of a type, or query dual-component pairs
- Automatic registration — Components auto-register when GameObjects are instantiated
- Reflection-optimized — Only calls lifecycle methods you actually override
- Factory pattern —
ECSFactoryfor runtime instantiation with proper ECS tracking - Singleton systems — Built-in
SingletonSystembase class
- Copy all
.csfiles to your Unity project'sAssets/folder (or a subfolder) - Create a bootstrapper and attach it to a GameObject in your scene
Components hold data. They implement IComponent and optionally inherit from MonoBehaviour for editor visibility:
using ChocolateECS;
using UnityEngine;
public class HealthComponent : MonoBehaviour, IComponent
{
public int currentHealth = 100;
public int maxHealth = 100;
}Systems contain logic. They extend ECSSystem and override lifecycle methods:
using ChocolateECS;
using System.Collections.Generic;
using UnityEngine;
public class HealthSystem : ECSSystem
{
public override void OnUpdate(float deltaTime)
{
var healthComponents = ComponentManager.GetComponents(typeof(HealthComponent));
for (int i = 0; i < healthComponents.Count; ++i)
{
var health = healthComponents[i] as HealthComponent;
if (health.currentHealth <= 0)
Debug.Log("Entity died!");
}
}
}The bootstrapper registers systems and attaches to a GameObject in your scene:
using ChocolateECS;
public class GameBootstrapper : Bootstrapper
{
public override void Awake()
{
// Register your systems here
RegisterSystem(new HealthSystem());
// Always call base.Awake() LAST
base.Awake();
}
}- Create an empty GameObject, name it "Bootstrapper"
- Attach your
GameBootstrapperscript - Add
HealthComponentto any GameObjects you want tracked - Press Play
Get all components of a specific type:
var enemies = ComponentManager.GetComponents(typeof(EnemyComponent));
for (int i = 0; i < enemies.Count; ++i)
{
var enemy = enemies[i] as EnemyComponent;
// Process enemy...
}Get components that exist on the same GameObject as another component type. Useful for querying entities that have multiple components:
// Get all HealthComponents that are on GameObjects that also have EnemyComponent
var enemyHealths = ComponentManager.GetDualComponents(
typeof(EnemyComponent), // Primary component type
typeof(HealthComponent) // Secondary component type to return
);
for (int i = 0; i < enemyHealths.Count; ++i)
{
var health = enemyHealths[i] as HealthComponent;
// This health belongs to an enemy...
}Use ECSFactory to instantiate GameObjects at runtime so their components are properly tracked:
using ChocolateECS;
using UnityEngine;
public class Spawner : MonoBehaviour
{
public GameObject enemyPrefab;
public void SpawnEnemy()
{
// Use ECSFactory instead of GameObject.Instantiate
GameObject enemy = ECSFactory.Instantiate(enemyPrefab);
}
public void DestroyEnemy(GameObject enemy)
{
// Use ECSFactory instead of GameObject.Destroy
ECSFactory.DestroyImmediate(enemy);
}
}Systems can override any of these methods (only overridden methods are called):
| Method | Unity Equivalent | Description |
|---|---|---|
OnAwake(List<ISystem> systems) |
Awake() |
Called once when bootstrapper awakens |
OnStart() |
Start() |
Called once before first Update |
OnEnable() |
OnEnable() |
Called when bootstrapper is enabled |
OnUpdate(float deltaTime) |
Update() |
Called every frame |
OnLateUpdate(float deltaTime) |
LateUpdate() |
Called after all Update calls |
OnFixedUpdate() |
FixedUpdate() |
Called at fixed time intervals |
OnDisable() |
OnDisable() |
Called when bootstrapper is disabled |
OnDestroy() |
OnDestroy() |
Called when bootstrapper is destroyed |
Marker interface for components. Implement on MonoBehaviours or plain classes.
public class MyComponent : MonoBehaviour, IComponent
{
public float speed;
}Base class for systems. Override lifecycle methods as needed.
public class MySystem : ECSSystem
{
public override void OnUpdate(float deltaTime) { }
}Alternative base class for systems that don't need multiple instances.
public class AudioSystem : SingletonSystem
{
public override void OnUpdate(float deltaTime) { }
}Accessed via ComponentManager property in systems.
| Method | Description |
|---|---|
GetComponents(Type type) |
Returns all components of the specified type |
GetDualComponents(Type main, Type secondary) |
Returns secondary components on GameObjects that also have main component |
CountComponents(Type type) |
Returns count of components of specified type |
Static factory for runtime GameObject management.
| Method | Description |
|---|---|
Instantiate(GameObject prefab) |
Instantiate and register components |
DestroyImmediate(GameObject obj) |
Unregister components and destroy |
Abstract MonoBehaviour that orchestrates the ECS. Extend and override Awake() to register systems.
| Method | Description |
|---|---|
RegisterSystem(ISystem system) |
Register a system to receive lifecycle calls |
ChocolateECS/
├── Bootstrapper.cs # Scene entry point, system orchestration
├── ComponentManager.cs # Component registration and queries
├── ECSFactory.cs # Runtime instantiation with tracking
├── ECSSystem.cs # Base class for systems
├── SingletonSystem.cs # Base class for singleton systems
├── IComponent.cs # Component marker interface
├── ISystem.cs # System interface
└── Examples/
├── Bootstrappers/ # Example bootstrapper
├── Components/ # Example components
└── Systems/ # Example systems
MIT License — see LICENSE for details.