unity-ECS架构Entitas记录

关于 ECS框架 Entitas 的相关记录


前篇


流程图

自己用 visio 画的比较粗糙的框架流程, 方便了解整体框架的流程, 下载地址: ecs-entitas.vsd


Attributes

官网详解:https://github.com/sschmid/Entitas-CSharp/wiki/Attributes

Unique

在 context 中只存在一份实例,生成代码会在 Context 中生成几个属性

  • Context.{ComponentName}Entity
  • Context.{ComponentName}.{ComponentProperty} (non-flag components)
  • Context.Set{ComponentName}({ComponentProperty}) (non-flag components)
  • Context.Replace{ComponentName}({ComponentProperty}) (non-flag components)
  • Context.Remove{ComponentName}() (non-flag components)
  • Context.has{ComponentName}() (non-flag components)

Define your component:

1
2
3
4
5
6
7
8
9
using Entitas;
using Entitas.CodeGeneration.Attributes;

[Core] // Context name
[Unique]
public class UserComponent : IComponent {
public string name;
public int age;
}

You now have the following properties and methods available:

1
2
3
4
5
6
7
8
var context = Contexts.core;
var e = context.userEntity;
var name = context.user.name;
var has = context.hasUser;

context.SetUser("John", 42);
context.ReplaceUser("Max", 24);
context.RemoveUser();

初始化一个 unique 实例, 会生成一个对应的 context 的一个字段 isXxx,true 为生成, false 销毁

1
2
3
readonly InputContext _context;
_context.isLeftMouse = true; // 生成实例
InputEntity _leftMouseEntity = _context.leftMouseEntity; // get 实例

PrimaryEntityIndex

EntityIndex

通过具体的 name get 到 对应环境下有用该组件的 所有entity 的值

1
2
3
4
5
6
[Game]
public class FactionComponent : IComponent {
[EntityIndex]
public string name;
}
// Can now use Game.GetEntitiesWithFaction("Player");

CustomComponentName

生成 PositionComponent, VelocityComponent

1
2
3
4
5
[Context, CustomComponentName("Position", "Velocity")]
public struct IntVector2 {
public int x;
public int y;
}

DontGenerate

禁止生成代码

1
2
3
4
5
6
using Entitas;
using Entitas.CodeGeneration;

[DontGenerate]
public class FutureFeatureComponent : IComponent {
}

添加组件 Component

假设所有的组件都定义在 GameComponents.cs 文件中

  1. GameComponents.cs 中增加一个组件 SayComponent ,并增加属性

    1
    2
    3
    4
    5
    6
    //指定entity的标签为 Game, 也就是可以通过 GameEntity 访问到, 生成代码在 GameSayComponent.cs
    [Game]
    public class SayComponent : IComponent { //命名必须为 XxxComponent
    public string msg;
    public int age;
    }
  2. 然后生成代码 ctrl + shift + g , 可以看下生成后的代码 GameSayComponent.cs 都有些神马东东

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 可以看到是通过 partial 关键字去扩展 GameEntity类,是其拥有对应组件的一些方法
public partial class GameEntity {

// 可以通过 xxx(say) 这个字段访问到 SayComponent,就可以访问到里面属性
public SayComponent say { get { return (SayComponent)GetComponent(GameComponentsLookup.Say); } }
public bool hasSay { get { return HasComponent(GameComponentsLookup.Say); } }

// 通过 AddXxx(AddSay) 就可以往 GameEntity对象 增加 SayComponent组件,并赋予所有属性值
public void AddSay(string newMsg, int newAge) {
var index = GameComponentsLookup.Say;
var component = CreateComponent<SayComponent>(index);
component.msg = newMsg;
component.age = newAge;
AddComponent(index, component);
}

// 通过 ReplaceXxx(ReplaceSay) 替换 SayComponent 组件,也就是重新创建一个新的组件
public void ReplaceSay(string newMsg, int newAge) {
var index = GameComponentsLookup.Say;
var component = CreateComponent<SayComponent>(index);
component.msg = newMsg;
component.age = newAge;
ReplaceComponent(index, component);
}

// 通过 RemoveXxx(RemoveSay) 移除 GameEntity对象 身上的 SayComponent组件
public void RemoveSay() {
RemoveComponent(GameComponentsLookup.Say);
}
}
  1. 生成一个 entity 后,添加组件 SayComponent
1
2
GameEntity mover = _gameContext.CreateEntity();
mover.AddSay("hello yangx", 123); // 两个属性就必须两个参数,顺序一直
  1. 通过entity访问组件 SayComponent 的属性

    1
    2
    3
    foreach (GameEntity e in entities) {
    Debug.LogFormat("--- SaySystem.Execute, say:{0}, age:{1}", e.say.msg, e.say.age);
    }

获取指定组 Group

  • get 到 拥有 SpriteComponent组件 的所有entity 的组

    1
    2
    3
    4
    5
    6
    7
    8
    public class MiddleMouseKeyChangeSpriteSystem : IExecuteSystem
    {
    readonly IGroup<GameEntity> _sprites;
    // 获取所有拥有Sprite的组
    public MiddleMouseKeyChangeSpriteSystem(Contexts contexts)
    {
    _sprites = contexts.game.GetGroup(GameMatcher.Sprite);
    }

添加系统 System

官网解释:https://github.com/sschmid/Entitas-CSharp/wiki/Systems

There are 4 different types of Systems:

  • IInitializeSystem: Executes once (system.Initialize())
  • IExecuteSystem: Executes every frame (system.Execute())
  • ICleanupSystem: Executes every frame after the other systems are finished (system.Cleanup())
  • ReactiveSystem: Executes when the observed group changed (system.Execute(Entity[]))

无论是那种系统,都需要添加进 Entitas 框架内,才能生效

1
2
3
4
5
6
7
8
public class AllSystems : Feature
{
public AllSystems(Contexts ctxs) : base("All Systems")
{
Add(new EmitInputSystem(ctxs));
Add(new CreateMoverSystem(ctxs));
}
}

反应系统 ReactiveSystem

ReactiveSystem:反应系统,只有在所监听的组件 _collector 有变化的时候,才会执行 Execute 方法,形参 entities 是发生变化的列表

  • 需要重写一些方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class AddViewSystem : ReactiveSystem<GameEntity> {
    // 创建Sprite的过滤器,指定 监听某种类型的 entity 的变化
    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context) {
    return context.CreateCollector(GameMatcher.Sprite);
    // return context.CreateCollector(InputMatcher.AllOf(InputMatcher.RightMouse, InputMatcher.MouseDown)); // 监听多种Entity
    }

    // 第二次过滤,没有View,没有关联上GameObject的情况
    protected override bool Filter(GameEntity entity) {
    return entity.hasSprite && !entity.hasView;
    }

    // 创建一个View的GameObject,并进行关联
    protected override void Execute(List<GameEntity> entities) {
    foreach (GameEntity e in entities) {
    Debug.Log("--- AddViewSystem.Execute");
    GameObject go = new GameObject("Game View");
    go.transform.SetParent(_viewContainer, false);
    e.AddView(go); // Entity关联GameObject
    go.Link(e, _context); // GameObject关联Entity
    }
    }
    • Execute : 官网的解释是 Execute() is where the bulk of your game logic resides ,也就是处理变化逻辑的地方

混合反应系统 MultiReactiveSystem

解释:To react to changes of entities from multiple contexts you will need to use multi-reactive system

教程:https://github.com/sschmid/Entitas-CSharp/wiki/MultiReactiveSystem-Tutorial

普通update系统

  • 实现接口 IExecuteSystem , 接受系统的 update

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class MiddleMouseKeyChangeSpriteSystem : IExecuteSystem
    {
    readonly IGroup<GameEntity> _sprites;

    // 获取所有拥有Sprite的组
    public MiddleMouseKeyChangeSpriteSystem(Contexts contexts) {
    _sprites = contexts.game.GetGroup(GameMatcher.Sprite);
    }

    // 需要实现的接口
    public void Execute() {
    ...
    }
    }