Scellecs.Morpeh 2024.1.0

dotnet add package Scellecs.Morpeh --version 2024.1.0                
NuGet\Install-Package Scellecs.Morpeh -Version 2024.1.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Scellecs.Morpeh" Version="2024.1.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Scellecs.Morpeh --version 2024.1.0                
#r "nuget: Scellecs.Morpeh, 2024.1.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// 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 License Unity Version

๐ŸŽฒ 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 logic
  • ISystem - main system that executes every frame in Update. Used for main game logic and data processing
  • IFixedSystem - system that executes in FixedUpdate with fixed time step
  • ILateSystem - system that executes in LateUpdate, after all Updates. Useful for logic that should run after main updates
  • ICleanupSystem - 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 which Dispose was called.

๐Ÿงน Component Disposing

[!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.

๐Ÿ“ 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


๐Ÿ“š Examples


๐Ÿ”ฅ Games

  • One State RP - Life Simulator by Chillbase
    Android iOS

  • Zombie City by GreenButtonGames
    Android iOS

  • Fish Idle by GreenButtonGames
    Android iOS

  • Stickman of Wars: RPG Shooters by Multicast Games
    Android iOS

  • Cowravaneer by FESUNENKO GAMES
    Android


๐Ÿ“˜ License

๐Ÿ“„ MIT License


๐Ÿ’ฌ Contacts

โœ‰๏ธ Telegram: olegmrzv
๐Ÿ“ง E-Mail: benjminmoore@gmail.com
๐Ÿ‘ฅ Telegram Community RU: Morpeh ECS Development

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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.
Version Downloads Last updated
2024.1.0 79 12/11/2024
2023.1.1 105 10/15/2024
2023.1.0 2,437 9/19/2023
2023.1.0-rc7 290 1/9/2023
2022.2.2 307 2/26/2023
2022.2.1 303 1/16/2023
2022.2.0 301 12/29/2022