Using Entitas in generic way, instead of generators
See original GitHub issueSorry that’s off topic, what is the advantage of using generators in particular, instead, for example, generic methods or barehands? Why there no any of tutorial, how to use entitas without generators notimetoexplain - use generators? Lack of time to make some, I think.
Today I spend whole day trying to work with Entitas and Generators, but it always broke all my code when I remove some parameters from components, or rename it. And that is annoying. 1 minute to change component, 30 minutes to generate code somehow… And there only TWO COMPONENTS. >___<. How it work when there ten of them, thousands, how it use with other teammates? Yeah, I know about compilers, reflections, mono, roslyn and etc, but it is so hard to work with(especially on mac), I did not even try to use them because of complexity.
I start thinking about two solution, first one, copy components in their generated environment (in different namespace, or in class), and use in system only components from there. It prevents of broke code and interrupt compiling process (in theory). Because it very hard to implement with one evening with my knowledge about entitas, I found second solution, that I’m prefer: Get some generated classes, and make them in generic way. It took about three hours and I make some working solution to start working with Entitas without generator.
Here are a my three hours code, maybe need to add some methods… Context:
namespace Generic {
// IInitializable is Zenject interface
public class Context<TEntity> : Entitas.Context<TEntity>, IInitializable where TEntity : class, IEntity, new() {
public readonly LookupTable Table;
public readonly Matcher<TEntity> Matcher;
public Context(string name, LookupTable table, Matcher<TEntity> matcher)
: base(table.Size, 0, new ContextInfo(name, table.Names, table.Components)) {
Table = table;
Matcher = matcher;
}
public void Initialize() {
#if(!ENTITAS_DISABLE_VISUAL_DEBUGGING && UNITY_EDITOR)
if (!UnityEngine.Application.isPlaying)
return;
var observer = new ContextObserver(this);
UnityEngine.Object.DontDestroyOnLoad(observer.gameObject);
#endif
}
public TEntity Get<T>() where T : IComponent {
return GetGroup(Matcher.For<T>()).GetSingleEntity();
}
public bool Has<T>() where T : IComponent {
return Get<T>() != null;
}
public TEntity Set<T>(T component) where T : IComponent {
if (Has<T>())
throw new Exception("Something went wrong with setting " + typeof(T) + " component.");
TEntity entity = CreateEntity();
entity.AddComponent(Table.IndexOf<T>(), component);
return entity;
}
public TEntity Replace<T>(T component) where T : IComponent {
TEntity entity = Get<T>();
if (entity == null)
return Set(component);
entity.ReplaceComponent(Table.IndexOf<T>(), component);
return entity;
}
public void Remove<T>() where T : IComponent {
DestroyEntity(Get<T>());
}
}
}
LookupTable:
namespace Generic {
public class LookupTable {
public Type[] Components { get; private set; }
public string[] Names { get; private set; }
public int Size {
get { return Components.Length; }
}
public LookupTable(Type[] components) {
Components = components;
Names = new string[components.Length];
for (int i = 0; i < components.Length; i++) {
Names[i] = components[i].ToString();
}
}
public int IndexOf<T>() where T : IComponent {
// Todo assert if -1
return Array.IndexOf(Components, typeof(T));
}
}
}
Matcher
namespace Generic {
public class Matcher<TEntity> where TEntity : class, IEntity, new() {
private readonly LookupTable _table;
private readonly Dictionary<Type, IMatcher<TEntity>> _cache = new Dictionary<Type, IMatcher<TEntity>>();
public Matcher(LookupTable table) {
_table = table;
}
public IMatcher<TEntity> For<T>() where T : IComponent {
Type type = typeof(T);
if (_cache.ContainsKey(type))
return _cache[type];
var matcher = (Entitas.Matcher<TEntity>) Entitas.Matcher<TEntity>.AllOf(_table.IndexOf<T>());
matcher.componentNames = _table.Names;
_cache[type] = matcher;
return _cache[type];
}
}
}
Entity
namespace Generic {
public class Entity : Entitas.Entity {}
}
How to use
- Create a context class:
namespace Generic {
public class GameContext : Context<Entity> {
public GameContext(LookupTable table) : base("Game", table, new Matcher<Entity>(table)) {
}
}
}
- Install it with zenject, or just make by your hands and save in singletone (that’s bad)
namespace Installers {
public class ContextInstaller : MonoInstaller {
public override void InstallBindings() {
GameContext();
}
public void GameContext() {
Type[] components = {
typeof(DebugMessageComponent),
typeof(PositionComponent)
};
Container.Bind<LookupTable>().FromInstance(new LookupTable(components)).WhenInjectedInto<GameContext>();
Container.BindInterfacesAndSelfTo<GameContext>().AsSingle();
}
}
public class ConextInstallerNoDependency {
private static GameContext _game;
public static GameContext Game {
get {
if (_game == null) {
Type[] components = {
typeof(DebugMessageComponent),
typeof(PositionComponent)
};
_game = new GameContext(new LookupTable(components));
}
return _game;
}
}
}
}
- Use it
namespace Systems {
public class DebugMessageSystem : ReactiveSystem<Entity>
{
private readonly GameContext _context;
public DebugMessageSystem(GameContext context) : base(context) {
_context = context;
}
protected override Collector<Entity> GetTrigger(IContext<Entity> context) {
return context.CreateCollector((context as GameContext).Matcher.For<DebugMessageComponent>());
}
protected override bool Filter(Entity entity) {
return entity.HasComponent(_context.Table.IndexOf<DebugMessageComponent>());
}
protected override void Execute(List<Entity> entities)
{
foreach (var e in entities) {
DebugMessageComponent component = (DebugMessageComponent)e.GetComponent(_context.Table.IndexOf<DebugMessageComponent>());
// This is my bicycle instead of Debug.Log
// D.Log(component.Message);
Debug.Log(component.Message);
}
}
}
public class HelloWorldSystem : IInitializeSystem, IExecuteSystem {
private readonly GameContext _context;
// I do not love to new objects in update, that's why I cached it, but this is not necessary
private DebugMessageComponent component;
public HelloWorldSystem(GameContext context) {
_context = context;
}
public void Initialize() {
component = new DebugMessageComponent {Message = "Test" + Random.Range(0, 1000)};
}
public void Execute() {
component.Message = "Message " + Random.Range(0, 1000);
_context.Replace(component);
}
}
public class Hello : Feature {
public Hello(GameContext context) : base("HelloFeature") {
Add(new DebugMessageSystem(context));
Add(new HelloWorldSystem(context));
}
}
// This class may be MonoBehaviour, but I use Zenject and prefer simple classes
public class GameController : ITickable, IInitializable, IDisposable {
private readonly GameContext _context;
private Entitas.Systems _systems;
[Inject] // Zenject attribute for injection
public GameController(GameContext context) {
_context = context;
}
// No injection
public GameController() {
_context = ConextInstallerNoDependency.Game;
}
// OnEnable
public void Initialize() {
_systems = new Feature("Systems").Add(new Hello(_context));
_systems.Initialize();
}
// Update
public void Tick() {
_systems.Execute();
_systems.Cleanup();
}
// OnDisable
public void Dispose() { }
}
}
Issue Analytics
- State:
- Created 7 years ago
- Comments:22 (5 by maintainers)

Top Related StackOverflow Question
A multiplayer backend doesn’t do anything involving graphics and it’s a question of real money if you need 1000 servers or if you can do the same with 100 servers with an optimized framework
Renaming is quite easy. Use your IDE for that job (Visual Studio or something else). Deleting and Adding Fields can be indeed a problem. But it don’t take hours as you described before. The real problem (at the moment) are blueprints. If you delete a field of a component wich you use in a blueprint, you have to recreate the hole blueprint… (because of the binary serialization). Thats my real problem by the way.
I had my difficulties with the codegen at beginning, too. But now i feel comfortable with it. Sure, there could be some improvements. But speed is very important! I build a game with thousands of “bots” and still I have 75 FPS. That’s inconceivable with the “intuitive” Unity way (Monobehaviour Scripts attached to each bot)
The best way to learn and use entitas is to take a look at the examples and videos. And first think about components and then act! And finally, the simplest solution is the right solution 😉