🎲 ECS Framework for Unity Game Engine and .Net Platform
- Simple Syntax.
- Plug & Play Installation.
- No code generation.
- Structure-Based and Cache-Friendly.
An identifier for components, which does not store any data but can be used to access components. It is a value type, and is trivially copyable. Underlying identifiers (IDs) are reused, but each reused ID is guaranteed to have a new generation, making each new Entity unique.
var healthStash = this.World.GetStash<HealthComponent>();
var entity = this.World.CreateEntity();
ref var addedHealthComponent = ref healthStash.Add(entity);
ref var gottenHealthComponent = ref healthStash.Get(entity);
//if you remove the last entity component, it will be destroyed during the next world.Commit() call
bool removed = healthStash.Remove(entity);
healthStash.Set(entity, new HealthComponent {healthPoints = 100});
bool hasHealthComponent = healthStash.Has(entity);
var debugString = entity.ToString();
//remove entity
this.World.RemoveEntity(entity);
//check disposal
bool isDisposed = this.World.IsDisposed(entity);
//alternatively
bool has = this.World.Has(entity);
Components are types which include only data.
In Morpeh components are value types for performance purposes.
public struct HealthComponent : IComponent {
public int healthPoints;
}
Types that process entities with a specific set of components.
Entities are selected using a filter.
All systems are represented by interfaces.
public class HealthSystem : ISystem {
public World World { get; set; }
private Filter filter;
private Stash<HealthComponent> healthStash;
public void OnAwake() {
this.filter = this.World.Filter.With<HealthComponent>().Build();
this.healthStash = this.World.GetStash<HealthComponent>();
}
public void OnUpdate(float deltaTime) {
foreach (var entity in this.filter) {
ref var healthComponent = ref healthStash.Get(entity);
healthComponent.healthPoints += 1;
}
}
public void Dispose() {
}
}
All systems types:
IInitializer
- have only OnAwake and Dispose methods, convenient for executing startup logicISystem
- main system that executes every frame in Update. Used for main game logic and data processingIFixedSystem
- system that executes in FixedUpdate with fixed time stepILateSystem
- system that executes in LateUpdate, after all Updates. Useful for logic that should run after main updatesICleanupSystem
- system that executes after ILateUpdateSystem. Designed for cleanup operations, resetting states, and handling end-of-frame tasks
Beware that ScriptableObject-based systems do still exist in 2024 version, but they are deprecated and will be removed in the future.
The type that contains the systems. Consider them as a "feature" to group the systems by their common purpose.
var newWorld = World.Create();
var newSystem = new HealthSystem();
var newInitializer = new HealthInitializer();
var systemsGroup = newWorld.CreateSystemsGroup();
systemsGroup.AddSystem(newSystem);
systemsGroup.AddInitializer(newInitializer);
//it is a bad practice to turn systems off and on, but sometimes it is very necessary for debugging
systemsGroup.DisableSystem(newSystem);
systemsGroup.EnableSystem(newSystem);
systemsGroup.RemoveSystem(newSystem);
systemsGroup.RemoveInitializer(newInitializer);
newWorld.AddSystemsGroup(order: 0, systemsGroup);
newWorld.RemoveSystemsGroup(systemsGroup);
A type that contains entities, components stashes, systems and root filter.
var newWorld = World.Create();
//a variable that specifies whether the world should be updated automatically by the game engine.
//if set to false, then you can update the world manually.
//and can also be used for game pauses by changing the value of this variable.
newWorld.UpdateByUnity = true;
var newEntity = newWorld.CreateEntity();
newWorld.RemoveEntity(newEntity);
var systemsGroup = newWorld.CreateSystemsGroup();
systemsGroup.AddSystem(new HealthSystem());
newWorld.AddSystemsGroup(order: 0, systemsGroup);
newWorld.RemoveSystemsGroup(systemsGroup);
var filter = newWorld.Filter.With<HealthComponent>();
var healthCache = newWorld.GetStash<HealthComponent>();
var reflectionHealthCache = newWorld.GetReflectionStash(typeof(HealthComponent));
//manually world updates
newWorld.Update(Time.deltaTime);
newWorld.FixedUpdate(Time.fixedDeltaTime);
newWorld.LateUpdate(Time.deltaTime);
newWorld.CleanupUpdate(Time.deltaTime);
//apply all entity changes, filters will be updated.
//automatically invoked between systems
newWorld.Commit();
A type that allows filtering entities constrained by conditions With and/or Without.
You can chain them in any order and quantity.
Call Build()
to finalize the filter for further use.
var filter = this.World.Filter.With<HealthComponent>()
.With<BooComponent>()
.Without<DummyComponent>()
.Build();
var firstEntityOrException = filter.First();
var firstEntityOrNull = filter.FirstOrDefault();
bool filterIsEmpty = filter.IsEmpty();
bool filterIsNotEmpty = filter.IsNotEmpty();
int filterLengthCalculatedOnCall = filter.GetLengthSlow();
A type that stores components data.
var healthStash = this.World.GetStash<HealthComponent>();
var entity = this.World.CreateEntity();
ref var addedHealthComponent = ref healthStash.Add(entity);
ref var gottenHealthComponent = ref healthStash.Get(entity);
bool removed = healthStash.Remove(entity);
healthStash.Set(entity, new HealthComponent {healthPoints = 100});
bool hasHealthComponent = healthStash.Has(entity);
//delete all HealthComponent from the world (affects all entities)
healthStash.RemoveAll();
bool healthStashIsEmpty = healthStash.IsEmpty();
bool healthStashIsNotEmpty = healthStash.IsNotEmpty();
var newEntity = this.World.CreateEntity();
//transfers a component from one entity to another
healthStash.Migrate(from: entity, to: newEntity);
//not a generic variation of stash, so we can only do a limited set of operations
var reflectionHealthCache = newWorld.GetReflectionStash(typeof(HealthComponent));
//set default(HealthComponent) to entity
reflectionHealthCache.Set(entity);
bool removed = reflectionHealthCache.Remove(entity);
bool hasHealthComponent = reflectionHealthCache.Has(entity);
Filter extensions are a way to reuse queries or their parts. Let's look at an example:
Create a struct and implement the IFilterExtension interface.
public struct SomeExtension : IFilterExtension {
public FilterBuilder Extend(FilterBuilder rootFilter) => rootFilter.With<Translation>().With<Rotation>();
}
The next step is to call the Extend method in any order when requesting a filter.
The Extend method continues query.
private Filter filter;
public void OnAwake() {
this.filter = this.World.Filter.With<TestA>()
.Extend<SomeExtension>()
.With<TestC>()
.Build();
}
Filter.Dispose
allows you to completely remove the filter from the world, as if it never existed there.
Important
Filter.Dispose
removes all filter instances across all systems where it was used, not just the instance on which Dispose
was called.
Important
Make sure you don't have the MORPEH_DISABLE_COMPONENT_DISPOSE
define enabled.
Sometimes it becomes necessary to clear component values.
For this, it is enough that a component implements IDisposable
. For example:
public struct PlayerView : IComponent, IDisposable {
public GameObject value;
public void Dispose() {
Object.Destroy(value);
}
}
An initializer or a system needs to mark the stash as disposable. For example:
public class PlayerViewDisposeInitializer : IInitializer {
public void OnAwake() {
this.World.GetStash<PlayerView>().AsDisposable();
}
public void Dispose() {
}
}
or
public class PlayerViewSystem : ISystem {
public void OnAwake() {
this.World.GetStash<PlayerView>().AsDisposable();
}
public void OnUpdate(float deltaTime) {
...
}
public void Dispose() {
}
}
Now, when the component is removed from an entity, the Dispose()
method will be called on the PlayerView
component.
If you know the expected number of components in a stash, you have the option to set a base size to prevent resizing and avoid unnecessary allocations.
ComponentId<T>.StashSize = 1024;
This value is not tied to a specific World
, so it needs to be set before starting ECS, so that all newly created stashes of this type in any World
have the specified capacity.
- Morpeh Helpers
- Morpeh.Events
- Morpeh.SystemStateProcessor
- Morpeh.Queries
- Morpeh.SourceGenerator
- PlayerLoopAPI Runner Morpeh plugin
- Tanks by SH42913
- Ping Pong by SH42913
- Flappy Bird by R1nge
-
Stickman of Wars: RPG Shooters by Multicast Games
Android iOS -
Cowravaneer by FESUNENKO GAMES
Android
✉️ Telegram: olegmrzv
📧 E-Mail: [email protected]
👥 Telegram Community RU: Morpeh ECS Development