
var context = contexts.game;
var movables = context.GetGroup(GameMatcher.Movable);
var count = movables.count; // count is 0, the group is empty
var entity1 = context.CreateEntity();
entity1.isMovable = true;
var entity2 = context.CreateEntity();
entity2.IsMovable = true;
count = movables.count; // count is 2, the group contains the entity1 and entity2
// GetEntities() always provides an up to date list
var movableEntities = movables.GetEntities();
foreach (var e in movableEntities) {
// Do sth
}
entity1.Destroy();
entity2.Destroy();
count = movables.count; // count is 0, the group is empty
var matcher = GameMatcher.Movable;
GameMatcher.AllOf(GameMatcher.Movable, GameMatcher.Position);
GameMatcher.AnyOf(GameMatcher.Move, GameMatcher.Position);
GameMatcher
.AllOf(GameMatcher.Position)
.AnyOf(GameMatcher.Health, GameMatcher.Interactive)
.NoneOf(GameMatcher.Animating);
- IInitializeSystem: 只执行一次 (system.Initialize())
- IExecuteSystem: 每帧执行 (system.Execute())
- ICleanupSystem: 在其他系统完成后每一帧执行(system.Cleanup())
- ReactiveSystem: 当观察的group改变时执行(system.Execute(Entity[]))
public class MoveSystem : IExecuteSystem {
public void Execute() {
// Do sth
}
}
public class CreateLevelSystem : IInitializeSystem {
public void Initialize() {
// Do sth
}
}
public class RenderPositionSystem: ReactiveSystem<GameEntity> {
public RenderPositionSystem(Contexts contexts) : base(contexts.Game) {
}
protected override Collector<GameEntity> GetTrigger(IContext<GameEntity> context) {
return context.CreateCollector(GameMatcher.Position);
}
protected override bool Filter(GameEntity entity) {
// check for required components (here it is position and view)
return entity.hasPosition && entity.hasView;
}
protected override void Execute(List<GameEntity> entities) {
foreach (var e in entities) {
// do stuff to the matched entities
e.view.gameObject.transform.position = e.position.position;
}
}
}
var systems = new Systems(contexts)
.Add(new CreateLevelSystem(contexts))
.Add(new UpdateBoardSystem(contexts))
.Add(new MoveSystem(contexts))
.Add(new RenderPositionSystem(contexts));
// Call once on start
systems.Initialize();
// Call every frame
systems.Execute();
using Entitas; public class MyInitSystem : IInitializeSystem { public void Initialize() { // Initialization code here } }
using Entitas; public class MyExecSystem : IExecuteSystem { public void Execute() { // per-frame code goes here } }
public class MyCleanupSystem : ICleanupSystem { public void Cleanup() { // cleanup code here // runs after every execute and reactive system has completed } }
using System.Collections.Generic; using Entitas; public class MyReactiveSystem : ReactiveSystem<MyContextEntity> { public MyReactiveSystem (Contexts contexts) : base(contexts.MyContext) { // pass the context of interest to the base constructor } protected override ICollector<MyContextEntity> GetTrigger(IContext<MyContextEntity> context) { // specify which component you are reacting to // return context.CreateCollector(MyContextMatcher.MyComponent); // you can also specify which type of event you need to react to // return context.CreateCollector(MyContextMatcher.MyComponent.Added()); // the default // return context.CreateCollector(MyContextMatcher.MyComponent.Removed()); // return context.CreateCollector(MyContextMatcher.MyComponent.AddedOrRemoved()); // combine matchers with AnyOf and AllOf // return context.CreateCollector(LevelMatcher.AnyOf(MyContextMatcher.Component1, MyContextMatcher.Component2)); // use multiple matchers // return context.CreateCollector(LevelMatcher.MyContextMatcher, MyContextMatcher.Component2.Removed()); // or any combination of all the above // return context.CreateCollector(LevelMatcher.AnyOf(MyContextMatcher.Component1, MyContextMatcher.Component2), // LevelMatcher.Component3.Removed(), // LevelMatcher.AllOf(MyContextMatcher.C4, MyContextMatcher.C5).Added()); } protected override bool Filter(MyContextEntity entity) { // check for required components } protected override void Execute(List<MyContextEntity> entities) { foreach (var e in entities) { // do stuff to the matched entities } } }
public interface PositionViewEntity : IEntity, IPosition, IView {} public partial class EnemyEntity : PositionViewEntity {} public partial class ProjectileEntity : PositionViewEntity {} public class ViewSystem : MultiReactiveSystem<PositionViewEntity, Contexts> { public ViewSystem(Contexts contexts) : base(contexts) {} protected override ICollector[] GetTrigger(Contexts contexts) { return new ICollector[] { contexts.Enemy.CreateCollector(EnemyMatcher.Position), contexts.Projectile.CreateCollector(ProjectileMatcher.Position) }; } protected override bool Filter(PositionViewEntityentity) { return entity.hasView && entity.hasPosition; } protected override void Execute(List<PositionViewEntity> entities) { foreach(var e in entities) { e.View.transform.position = e.Position.value; } } }
using Entitas; public class InputSystems : Feature { public InputSystems(Contexts contexts) : base("Input Systems") { // order is respected Add(new EmitInputSystem(contexts)); Add(new ProcessInputSystem(contexts)); } }
Systems createSystems(Contexts contexts) { // order is respected return new Feature("Systems") // Input executes first .Add(new InputSystems(contexts)) // Update .Add(new GameBoardSystems(contexts)) .Add(new GameStateSystems(contexts)) // Render executes after game logic .Add(new ViewSystems(contexts)) // Destroy executes last .Add(new DestroySystem(contexts)); }
- [Context]: 可以使用此特性使组件仅在指定的context中可用;例如 [MyContextName], [Enemies], [UI]....提高内存占用。它还可以创建组件。
- [Unique]: 代码生成器将提供额外的方法,以确保最多存在一个具有该组件的实体。
- [FlagPrefix]:仅可用于支持标记组件的自定义前缀。
- [PrimaryEntityIndex]: 可用于将实体限制为唯一的组件值。
- [EntityIndex]: 可用于搜索具有组件值的实体。
- [CustomComponentName]: 为一个类或接口生成具有不同名称的多个组件。
- [DontGenerate]: 代码生成器不会使用此属性处理组件。
- [Cleanup]: 代码生成器将生成删除组件或销毁实体的系统。
- 遵循这个框架的规则去写代码,代码结构清晰。
- ECS这种模式,耦合度就很低,所有的游戏物体都是组件的组合而已,可扩展性强,通过合理组合component就能配置出一个新的游戏物体。
- 很方便的管理所有实体状态,entitas提供了类似状态机的功能,当感兴趣的某个属性发生变化时,能在System中很方便的做出响应,不管什么状态,都能很方便的做出对应处理。
- unity本身的开发模式就类似ECS,unity2018更是推出了最新的ECS框架,entitas很符合这种开发模式
- entitas自开源以来,一直在更新维护,并受到了unity官方的认可,在unite大会上都有提到。所以,这个框架还是很靠谱的。
- 国内资料少,上手难度高。国内用这个框架开发的特别少,遇到问题需要自己爬坑。
- 不适合小项目。小项目用entitas反而麻烦
- entitas更新太快,官方wiki文档更新没有跟上,比如,我在看官方Demo-MatchOne的时候,有个Event的Attribute, wiki上暂时还没有这个的
- 代码热更方面是个问题, entitas基本对unity开发定了一套完整的规则,特别是有Code Generate,如果项目发布后想要更新加入新的代码会很麻烦,官方对此也没有说明,目前好像也没有人分享在entitas中加入lua热更的功能
所有评论(0)