A lightweight, beginner-friendly Entity Component System (ECS) framework for Unity, designed with simplicity and clarity in mind. ArtyECS provides a clean API that makes it easy to get started with ECS architecture, even if you're new to the pattern.
- Clear method names that express their purpose
- No complex registration - systems are just classes you instantiate
- Straightforward component management - add, get, remove with simple calls
- Beginner-friendly - designed to be approachable for developers new to ECS
- Maximum functionality through code - minimal Unity Inspector dependency
- No ScriptableObject configuration - everything happens in code
- Automatic initialization - All created automatically when needed
- Hybrid approach - works alongside MonoBehaviour, doesn't replace it
- Entity ↔ GameObject linking - easily connect ECS entities to Unity GameObjects
- Scene persistence - ECS World persists across Unity scene changes
- Multiple worlds support - global world by default, create scoped worlds when needed
- Visual hierarchy - see your ECS structure in Unity Hierarchy window
- Component inspection - view and edit component values in Play Mode
- Runtime debugging - monitor entities, components, and systems in real-time
- Editor-only - all tools automatically excluded from builds
- Performance metrics - track system execution times
- Query performance - monitor component query efficiency
- Memory usage - view component storage statistics
- Allocation tracking - identify memory allocation patterns
Requires a version of unity that supports path query parameter for git packages (Unity 2021.3 or later). You can add a reference https://github.com/arty-F/ArtyECS.git?path=Assets/ArtyECS to Package Manager.
ArtyECS makes ECS operations simple and intuitive. Here's how easy it is to get started:
Components are simple structs that implement IComponent:
using ArtyECS.Core;
public struct Health : IComponent
{
public float Amount;
}// Create an entity
var entity = World.CreateEntity();
// Add a component
World.AddComponent(entity, new Health { Amount = 100 });Systems use queries to find entities. Here's a system that finds entities with Health but without Dead component, and modifies the health value:
using ArtyECS.Core;
public class HealthSystem : SystemHandler
{
public override void Execute(WorldInstance world)
{
// Query: entities with Health component, but without Dead component
var entities = world.Query()
.With<Health>()
.Without<Dead>()
.Execute();
foreach (var entity in entities)
{
// Get modifiable reference to component
ref var health = ref world.GetModifiableComponent<Health>(entity);
// Modify component value directly
health.Amount -= 1f;
}
}
}var healthSystem = new HealthSystem();
World.AddToUpdate(healthSystem);That's it! The system will automatically execute every frame, finding all living entities and reducing their health.
ArtyECS supports multiple worlds. By default, all operations use the global world. You can also create named local worlds for isolation.
Global World (default):
// All World static methods use global world automatically
var entity = World.CreateEntity();
World.AddComponent(entity, new Health { Amount = 100 });
// Access global world explicitly
var globalWorld = World.GlobalWorld;Local Worlds:
// Get or create a named world
var localWorld = World.GetOrCreate("MyWorld");
// Check if world exists
if (World.Exists("MyWorld"))
{
// World exists
}
// Get all worlds
var allWorlds = World.GetAllWorlds();
// Destroy local world (cannot destroy global world)
World.Destroy(localWorld);World Methods (for global world):
World.GetOrCreate(string name = null)- Get or create world (null = global)World.GlobalWorld- Access global world instanceWorld.Exists(string name)- Check if world existsWorld.GetAllWorlds()- Get all active worldsWorld.Destroy(WorldInstance world)- Destroy local worldWorld.ClearAllECSState()- Clear all worlds
Using WorldInstance:
All World static methods are also available as instance methods on WorldInstance. Use World for the global world, or WorldInstance for a specific world:
// Using World (global world)
var entity = World.CreateEntity();
var health = World.GetComponent<Health>(entity);
// Using WorldInstance (specific world)
var localWorld = World.GetOrCreate("MyWorld");
var entity = localWorld.CreateEntity();
var health = localWorld.GetComponent<Health>(entity);Entities are lightweight identifiers (structs with Id and Generation fields). They don't store data themselves - they're just handles used to access components.
Creating and Destroying:
// Create entity (in global world)
var entity = World.CreateEntity();
// Create entity in specific world
var entity = localWorld.CreateEntity();
// Destroy entity (automatically removes all components)
World.DestroyEntity(entity);
localWorld.DestroyEntity(entity);Validation:
// Check if entity is valid
if (World.IsEntityValid(entity))
{
// Entity is valid
}Cloning:
// Clone entity with all components
var clonedEntity = World.CloneEntity(sourceEntity);Entity ↔ GameObject Linking:
// Create entity linked to GameObject
var entity = World.CreateEntity(gameObject);
// Get GameObject from entity
var go = World.GetGameObject(entity);
// Get entity from GameObject
var entity = World.GetEntity(gameObject);
// Destroy linked entity will automatically unlinks and destroys linked GameObject if exists
World.DestroyEntity(entity);
localWorld.DestroyEntity(entity);Entity Methods:
World.CreateEntity()/worldInstance.CreateEntity()- Create entityWorld.CreateEntity(GameObject)/worldInstance.CreateEntity(GameObject)- Create and linkWorld.DestroyEntity(Entity)/worldInstance.DestroyEntity(Entity)- Destroy entityWorld.IsEntityValid(Entity)/worldInstance.IsEntityValid(Entity)- Check validityWorld.GetGameObject(Entity)/worldInstance.GetGameObject(Entity)- Get linked GameObjectWorld.GetEntity(GameObject)/worldInstance.GetEntity(GameObject)- Get linked entityWorld.GetAllEntities()/worldInstance.GetAllEntities()- Get all entities
Entity Extension Methods:
entity.Get<T>(WorldInstance world = null)- Get component (uses global world if null)entity.Has<T>(WorldInstance world = null)- Check if has componententity.AddComponent<T>(T component, WorldInstance world = null)- Add componententity.RemoveComponent<T>(WorldInstance world = null)- Remove component
Components are structs that store data. Each entity can have at most one component of each type.
Adding and Removing:
// Add component
World.AddComponent(entity, new Health { Amount = 100 });
localWorld.AddComponent(entity, new Health { Amount = 100 });
entity.AddComponent(new Health { Amount = 100 }); // Extension method
// Remove component
World.RemoveComponent<Health>(entity);
localWorld.RemoveComponent<Health>(entity);
entity.RemoveComponent<Health>(); // Extension methodGetting Components:
// Get component (throws ComponentNotFoundException if not found)
var health = World.GetComponent<Health>(entity);
var health = localWorld.GetComponent<Health>(entity);
var health = entity.Get<Health>(); // Extension method
// Get modifiable component (for direct modification)
ref var health = ref World.GetModifiableComponent<Health>(entity);
ref var health = ref localWorld.GetModifiableComponent<Health>(entity);
ref var health = ref entity.GetModifiable<Health>(); // Extension method
// Check if entity has component
if (entity.Has<Health>())
{
// Entity has Health component
}Getting All Components of Type:
// Read-only access
var healths = World.GetComponents<Health>();
foreach (ref readonly var health in healths)
{
// Process health
}
// Modifiable access
var healths = World.GetModifiableComponents<Health>();
for (int i = 0; i < healths.Count; i++)
{
ref var health = ref healths[i];
health.Amount -= 1f; // Modify directly
}Component Methods:
World.AddComponent<T>(Entity, T)/worldInstance.AddComponent<T>(Entity, T)- Add componentWorld.GetComponent<T>(Entity)/worldInstance.GetComponent<T>(Entity)- Get componentWorld.GetModifiableComponent<T>(Entity)/worldInstance.GetModifiableComponent<T>(Entity)- Get modifiable refWorld.RemoveComponent<T>(Entity)/worldInstance.RemoveComponent<T>(Entity)- Remove componentWorld.GetComponents<T>()/worldInstance.GetComponents<T>()- Get all components (read-only)World.GetModifiableComponents<T>()/worldInstance.GetModifiableComponents<T>()- Get all components (modifiable)World.GetAllComponentInfos(Entity)/worldInstance.GetAllComponentInfos(Entity)- Get all component infoWorld.CloneEntity(Entity)/worldInstance.CloneEntity(Entity)- Clone entity
Systems contain the logic that processes entities and components. They execute in a deterministic order within their queue. Systems are tied to Unity's Update/FixedUpdate lifecycle - Update systems execute every frame, FixedUpdate systems execute every fixed timestep.
Creating Systems:
public class HealthSystem : SystemHandler
{
public override void Execute(WorldInstance world)
{
var entities = world.GetEntitiesWith<Health>();
foreach (var entity in entities)
{
ref var health = ref world.GetModifiableComponent<Health>(entity);
health.Amount -= 1f;
}
}
}Registering Systems:
var healthSystem = new HealthSystem();
// Add to Update queue (executes every frame)
World.AddToUpdate(healthSystem);
localWorld.AddToUpdate(healthSystem);
healthSystem.AddToUpdate(); // Extension method (uses global world)
healthSystem.AddToUpdate(localWorld); // Extension method (specific world)
// Add to Update with execution order (lower values execute first)
World.AddToUpdate(healthSystem, order: 100);
healthSystem.AddToUpdate(order: 100); // Extension method
// Add to FixedUpdate queue (executes every fixed timestep)
World.AddToFixedUpdate(healthSystem);
healthSystem.AddToFixedUpdate(); // Extension method
// Add to FixedUpdate with execution order
World.AddToFixedUpdate(healthSystem, order: 50);
healthSystem.AddToFixedUpdate(order: 50); // Extension methodManual Execution:
// Execute system once immediately (not added to queue)
World.ExecuteOnce(healthSystem);
localWorld.ExecuteOnce(healthSystem);
healthSystem.ExecuteOnce(); // Extension method
// Manually execute all systems in queue
// Note: These methods execute systems immediately, in addition to Unity's automatic execution. Systems will be called twice per frame if you call these manually.
World.ExecuteUpdate(); // Execute all Update systems
World.ExecuteFixedUpdate(); // Execute all FixedUpdate systemsRemoving Systems:
// Remove from Update queue
World.RemoveFromUpdate(healthSystem);
// Remove from FixedUpdate queue
World.RemoveFromFixedUpdate(healthSystem);Inspecting Queues:
// Get all systems in Update queue
var updateSystems = World.GetUpdateQueue();
// Get all systems in FixedUpdate queue
var fixedUpdateSystems = World.GetFixedUpdateQueue();System Methods:
World.AddToUpdate(SystemHandler)/worldInstance.AddToUpdate(SystemHandler)- Add to Update queueWorld.AddToUpdate(SystemHandler, int order)/worldInstance.AddToUpdate(SystemHandler, int order)- Add with orderWorld.AddToFixedUpdate(SystemHandler)/worldInstance.AddToFixedUpdate(SystemHandler)- Add to FixedUpdate queueWorld.AddToFixedUpdate(SystemHandler, int order)/worldInstance.AddToFixedUpdate(SystemHandler, int order)- Add with orderWorld.ExecuteOnce(SystemHandler)/worldInstance.ExecuteOnce(SystemHandler)- Execute onceWorld.ExecuteUpdate()/worldInstance.ExecuteUpdate()- Execute Update queueWorld.ExecuteFixedUpdate()/worldInstance.ExecuteFixedUpdate()- Execute FixedUpdate queueWorld.RemoveFromUpdate(SystemHandler)/worldInstance.RemoveFromUpdate(SystemHandler)- Remove from UpdateWorld.RemoveFromFixedUpdate(SystemHandler)/worldInstance.RemoveFromFixedUpdate(SystemHandler)- Remove from FixedUpdateWorld.GetUpdateQueue()/worldInstance.GetUpdateQueue()- Get Update queueWorld.GetFixedUpdateQueue()/worldInstance.GetFixedUpdateQueue()- Get FixedUpdate queue
System Extension Methods:
system.AddToUpdate(WorldInstance world = null)- Add to Updatesystem.AddToUpdate(int order, WorldInstance world = null)- Add to Update with ordersystem.AddToFixedUpdate(WorldInstance world = null)- Add to FixedUpdatesystem.AddToFixedUpdate(int order, WorldInstance world = null)- Add to FixedUpdate with ordersystem.ExecuteOnce(WorldInstance world = null)- Execute once
Queries find entities based on component presence. ArtyECS supports queries with up to 3 component types using direct methods, or unlimited types using QueryBuilder. All query methods are optimized for zero allocations using pooled collections and frame-based lifetime management.
Simple Queries (up to 3 types):
// Entities with single component
var entities = World.GetEntitiesWith<Health>();
var entities = localWorld.GetEntitiesWith<Health>();
// Entities with two components
var entities = World.GetEntitiesWith<Health, Position>();
// Entities with three components
var entities = World.GetEntitiesWith<Health, Position, Velocity>();
// Entities without single component
var entities = World.GetEntitiesWithout<Dead>();
// Entities without two components (without T1, T2)
var entities = World.GetEntitiesWithout<Dead, Destroyed>();
// Entities without three components (without T1, T2, T3)
var entities = World.GetEntitiesWithout<Dead, Destroyed, Removed>();Composable Queries (QueryBuilder):
// Query with multiple With and Without conditions
var entities = World.Query()
.With<Health>()
.With<Position>()
.Without<Dead>()
//...
.Execute();Query Methods:
World.GetEntitiesWith<T1>()/worldInstance.GetEntitiesWith<T1>()- Entities with T1World.GetEntitiesWith<T1, T2>()/worldInstance.GetEntitiesWith<T1, T2>()- Entities with T1 and T2World.GetEntitiesWith<T1, T2, T3>()/worldInstance.GetEntitiesWith<T1, T2, T3>()- Entities with T1, T2, and T3World.GetEntitiesWithout<T1>()/worldInstance.GetEntitiesWithout<T1>()- Entities without T1World.GetEntitiesWithout<T1, T2>()/worldInstance.GetEntitiesWithout<T1, T2>()- Entities without T1 or T2World.GetEntitiesWithout<T1, T2, T3>()/worldInstance.GetEntitiesWithout<T1, T2, T3>()- Entities without T1, T2, or T3World.Query()/worldInstance.Query()- Create QueryBuilder for composable queries
When you enter Play Mode, ArtyECS automatically creates a visual hierarchy in the Unity Hierarchy window:
- ArtyEcs (root)
- Global (world)
- Entities (container)
- Entity_123 (entity)
- Component displays
- Entity_123 (entity)
- Systems (container)
- Update (queue)
- MovementSystem (system)
- FixedUpdate (queue)
- PhysicsSystem (system)
- Update (queue)
- Entities (container)
- Global (world)
In Play Mode, you can create a new entity from inspector:
In Play Mode, you can:
- View component values
- Edit component values in real time
- Add or remove components
- Monitor changes
In Play Mode, you can add any system to queue:
Also you can remove specific sysem from a queue:
ArtyECS includes built-in performance monitoring tools accessible through the Unity Editor.
Navigate to: Window → ArtyECS → Performance Monitor → Enable Monitoring (in monitoring window)
The Performance Monitor displays:
-
System Execution Times
- Execution time per system
- Total execution time per frame
- System execution order
-
Query Performance
- Query execution times
- Number of entities processed
- Query frequency
-
Memory Usage
- Component storage statistics
- Entity pool usage
- Memory allocation patterns
-
Allocation Tracking
- Allocation events per frame
- Allocation sources
- Memory growth over time
- Auto-refresh: Toggle to automatically update metrics
- Manual refresh: Click refresh button to update immediately
- World filtering: Select specific world to monitor (if multiple worlds exist)
- Throttled updates: Metrics update every 1 seconds by default