npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

com.elestrago.unity.entitas-redux

v4.1.3

Published

Entitas Redux is an fast, accessible, and feature-rich ECS framework for Unity. It leverages code generation and an extensible plugin framework to make life easier for developers.

Readme

JCMG Entitas Redux

NPM package License: MIT

Installing

Using the native Unity Package Manager introduced in 2017.2, you can add this library as a package by modifying your manifest.json file found at /ProjectName/Packages/manifest.json to include it as a dependency. See the example below on how to reference it.

Install via NPM

The package is available on the npmjs registry.

Add registry scope

{
  "dependencies": {
    "com.elestrago.unity.entitas-redux": "x.x.x"
  },
  "scopedRegistries": [
    {
      "name": "eLeSTRaGo",
      "url": "https://registry.npmjs.org",
      "scopes": [
        "com.elestrago.unity"
      ]
    }
  ]
}

Entitas Redux

About

This version of Entitas Redux is a reworked version of EntitasRedux with a sole focus on Unity.

Requirements

  • Min Unity Version: 2022.3

Entitas Redux ECS Usage Guideline

This guideline describes the standard usage of the Entitas Redux ECS framework in this project.

1. Context Creation

Contexts are defined using the ContextAttribute. Declare a partial class inheriting from ContextAttribute.

using JCMG.EntitasRedux;

public sealed partial class GameAttribute : ContextAttribute
{
}

The generator will automatically create GameContext, GameEntity, and GameMatcher.

2. Component Creation

Components must implement IComponent, have one or more context attributes, and their name should end with Component.

  • Simple Component:
[Game]
public struct PositionComponent : IComponent
{
    public float x;
    public float y;
}
  • Flag Component: Components with no fields are treated as flags. The generator creates a Has[FlagName] readonly property and a Set[FlagName](bool value) method to manage them.
[Game]
public struct MovableComponent : IComponent
{
}

3. Entity Indexes

Entity Indexes allow you to efficiently retrieve entities based on component values.

  • PrimaryEntityIndex: Used for unique values.
[Game]
public struct IdComponent : IComponent
{
    [PrimaryEntityIndex]
    public int value;
}
  • EntityIndex: Used for non-unique values.
[Game]
public struct NameComponent : IComponent
{
    [EntityIndex]
    public string value;
}

4. Unique Components

For components that should only exist on a single entity at any time, use the [Unique] attribute.

[Game, Unique]
public struct PlayerComponent : IComponent
{
}

This generates a property on the context for direct access:

// Get the unique player entity
var player = context.PlayerEntity;

// Check if it exists
if (context.HasPlayer) { /* ... */ }

// Set a new unique entity (the old one will lose the component)
context.PlayerEntity = newPlayerEntity;

5. Entity Creation and Mutation

Use the generated context and entity API to create and mutate entities.

  • Creating an Entity:
var context = Contexts.SharedInstance.Game;
var entity = context.CreateEntity();
  • Adding, Replacing, and Removing Components:
// Add or replace a component with values
entity.AddPosition(10f, 20f);

// Safely add a component
if (entity.TryAddPosition(10f, 20f)) { /* Added */ }

// Remove a component
entity.RemovePosition();

// Safely remove a component
if (entity.TryRemovePosition()) { /* Removed */ }
  • Working with Flag Components: Flag components are managed with a setter method.
// Check for a flag
bool isMovable = entity.HasMovable;

// Add a flag. Triggers groups only if the flag was not already present.
entity.SetMovable(true);

// Remove a flag. Triggers groups only if the flag was present.
entity.SetMovable(false);
  • Entity Lifecycle and Safety: Check if an entity is still active in the context using IsAlive.
if (entity.IsAlive) { /* Safe to mutate */ }
  • Retrieving Entities by Index:
// Primary
var entity = context.GetEntityWithId(42);

// Non-primary
var entities = context.GetEntitiesWithName("Player");

6. Feature and System Creation

Systems implement specific lifecycle interfaces, and Features group them together.

  • Execution Systems: Implement IInitializeSystem, IUpdateSystem, IFixedUpdateSystem, ILateUpdateSystem, ICleanupSystem, or ITearDownSystem.
  • Asynchronous Systems: Implement IInitializeAsyncSystem for async setup logic.
  • Reactive Systems: Implement IReactiveSystem to react to component changes.
// IUpdateSystem for frame-rate dependent logic
public class MovementSystem : IUpdateSystem
{
    private readonly IGroup<GameEntity> _group;

    public MovementSystem(GameContext context)
    {
        _group = context.GetGroup(GameMatcher.AllOf(GameMatcher.Position, GameMatcher.Velocity));
    }

    public void Update()
    {
        var dt = UnityEngine.Time.deltaTime;
        using var _ = _group.GetEntities(out var buffer);
        for (var i = 0; i < buffer.Length; i++)
        {
            var e = buffer[i];
            var pos = e.Position;
            var vel = e.Velocity;
            e.AddPosition(pos.x + vel.x * dt, pos.y + vel.y * dt);
        }
    }
}

// IReactiveSystem for reacting to data changes
public class DebugLogHealthSystem : ReactiveSystem<GameEntity>
{
    public DebugLogHealthSystem(GameContext context) : base(context) { }

    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
    {
        // Trigger when HealthComponent is added or replaced
        return context.CreateCollector(GameMatcher.Health);
    }

    protected override bool Filter(GameEntity entity)
    {
        // Only process entities that have health and are still alive
        return entity.HasHealth && entity.IsAlive;
    }

    protected override void Execute(System.Collections.Generic.List<GameEntity> entities)
    {
        foreach (var e in entities)
        {
            UnityEngine.Debug.Log($"Entity {e.Id} health: {e.Health.value}");
        }
    }
}
  • Features: Create a class inheriting from Feature to group systems.
public class GameSystems : Feature
{
    public GameSystems(Contexts contexts) : base("Game Systems")
    {
        // Initialization
        Add(new DataLoadSystem()); // IInitializeAsyncSystem
        Add(new InitializeLevelSystem(contexts.Game)); // IInitializeSystem

        // Logic
        Add(new MovementSystem(contexts.Game)); // IUpdateSystem
        Add(new DebugLogHealthSystem(contexts.Game)); // IReactiveSystem

        // Cleanup
        Add(new CleanupSystem()); // ICleanupSystem
    }
}

7. Group API

The IGroup<TEntity> interface provides efficient ways to access and observe entities:

  • Optimized Iteration: Use GetEntities(out var buffer) which returns an ArrayDisposable to minimize allocations.
  • Single Entity: Use GetSingleEntity() when you expect only one entity to match the matcher (throws if more than one exists).
  • Events: Subscribe to OnEntityAdded, OnEntityRemoved, or OnEntityUpdated to react to group changes.
  • Count: Get the current number of entities in the group via Count.

8. Integration in Unity

Initialize and update your systems in a MonoBehaviour. Use LateUpdate for Cleanup to ensure it runs after all update logic.

public class GameController : MonoBehaviour
{
    private Systems _systems;
    private CancellationTokenSource _cts;

    async void Start()
    {
        _cts = new CancellationTokenSource();
        var contexts = Contexts.SharedInstance;
        _systems = new GameSystems(contexts);

        // Run async initialization
        await _systems.InitializeAsync(_cts.Token);

        // Run regular initialization
        _systems.Initialize();
    }

    void Update()
    {
        _systems.Update();
    }

    void FixedUpdate()
    {
        _systems.FixedUpdate();
    }

    void LateUpdate()
    {
        _systems.Cleanup();
    }

    void OnDestroy()
    {
        _cts?.Cancel();
        _systems.TearDown();
    }
}

9. Data Transfer API

The Data Transfer API allows you to easily copy data between entities and POCO data objects. This is useful for serialization, networking, or saving/loading game state.

To enable this feature, add the [GenerateDataTransfer] attribute to your component.

[Game]
[GenerateDataTransfer]
public struct PlayerDataComponent : IComponent
{
    public string Name;
    public int Level;
    public List<string> Inventory;
}

This generates an interface IGamePlayerDataData and a static class GamePlayerDataDataTransfer with the following methods. The interface properties follow the naming convention {ContextName}{ComponentName}{MemberName}.

  • CopyTo: Copies data from an entity's component to a data object.
public class MyPlayerData : IGamePlayerDataData
{
    public string GamePlayerDataName { get; set; }
    public int GamePlayerDataLevel { get; set; }
    public List<string> GamePlayerDataInventory { get; set; }
}

// ...

var data = new MyPlayerData();
GamePlayerDataDataTransfer.CopyTo(entity, data);
  • WriteTo: Copies data from a data object to an entity's component.
var data = new MyPlayerData { GamePlayerDataName = "Hero", GamePlayerDataLevel = 5 };
GamePlayerDataDataTransfer.WriteTo(entity, data);
  • Clear: Clears the data object (sets fields to default or clears collections).
GamePlayerDataDataTransfer.Clear(data);

Nullable Support: You can enable nullable support by setting Nullable = true in the attribute.

[Game]
[GenerateDataTransfer(Nullable = true)]
public struct OptionalDataComponent : IComponent
{
    public int Value;
    public List<int> Items;
}
  • Behavior with Nullable:
    • WriteTo:
      • If all fields in the data object are null (e.g. GameOptionalDataValue is null), the component is **removed ** from the entity.
      • If any field is null, it is either skipped (if value type) or set to null (if reference type) on the component.
    • CopyTo:
      • If a component field is null (for collections), the data object's field is set to null.
    • Add:
      • When adding a new component, null values in the data object result in default values or null (for collections) in the component.

Skip Empty Support: You can enable skip empty support by setting SkipEmpty = true in the attribute.

[Game]
[GenerateDataTransfer(SkipEmpty = true)]
public struct OptionalDataComponent : IComponent
{
    public int Value;
    public List<int> Items;
}
  • Behavior with SkipEmpty:
    • WriteTo:
      • If all fields in the data object are default (e.g. 0 for int, null or empty for collections), the component is not added to the entity.

Ignore Data Transfer: You can exclude specific members from data transfer using the [IgnorDataTransfer] attribute.

[Game]
[GenerateDataTransfer]
public struct PlayerDataComponent : IComponent
{
    public string Name;

    [IgnorDataTransfer]
    public int RuntimeId; // This field will be ignored by the Data Transfer API
}

Hooks: You can execute custom logic before or after data is written to the component using [BeforeDataTransfer] and [AfterDataTransfer] attributes on methods within the component.

[Game]
[GenerateDataTransfer]
public struct PlayerDataComponent : IComponent
{
    public string Name;

    [BeforeDataTransfer]
    public void OnBeforeTransfer()
    {
        // Logic to execute before data is copied from the component (CopyTo)
    }

    [AfterDataTransfer]
    public void OnAfterTransfer()
    {
        // Logic to execute after data is written to the component (WriteTo)
    }
}

10. Event System

You can generate event systems and API to react to component changes by adding the [Event] attribute to a component.

[Game, Event(EventTarget.Self)]
public struct PositionComponent : IComponent
{
    public float x;
    public float y;
}

This generates methods on the entity to subscribe to changes.

  • Subscribe: The Subscribe method adds a listener and returns an IDisposable that removes the listener when disposed. This is the recommended way to handle events.

    You can also control whether the delegate is invoked immediately upon subscription using the invokeOnSubscribe parameter (default: true for Added/Updated, false for Removed).

public class PositionController : MonoBehaviour
{
    private System.IDisposable _listener;

    void Start()
    {
        var entity = Contexts.SharedInstance.Game.CreateEntity();

        // Subscribe to the Position event.
        // By default, this will invoke OnPosition immediately with the current component values.
        _listener = entity.SubscribePosition(OnPosition);
    }

    void OnPosition(GameEntity entity, float x, float y)
    {
        transform.position = new Vector3(x, y);
    }

    void OnDestroy()
    {
        // Unsubscribe by disposing the listener
        _listener?.Dispose();
    }
}
  • Add/Remove Listener: You can also manually add and remove listeners using Add[EventName]Listener and Remove[EventName]Listener.
entity.AddPositionListener(OnPosition);
entity.RemovePositionListener(OnPosition);

11. Common Pitfalls and Fixes

  • Modifying Components During Iteration: Wrong: Modifying a component reference directly doesn't notify observers.
using var _ = _group.GetEntities(out var buffer);
for (var i = 0; i < buffer.Length; i++)
{
    var e = buffer[i];
    e.Position.x += 1; // Observers NOT notified
}

Fix: Use AddXyz. AddXyz will replace the component if it already exists.

using var _ = _group.GetEntities(out var buffer);
for (var i = 0; i < buffer.Length; i++)
{
    var e = buffer[i];
    var pos = e.Position;
    e.AddPosition(pos.x + 1, pos.y);
}
  • Missing Cleanup/TearDown: Ensure _systems.Cleanup() is called (usually in LateUpdate) and _systems.TearDown() is called (in OnDestroy) to process entity destruction, release memory, and prevent leaks.

  • Reactive System Filtering: Always implement a strict Filter in ReactiveSystem (as shown in the example in Section 6) to ensure entities still match the required state when Execute is called.

  • Event Listener Cleanup: Always remove listeners in OnDestroy to prevent memory leaks. The easiest way is to use the Subscribe method and dispose the returned IDisposable.

void OnDestroy()
{
    _listener?.Dispose();
}

If using manual Add/Remove, ensure you remove the delegate.

void OnDestroy()
{
    if (_entity != null && _entity.IsAlive)
    {
        _entity.RemovePositionListener(OnPosition);
    }
}