Framework
scellecs/morpeh avatar
scellecs/morpeh

morpeh: an ECS framework that keeps archetypes out of storage

🎲 ECS Framework for Unity Game Engine and .Net Platform

671 stars56 forksC#MIT

At a glance

What is it?
Morpeh is a C# entity component system framework for Unity and .NET with an unusual split: archetypes exist only to select entities, while component data lives in per-type stashes. The result is a framework aimed at large codebases and many component types rather than large numbers of entities.
Who is it for?
Adopt morpeh if you are building a Unity or .NET game with a large number of component types and systems, a team that needs systems to be plain interfaces, and a preference for readable syntax over code generation. Skip it if your workload is dominated by very large entity counts, since the design targets many types rather than many entities, or if you rely on ScriptableObject based systems, which are deprecated in the 2024 version.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 39 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Archetypes filter, stashes store

The README spends most of its opening on how Morpeh differs from other entity component system frameworks, and the first difference is the storage model.

Morpeh is archetype-based, but it stores data separately from archetypes. Archetypes are used only for filtering, and there is no archetype transition graph. The stated consequence is that you should not have to worry about performance degradation over time for lots of chaotically changing entities, which is the failure mode that archetype migration graphs create when entities change shape constantly.

Component data itself lives in what the README calls stashes, known as pools in other frameworks. A stash holds the data for one component type, and entities point into it.

The scope of the design is stated just as precisely. Morpeh is designed to work well with a large quantity of different component types and systems in a single world, several thousands of component types and systems, but not necessarily with a large number of entities. It is also designed to work well in a large team with a large codebase, reducing how much a typical developer has to remember, and you are free to migrate entities through structural changes even when an entity holds tens of different components, without a significant performance impact.

Read that pair of sentences together and the framework's target becomes obvious: breadth of types, not depth of entities.

Entities are value types with reused identifiers

An entity in Morpeh is an identifier for components. It stores no data of its own, which the README compares to a GameObject in Unity, except that the GameObject analogue is logical rather than literal. An entity is a value type and is trivially copyable.

The interesting part is the identity handling. Underlying identifiers are reused, but each reused identifier is guaranteed to have a new generation, so every new entity is unique. That is the standard technique for avoiding the classic entity handle bug where a stale reference to a destroyed entity silently starts pointing at whatever took its slot, but here it is a documented guarantee rather than an implementation detail you have to discover.

The stash is where the interaction happens, and the API returns references so that a system can mutate a component without copying it:

csharp
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);

The comment on the removal line is a real semantic to keep in mind: removing the last component of an entity does not destroy it immediately, it is destroyed during the next world.Commit() call. Code that removes a component and then assumes the entity is gone in the same frame is working against the framework's model.

Components are plain structs marked with an interface

Components in Morpeh are types that store component data, and they are value types for performance purposes. The declaration is minimal, with no base class to inherit and no attributes to attach:

csharp
public struct HealthComponent : IComponent {
    public int healthPoints;
}

Because a component is a struct, it is copied when assigned and it lives inside the stash array rather than on the heap as an individual object. That has a practical consequence for how systems are written, which the next section shows.

The IComponent marker interface is the only requirement, which keeps the constraint light enough that a component can be declared next to the system that uses it without any registration step. The README advertises the absence of code generation as a feature, with the parenthesis yet, so the framework currently expects you to write these types by hand.

There is a downside that follows from structs in a stash: because the data is stored in an array indexed by entity, and because a removed component is not compacted until the next Commit, code that holds a reference to a component across a removal can end up pointing at a slot that is about to be reused. The reference-returning stash calls are designed to be used inside a system loop rather than stored for later.

Systems are interfaces with five lifecycle shapes

Systems are types that process entities holding a specific set of components, and entities are selected with a filter. All systems are represented by interfaces, and the README lists five of them.

IInitializer has OnAwake and Dispose only, which makes it convenient for startup logic. ISystem is the main system that runs every frame in Update, used for main game logic and data processing. IFixedSystem runs in FixedUpdate with a fixed time step. ILateSystem runs in LateUpdate after all Updates, for logic that should come after the main pass. ICleanupSystem runs after ILateSystem, for cleanup, resetting state and end-of-frame tasks.

A system therefore looks like this, with the filter and the stash both resolved in OnAwake:

csharp
public class HealthSystem : ISystem {

    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;
        }
    }

The interface approach is what makes the framework pleasant in a large team: a system is an ordinary class, unit tests can construct one, and there is no editor asset to keep in sync. The README also warns that ScriptableObject based systems still exist in the 2024 version but are deprecated and will be removed in the future, so any tutorial showing them describes a path that is closing.

Filters belong in OnAwake, not in the update loop

Filters select entities by component type, and the README is unusually prescriptive about how to use them: unlike in other frameworks they should not be used in place, and should rather be declared in the system OnAwake and stored in a variable. The build call in the system example above is the pattern, with the filter constructed once and reused every frame.

The reason given is performance and idle time. Filters directly store archetypes, and you can know whether a filter is empty without iterating over it. That check is worth doing when a world contains a large number of systems, since each idle system still costs a call, and the README points at Unity's IL2CPP as a case where shaving that cost matters.

The trade-off is that the filter is a snapshot of the world built at OnAwake time. Since a system selects by component type, and a filter stores archetypes, the framework has to keep the two in step as entities gain and lose components, and the document's guidance to declare filters in OnAwake rather than build them per frame is really about avoiding that bookkeeping in the hot path.

The rest of the advanced sections follow from the same model: filter extensions, filter and component disposal, aspects that group filters, Unity Jobs and Burst support, defines for conditional compilation, world plugins, a world browser for inspection, metrics, stash size inspection and the collection types that go with it.

Installing through the Unity package manager or NuGet

For Unity, the minimum supported version is 2020.3. Installing the package requires Git, and drawing the inspector requires Tri Inspector, which is an external editor tool the framework depends on for its custom inspectors.

The install itself goes through the Unity Package Manager, where the repository URL points at a subfolder of the Git repository:

bash
https://github.com/scellecs/morpeh.git?path=Scellecs.Morpeh

The README also lists the same URL with a branch suffix for the stage branch and with a tag suffix for a specific version, so a project can pin the exact package version it was written against. The path query is the part that matters, since the repository holds more than the Unity package: the solution file, the Tests directory, the Benchmarks directory and the StaticGenerators directory sit alongside it.

For a plain .NET project, the framework is distributed on NuGet:

bash
https://www.nuget.org/packages/Scellecs.Morpeh

The .NET route skips Unity entirely, which is the point of the separate NuGet README, and it means the core ECS model can be used in a server, a tool or a test harness.

Two upgrade aids are referenced for anyone moving between versions: MIGRATION.md in English and a Russian translation, MIGRATION_RU.md, both generated as part of a version migration process rather than left to release notes.

Where the design stops fitting, and what the release history says

The clearest limit is stated by the project itself. Morpeh is built for a world with several thousands of component types and systems, but not necessarily with a large number of entities. A project whose bottleneck is a hundred thousand entities and a handful of component types is looking at a different set of trade-offs than the ones Morpeh optimised for.

The other boundary is the code generation question. The feature list says there is no code generation yet, so component and system types are written by hand, and the repository holds a StaticGenerators directory that suggests work in that direction without documenting it as shipped.

On versions, the release history is steady and versioned by year: 2023.1.1 published on 2024-10-15, 2024.1.0 on 2024-12-11, and 2024.1.1 on 2025-04-01. The last push was on 2026-09-14, so the code is moving faster than the tagged packages, which is a normal arrangement for a Unity package distributed by git URL.

The licence is MIT, which for a framework you embed in a game is about as permissive as it gets, with no obligation to publish a game built on it.

The repository also ships Tests and Benchmarks directories, and a changelog. That is a good sign for a framework whose correctness depends on subtle identity and lifetime rules, but the Benchmarks folder is the place to look before making claims about throughput, since it is the project's own harness rather than an independent measurement.

Editorial conclusion

Adopt morpeh if you are building a Unity or .NET game with a large number of component types and systems, a team that needs systems to be plain interfaces, and a preference for readable syntax over code generation. Skip it if your workload is dominated by very large entity counts, since the design targets many types rather than many entities, or if you rely on ScriptableObject based systems, which are deprecated in the 2024 version. Before migrating an existing project, read MIGRATION.md and check the version tags listed in the README, since the 2024 release removed behaviour older tutorials still describe. The last push was on 2026-09-14 and the most recent published package is 2024.1.1 from 2025-04-01.

Frequently asked questions

What are stashes and archetypes in morpeh?

Components are stored in stashes, also known as pools in other frameworks, while archetypes are used only for filtering entities. There is no archetype transition graph, so chaotically changing entities do not accumulate a performance penalty over time.

How do I install morpeh in a Unity project?

Add the Git URL for the package subfolder in the Unity Package Manager, using the path query to point at Scellecs.Morpeh. You need Unity 2020.3 or later, Git for the install, and Tri Inspector for drawing the inspector.

Can I use morpeh outside of Unity?

Yes. The framework is published on NuGet for the .Net platform, and the README keeps a separate README_NUGET.md for that distribution. The core entity, component and system model does not depend on Unity.

How do I write a system in morpeh?

Implement one of the system interfaces, such as ISystem, build a filter in OnAwake with the World.Filter.With call, and iterate the filter in OnUpdate. The README recommends declaring filters once in OnAwake rather than using them in place.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. scellecs/morpeh on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/scellecs-morpeh.svg)](https://hysenlabs.com/projects/scellecs-morpeh)

Was this useful?