Scellecs.Morpeh
2024.1.0
dotnet add package Scellecs.Morpeh --version 2024.1.0
NuGet\Install-Package Scellecs.Morpeh -Version 2024.1.0
<PackageReference Include="Scellecs.Morpeh" Version="2024.1.0" />
paket add Scellecs.Morpeh --version 2024.1.0
#r "nuget: Scellecs.Morpeh, 2024.1.0"
// Install Scellecs.Morpeh as a Cake Addin #addin nuget:?package=Scellecs.Morpeh&version=2024.1.0 // Install Scellecs.Morpeh as a Cake Tool #tool nuget:?package=Scellecs.Morpeh&version=2024.1.0
Morpeh
๐ฒ ECS Framework for Unity Game Engine and .Net Platform
- Simple Syntax.
- Plug & Play Installation.
- No code generation.
- Structure-Based and Cache-Friendly.
๐ Table of Contents
๐ Introduction
๐ Base concept of ECS pattern
๐ Entity
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);
๐ Component
Components are types which include only data.
In Morpeh components are value types for performance purposes.
public struct HealthComponent : IComponent {
public int healthPoints;
}
๐ System
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.
๐ SystemsGroup
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);
๐ World
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();
๐ Filter
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();
๐ Stash
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);
๐ Advanced
๐งฉ Filter Extensions
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 Disposing
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 whichDispose
was called.
๐งน Component Disposing
[!IMPORTANT]
Make sure you don't have theMORPEH_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.
๐ Stash size
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.
๐ Plugins
- Morpeh Helpers
- Morpeh.Events
- Morpeh.SystemStateProcessor
- Morpeh.Queries
- Morpeh.SourceGenerator
- PlayerLoopAPI Runner Morpeh plugin
๐ Examples
- Tanks by SH42913
- Ping Pong by SH42913
- Flappy Bird by R1nge
๐ฅ Games
Stickman of Wars: RPG Shooters by Multicast Games
Android iOSCowravaneer by FESUNENKO GAMES
Android
๐ License
๐ MIT License
๐ฌ Contacts
โ๏ธ Telegram: olegmrzv
๐ง E-Mail: benjminmoore@gmail.com
๐ฅ Telegram Community RU: Morpeh ECS Development
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.1 is compatible. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Scellecs.Morpeh:
Package | Downloads |
---|---|
Undine.Morpeh
Undine bindings for Morpeh Ecs |
GitHub repositories (1)
Showing the top 1 popular GitHub repositories that depend on Scellecs.Morpeh:
Repository | Stars |
---|---|
Doraku/Ecs.CSharp.Benchmark
Benchmarks of some C# ECS frameworks.
|