Entitas-Lite is a No-CodeGenerator branch of Entitas.
It's suitable for large projects and teams prefer ECS without code-generation.
Some core of Entitas was rewritten to provide easy interface for hand-coding.
Download and extract "Build/deploy/Entitas-Lite" folder into your Unity Project/Assets/.
Just write your own Components and Systems, then a GameController class for game entry.
No CodeGenerator required! Have fun!
[Default]
public class PositionComponent : IComponent
{
public int x;
public int y;
}
// if no context declaration, it comes into Default context
public class VelocityComponent : IComponent
{
public int x;
public int y;
// don't be afraid of writing helper accessor
public void SetValue(int nx, int ny)
{
x = nx;
y = ny;
}
}
// if no feature-set declaration, it comes into UnnamedFeature
public class MovementSystem : IExecuteSystem
{
public void Execute()
{
// new API for getting group with all matched entities from context
var entities = Context<Default>.AllOf<PositionComponent, VelocityComponent>().GetEntities();
foreach (var e in entities)
{
var vel = e.Get<VelocityComponent>();
var pos = e.Modify<PositionComponent>(); // new API for trigger Monitor/ReactiveSystem
pos.x += vel.x;
pos.y += vel.y;
}
}
}
// Sample view just display Entity's Position if changed
public class ViewSystem : ReactiveSystem
{
public ViewSystem()
{
// new API, add monitor that watch Position changed and call Process
monitors += Context<Default>.AllOf<PositionComponent>().OnAdded(Process);
}
protected void Process(List<Entity> entities)
{
foreach (var e in entities)
{
var pos = e.Get<PositionComponent>();
Debug.Log("Entity" + e.creationIndex + ": x=" + pos.x + " y=" + pos.y);
}
}
}
public class GameController : MonoBehaviour
{
private Systems _feature;
public void Start()
{
var contexts = Contexts.sharedInstance;
#if UNITY_EDITOR
ContextObserverHelper.ObserveAll(contexts);
#endif
// create random entity
var rand = new System.Random();
var context = Contexts.Default;
var e = context.CreateEntity();
e.Add<PositionComponent>();
e.Add<VelocityComponent>().SetValue(rand.Next()%10, rand.Next()%10);
#if UNITY_EDITOR
_feature = FeatureObserverHelper.CreateFeature(null);
#else
// init systems, auto collect matched systems, no manual Systems.Add(ISystem) required
_feature = new Feature(null);
#endif
_feature.Initialize();
}
public void Update()
{
_feature.Execute();
_feature.Cleanup();
}
}
-
Entity, Context, Contexts, Matcher, Feature are now just one class.
No more generated GameEntity, GameContext, GameMatcher, InputEntity ...
However, another generic helpers were added for easy hand-coding. -
Feature: Auto add matched Systems, no manual Systems.Add(ISystem) required
-
Scoping: Subclassing from ContextAttribute for Components, and FeatureAttribute for Systems.
public class Game : ContextAttribute {}
public class MyFeature : FeatureAttribute { public MyFeature(int prior = 0) :base(prior) {} }
[Game] public class MyComponent : IComponent {}
[MyFeature] public class MySystem : IExecuteSystem {}
- Entity: Generic API for Add/Replace/Get/RemoveComponents. Forget component-index!
e.Add<PositionComponent>(); // equals to e.AddComponent<PositionComponent>();
e.Remove<PositionComponent>(); // equals to e.RemoveComponent<PositionComponent>();
var vel = e.Get<VelocityComponent>();
var pos = e.Modify<PositionComponent>(); // get component for modification, will trigger Monitor/ReactiveSystem
- Context: Auto register all components with the same ContextAttribute.
var e1 = context.GetEntity(100); // get entity with creationIndex==100
public class UserComponent : IUniqueComponent {} // mark this component unique in context
var e2 = context.GetSingleEntity<UserComponent>(); // whill raise exception if not unique
var user = context.GetUnique<UserComponent>(); // directly fetch unique component
var user2 = context.ModifyUnique<UserComponent>();
var user3 = context.AddUnique<UserComponent>();
- Contexts: Auto register all context. Add name-Context lookup and a defaultContext for notitled Components.
Context c = Contexts.Default; // = Contexts.sharedInstance.defaultContext;
Context game = Contexts.Get("Game"); // = Contexts.sharedInstance.GetContext("Game");
Context input = Contexts.Get<Input>(); // = Contexts.sharedInstance.GetContext<Input>();
- Matcher: Generic templates for easy Matcher creation
var matcher = Match<Game>.AllOf<PositionComponent, VelocityComponent>();
var group = Context<Game>.AllOf<PositionComponent, VelocityComponent>(); // easy combin context.GetGroup(matcher)
- Monitor/Collector: Monitor combins collector/filter/processor for Reactive-programming
var monitor = Context<Default>.AllOf<PositionComponent>() // group => monitor
.OnAdded(entities => { foreach (var e in entities) { /* do something */ }})
.where(e => e.Has<ViewComponent>); // filter
monitor.Execute(); // in each update
- ReactiveSystem: brand-new usage by Monitor, allow multi monitors
public class ViewSystem : ReactiveSystem {
public ViewSystem() {
// use += to add more monitors to Execute
monitors += Context<Default>.AllOf<PositionComponent>().OnAdded(this.Process);
}
}
- new ExecuteSystem: simplify IExecuteSystem for subclass
constructor ExecuteSystem(Context context, IMatcher<Entity> matcher) // requre both context and matcher
abstract void Execute(Entity entity) // override this for executing on each matched entity
- Bug fixes
- Performance tweak
- Clean document
- More examples