下载与安装

Release Entitas 1.13.0 · sschmid/Entitas-CSharp · GitHub 下载最新的ECS安装包

解压到Asset目录

image-20220425184918682

随便打开一个脚本,会自动编译Unity项目

然后又如下设置

首先点击Auto Import

然后把Project Path改为自己的项目名称,一般来说就是这个

image-20220425185429211

现在可以点击生成了,会根据Contexts的内容自动生成上下文

默认就两个Game和Input

image-20230415184220070

  1. Attribute:一个标签

    比如有GameAttribute之后,我们就可以在代码里这么写

    这是一个标准组件的写法

    [Game]
    public class PlayerName : IComponent
    {
        public string name;
    }
    
  2. ComponentsLookUp:会存放对应上下文里的所有组件,这个类是用来作为查找工具的

    可以看看源码

    public static class GameComponentsLookup 
    {
        public const int TotalComponents = 0;
        public static readonly string[] componentNames = {};
        public static readonly System.Type[] componentTypes = {};
    }
    

    目前啥也没有,组件数量为0,因为我们确实没有写任何组件

  3. Context:上下文,是一个分布类

  4. Entity:游戏实体,任何运行在游戏里的数据都可以算作是一个实体,这是一个分布类

  5. Matcher:匹配器,用来筛选Entity的,这是一个分布类

Hello World

创建一个Log组件

[Game]
public class LogComponent : IComponent
{
    public string msg;
}

这是一个标准组件写法,组件在ECS里,只有信息,而没有逻辑

同时标签[Game]说明,这个组件位于GameContext下

注意写完后需要Generate,不然不会自动生成代码

创建一个系统

ECS有很多种系统,这里选择继承ReactiveSystem,代表反应系统,意思就是当你关心的实体产生变换时,这个系统会有反应

哪些实体是我们关心的呢?

可以发现这是一个泛型类,而泛型填入我们所需要关心的实体类型,这里关心的是GameEntity

public class LogSystem : ReactiveSystem<GameEntity>
{
    public LogSystem(IContext<GameEntity> context) : base(context)
    {
    }

    public LogSystem(ICollector<GameEntity> collector) : base(collector)
    {
    }

    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
    {
        throw new System.NotImplementedException();
    }

    protected override bool Filter(GameEntity entity)
    {
        throw new System.NotImplementedException();
    }

    protected override void Execute(List<GameEntity> entities)
    {
        throw new System.NotImplementedException();
    }
}

最上面是两个构造方法,我们不关心,主要是下面三个方法

  1. GetTrigger:可以认为是初步筛选,因为Game上下文内有非常多种类的GameEntity,我们这个系统不可能关心所有的实体,这一步就是收集我们关心的实体

    public class LogSystem : ReactiveSystem<GameEntity>
    {
        protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
        {
            return context.CreateCollector(GameMatcher.Log);
        }
    }
    

    注意,必须生成过代码,才有GameMatcher.Log

  2. Filter:过滤器,可以认为是再次筛选,因为可能又很多的Log实体,我们还可以对他们再次细分

    我们这里的要求很简单,就只要拥有log组件就可以了

    hasLog方法是自动生成的,等于hasLogComponent,用于判读实体是否拥有Log组件

    public class LogSystem : ReactiveSystem<GameEntity>
    {
        protected override bool Filter(GameEntity entity)
        {
            return entity.hasLog;
        }
    }
    
  3. Execute:真正的执行逻辑,传入一个数组,这个数组里是所有符合上述两步筛选的Entity

    public class LogSystem : ReactiveSystem<GameEntity>
    {
        protected override void Execute(List<GameEntity> entities)
        {
            foreach (var entity in entities)
            {
                Debug.Log(entity.log.msg);
            }
        }
    }
    

初始化系统

在构造方法里获取到Game上下文,并在初始化的时候创建一个实体,并给实体添加一个Log组件

public class InitializeSystem : IInitializeSystem
{
    public GameContext context;
    
    public InitializeSystem(Contexts contexts)
    {
        context = contexts.game;
    }
    
    public void Initialize()
    {
        context.CreateEntity().AddLog("hello world");
    }
}

创建一个功能

一个功能,比如战斗功能,可能有很多系统,比如攻击啊,收集啊,连击啊,等等

所以一个功能会包含多个 系统,我们可以以功能划分系统

当然这个功能我们只添加了一个初始化系统和Log系统

public class LogFeature : Feature
{
    public LogFeature(Contexts contexts): base("LogFeature")
    {
        Add(new InitializeSystem(contexts));
        Add(new LogSystem(contexts));
    }
}

功能入口

public class Test : MonoBehaviour
{
    private Systems systems;
    
    void Start()
    {
        var context = Contexts.sharedInstance;
        systems = new Feature("Systems").Add(new LogFeature(context));
        systems.Initialize();
    }

    void Update()
    {
        systems.Execute();
        systems.Cleanup();
    }
}

systems相当于所有Feature管理器

我们先创建一个总的管理器,然后添加了一个LogFeature

在LogFeature里又有2个子系统

然后调用总系统初始化,这一步会执行所有继承自InitializeSystem的Initialize方法

然后Update里去调用所有系统的Execute和CleanUp

此时运行,可以发现打印出了想要的结果

交互组件

实现如下需求

  1. 右键点击屏幕时,在当前位置生成一张图片
  2. 左键点击屏幕时,所有图片向此位置移动

Input上下文内的组件

[Input, Unique]
public class LeftMouseComponent :IComponent
{
}
 
[Input, Unique]
public class RightMouseComponent :IComponent
{
}
 
[Input]
public class MouseDownComponent :IComponent
{
    public Vector2 position;
}
 
[Input]
public class MousePositionComponent :IComponent
{
    public Vector2 position;
}
 
[Input]
public class MouseUpComponent :IComponent
{
    public Vector2 position;
}
  • [Unique]标签,限定此组件为全局唯一,并且可以通过InputContext.isLeftMouse = true来创建唯一Entity,通过InputContext.LeftMouseEntity访问

InputSystem

public class InputSystem :IInitializeSystem,IExecuteSystem
{
    private InputContext context;
    private InputEntity leftMouseEntity;
    private InputEntity rightMouseEntity;
 
    public InputSystem(Contexts contexts)
    {
        context = contexts.input;
    }
 
    public void Initialize()
    {
        context.isLeftMouse = true;
        context.isRightMouse = true;
        
        leftMouseEntity = context.leftMouseEntity;
        rightMouseEntity = context.rightMouseEntity;
    }
 
    public void Execute()
    {
        Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);

        if (Input.GetMouseButtonDown(0))
            leftMouseEntity.ReplaceMousePosition(mousePosition);

        if (Input.GetMouseButtonDown(1))
            rightMouseEntity.ReplaceMousePosition(mousePosition);
    }
}

Game上下文内的组件

// 当前GameObject所在的位置
[Game]
public sealed class PositionComponent : IComponent
{
    public Vector2 value;
}
 
// 当前GameObject朝向
[Game]
public class DirectionComponent : IComponent
{
    public float value;
}
 
// GameObject显示的图片
[Game]
public class ViewComponent : IComponent
{
    public GameObject gameObject;
}
 
// 显示图片的名称
[Game]
public class SpriteComponent : IComponent
{
    public string name;
}
 
// GameObject是否是Mover的标志
[Game]
public class MoverComponent : IComponent
{
}
 
// 移动的目标
[Game]
public class MoveComponent : IComponent
{
    public Vector2 target;
}
 
// 移动完成标志
[Game]
public class MoveCompleteComponent : IComponent
{
}

CreateMoverSystem

public class CreateMoverSystem : ReactiveSystem<InputEntity>
{
    readonly GameContext gameContext;
    
    public CreateMoverSystem(Contexts contexts) : base(contexts.input)
    {
        gameContext = contexts.game;
    }
 
    // 收集有RightMouse和MouseDown的InputEntity
    protected override ICollector<InputEntity> GetTrigger(IContext<InputEntity> context)
    {
        return context.CreateCollector(InputMatcher.AllOf(InputMatcher.RightMouse, InputMatcher.MousePosition));
    }
 
    // 第二过滤,直接返回true也无所谓
    protected override bool Filter(InputEntity entity)
    {
        return entity.hasMousePosition;
    }
 
    // 执行,每次按下右键,设置Mover标志,添加Position、Direction,并添加表现该Entity的图片名称
    protected override void Execute(List<InputEntity> entities)
    {
        foreach (InputEntity e in entities)
        {
            GameEntity mover = gameContext.CreateEntity();
            mover.isMover = true;
            mover.AddPosition(e.mousePosition.position);
            mover.AddDirection(Random.Range(0, 360));
            mover.AddSprite("Sprite");
        }
    }
}

CommandMoveSystem

public class CommandMoveSystem : ReactiveSystem<InputEntity>
{
    readonly IGroup<GameEntity> movers;
 
    // 获取拥有Mover标志Entity的组
    public CommandMoveSystem(Contexts contexts) : base(contexts.input)
    {
        movers = contexts.game.GetGroup(GameMatcher.AllOf(GameMatcher.Mover));
    }
 
    // 过滤左键点击,和右键点击那个System一样
    protected override ICollector<InputEntity> GetTrigger(IContext<InputEntity> context)
    {
        return context.CreateCollector(InputMatcher.AllOf(InputMatcher.LeftMouse, InputMatcher.MousePosition));
    }
 
    protected override bool Filter(InputEntity entity)
    {
        return entity.hasMousePosition;
    }
 
    // 在Entity上设置移动命令Move
    protected override void Execute(List<InputEntity> entities)
    {
        foreach (InputEntity e in entities)
        {
            GameEntity[] movers = this.movers.GetEntities();
            foreach (GameEntity entity in movers)
                entity.ReplaceMove(e.mousePosition.position);
        }
    }
}

AddViewSystem

public class AddViewSystem : ReactiveSystem<GameEntity>
{
    // 为了好看,所有ViewGameObject都放在该父节点下
    readonly Transform viewContainer = new GameObject("Game Views").transform;
    readonly GameContext context;
 
    public AddViewSystem(Contexts contexts) : base(contexts.game)
    {
        context = contexts.game;
    }
 
    // 创建Sprite的过滤器
    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
    {
        return context.CreateCollector(GameMatcher.Sprite);
    }
 
    // 第二次过滤,没有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)
        {
            GameObject go = new GameObject("Game View");
            go.transform.SetParent(viewContainer, false);
            e.AddView(go);  // Entity关联GameObject
            go.Link(e);   // GameObject关联Entity
        }
    }
}

RenderSpriteSystem

public class RenderSpriteSystem : ReactiveSystem<GameEntity>
{
    public RenderSpriteSystem(Contexts contexts) : base(contexts.game)
    {
    }
 
    // 过滤拥有Sprite的Entity
    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
    {
        return context.CreateCollector(GameMatcher.Sprite);
    }
 
    protected override bool Filter(GameEntity entity)
    {
        return entity.hasSprite && entity.hasView;
    }
 
    // 在这里的时候Entity已经创建了关联的节点,所以只要添加Sprite的渲染就OK了。
    // 所以当然也要注意,在添加程序组的时候要先添加AddViewSystem,在添加该System。
    // 不然GameObject都没有创建就执行该代码肯定报错的。
    protected override void Execute(List<GameEntity> entities)
    {
        foreach (GameEntity e in entities)
        {
            GameObject go = e.view.gameObject;
 
            // 先获取SpriteRenderer组件,没有获取到再添加,大家还记得只要改变Sprite的内容就会执行这边的代码吧?
            SpriteRenderer sr = go.GetComponent<SpriteRenderer>();
            if (sr == null) sr = go.AddComponent<SpriteRenderer>();
 
            sr.sprite = Resources.Load<Sprite>(e.sprite.name);
        }
    }
}

RenderPositionSystem

public class RenderPositionSystem : ReactiveSystem<GameEntity>
{
    public RenderPositionSystem(Contexts contexts) : base(contexts.game)
    {
    }
 
    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
    {
        return context.CreateCollector(GameMatcher.Position);
    }
 
    protected override bool Filter(GameEntity entity)
    {
        return entity.hasPosition && entity.hasView;
    }
 
    protected override void Execute(List<GameEntity> entities)
    {
        foreach (GameEntity e in entities)
        {
            e.view.gameObject.transform.position = e.position.value;
        }
    }
}

RenderDirectionSystem

public class RenderDirectionSystem : ReactiveSystem<GameEntity>
{
    readonly GameContext context;
 
    public RenderDirectionSystem(Contexts contexts) : base(contexts.game)
    {
        context = contexts.game;
    }
 
    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
    {
        return context.CreateCollector(GameMatcher.Direction);
    }
 
    protected override bool Filter(GameEntity entity)
    {
        return entity.hasDirection && entity.hasView;
    }
 
    protected override void Execute(List<GameEntity> entities)
    {
        foreach (GameEntity e in entities)
        {
            float ang = e.direction.value;
            e.view.gameObject.transform.rotation = Quaternion.AngleAxis(ang - 90, Vector3.forward);
        }
    }
}

MoveSystem

public class MoveSystem : IExecuteSystem, ICleanupSystem
{
    readonly IGroup<GameEntity> moves;
    readonly IGroup<GameEntity> moveCompletes;
    const float speed = 4f;
 
    // 获取有移动目标Move组和完成移动MoveComplete组
    public MoveSystem(Contexts contexts)
    {
        moves = contexts.game.GetGroup(GameMatcher.Move);
        moveCompletes = contexts.game.GetGroup(GameMatcher.MoveComplete);
    }
 
    // 拥有目标的Mover每帧执行
    public void Execute()
    {
        foreach (GameEntity e in moves.GetEntities())
        {
            // 计算下一个GameObject的位置,并替换
            Vector2 dir = e.move.target - e.position.value;
            Vector2 newPosition = e.position.value + dir.normalized * speed * Time.deltaTime;
            e.ReplacePosition(newPosition);
 
            // 计算下一个方向
            float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
            e.ReplaceDirection(angle);
 
            // 如果距离在0.5f之内,则判断为移动完成,移除Move命令,并添加移动完成标志
            float dist = dir.magnitude;
            if (dist <= 0.5f)
            {
                e.RemoveMove();
                e.isMoveComplete = true;
            }
        }
    }
 
    // 清除所有MoveComplete,MoveComplete暂时没有作用
    public void Cleanup()
    {
        foreach (GameEntity e in moveCompletes.GetEntities())
        {
            e.isMoveComplete = false;
        }
    }
}

GameFeature

public class GameFeature : Feature
{
    public GameFeature(Contexts contexts) : base("GameFeature")
    {
        Add(new AddViewSystem(contexts));
        Add(new RenderSpriteSystem(contexts));
        Add(new RenderPositionSystem(contexts));
        Add(new RenderDirectionSystem(contexts));
    }
}

需要注意的是,ViewSystem和SpriteSystem同时监听Sprite组件

但ViewSystem的过滤器是这样的

protected override bool Filter(GameEntity entity)
{
    return entity.hasSprite && !entity.hasView;
}

而SpriteSystem的过滤器是这样的

protected override bool Filter(GameEntity entity)
{
    return entity.hasSprite && entity.hasView;
}

当执行SpriteSystem时,必须已经拥有view了,如果反过来那么永远不会触发SpriteSystem

因此,SpriteSystem必须后于ViewSystem添加

同理Position和Direction也是如此

InputFeature

public class InputFeature : Feature
{
    public InputFeature(Contexts contexts) : base("InputFeature")
    {
        Add(new InputSystem(contexts));
        Add(new CreateMoverSystem(contexts));
        Add(new CommandMoveSystem(contexts));
    }
}

运行入口

public class Enter : MonoBehaviour
{
    public Systems systems;
    
    private void Start()
    {
        var contexts = Contexts.sharedInstance;
        systems = new Feature("Systems")
            .Add(new MoveSystem(contexts))
            .Add(new GameFeature(contexts))
            .Add(new InputFeature(contexts));
        systems.Initialize();
    }


    private void Update()
    {
        systems.Execute();
        systems.Cleanup();
    }
}

1

运行到指定位置时切换图片

public class ChangeSpriteSystem : ReactiveSystem<GameEntity>
{
    public ChangeSpriteSystem(Contexts contexts) : base(contexts.game)
    {
    }
    
    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
    {
        return context.CreateCollector(GameMatcher.MoveComplete);
    }

    protected override bool Filter(GameEntity entity)
    {
        return entity.isMoveComplete && entity.isMover;
    }

    protected override void Execute(List<GameEntity> entities)
    {
        foreach (var entity in entities)
        {
            entity.ReplaceSprite("Sprite2");
        }
    }
}

1

ReactiveSystem解析

创建如下简单组件和系统

[Game]
public class DebugComponent : IComponent
{
    public string msg;
}
public class InputSystem : IExecuteSystem
{
    private Contexts contexts;
    private GameEntity entity;
    private int count = 0;

    public InputSystem(Contexts contexts)
    {
        this.contexts = contexts;
    }

    public void Execute()
    {
        if (Input.GetMouseButtonDown(0))
        {
            count++;
            entity ??= contexts.game.CreateEntity();
            entity.ReplaceDebug($"左键点击了{count}次");
        }
    }
}
public class DebugSystem : ReactiveSystem<GameEntity>
{
    public DebugSystem(Contexts contexts) : base(contexts.game)
    {
    }

    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
    {
        return context.CreateCollector(GameMatcher.Debug);
    }

    protected override bool Filter(GameEntity entity)
    {
        return true;
    }

    protected override void Execute(List<GameEntity> entities)
    {
        foreach (var entity in entities)
        {
            Debug.Log(entity.debug.msg);
        }
    }
}

很简单,就是当我们左键鼠标时会答应日志

image-20220427021324509

GetTrigger

很容易发现GetTrigger是在父类的构造函数中执行的

public abstract class ReactiveSystem<TEntity> : IReactiveSystem, IExecuteSystem, ISystem 
    where TEntity : class, IEntity
{
    private readonly ICollector<TEntity> _collector;
        
    protected ReactiveSystem(IContext<TEntity> context)
    {
      this._collector = this.GetTrigger(context);
    }
}

这里的TEntity泛型我们很明显能知道应该是GameEntity(或者其他上下文)

推测一下,这个ICollector<GameEntiry>应该就是某一类GameEntity的收集

继续看GetTrigger方法,这个方法被我们重写了,看方法名,很明显就是创建了某一个Collector

public class DebugSystem : ReactiveSystem<GameEntity>
{
    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
    {
        return context.CreateCollector(GameMatcher.Debug);
    }
}

GameMachter

我们先观察这个参数,GameMatcher.Debug

public sealed partial class GameMatcher {

    static IMatcher<GameEntity> _matcherDebug;

    public static IMatcher<GameEntity> Debug 
    {
        get {
            if (_matcherDebug == null) 
            {
                var matcher = (Matcher<GameEntity>)Matcher<GameEntity>.AllOf(GameComponentsLookup.Debug);
                matcher.componentNames = GameComponentsLookup.componentNames;
                _matcherDebug = matcher;
            }

            return _matcherDebug;
        }
    }
}

通过一个静态方法创建

public class Matcher
{
    private int[] _allOfIndices;

	public static IAllOfMatcher<TEntity> AllOf(params int[] indices) => (IAllOfMatcher<TEntity>) new Matcher<TEntity>()
    {
      	_allOfIndices = Matcher<TEntity>.distinctIndices((IList<int>) indices)
    };
}

这个方法的参数就是组件的ID,可以是一个数组

这些内容是自动生成的

public static class GameComponentsLookup {

    public const int Asset = 0;
    public const int Board = 1;
    public const int Debug = 2; // 当前的组件
    public const int GameItem = 3;
    public const int Positon = 4;
    public const int View = 5;

    public const int TotalComponents = 6;

    public static readonly string[] componentNames = {
        "Asset",
        "Board",
        "Debug",
        "GameItem",
        "Positon",
        "View"
    };

    public static readonly System.Type[] componentTypes = {
        typeof(AssetComponent),
        typeof(BoardComponent),
        typeof(DebugComponent),
        typeof(GameItemComponent),
        typeof(PositonComponent),
        typeof(ViewComponent)
    };
}

并且在这里,IMatcherIAllOfMatcher都是一个接口而已,Matcher类实现了这两个接口,所以可以转换

这个方法其实很简单,直接返回了一个new Matcher<GameEntity>(),并修改了属性_allOfIndices

这只是一个数组而已,继续看方法

public class Matcher
{
    private static readonly HashSet<int> _indexSetBuffer = new HashSet<int>();
    
    private static int[] distinctIndices(IList<int> indices)
    {
      foreach (int index in (IEnumerable<int>) indices)
        Matcher<TEntity>._indexSetBuffer.Add(index);
      int[] array = new int[Matcher<TEntity>._indexSetBuffer.Count];
      Matcher<TEntity>._indexSetBuffer.CopyTo(array);
      Array.Sort<int>(array);
      Matcher<TEntity>._indexSetBuffer.Clear();
      return array;
    }
}

这个操作就很简单了,输入的一个组件ID数组,这一步的目的就是给这个数组去重,然后排序,并返回

Collector

了解了参数GameMatcher,我们继续看GetTrigger方法

这一部分是个扩展方法

public static class CollectorContextExtension
{
    public static ICollector<TEntity> CreateCollector<TEntity>(this IContext<TEntity> context,IMatcher<TEntity> matcher)
    {
        return context.CreateCollector<TEntity>(new TriggerOnEvent<TEntity>(matcher, GroupEvent.Added));
    }
}

TriggerOnEvent是一个简单的封装类,GroupEvent只是一个枚举类型而已

public enum GroupEvent : byte
{
    Added,
    Removed,
    AddedOrRemoved,
}

public struct TriggerOnEvent<TEntity> where TEntity : class, IEntity
{
    public readonly IMatcher<TEntity> matcher;
    public readonly GroupEvent groupEvent;

    public TriggerOnEvent(IMatcher<TEntity> matcher, GroupEvent groupEvent)
    {
        this.matcher = matcher;
        this.groupEvent = groupEvent;
    }
}

再继续看创建方法

public static class CollectorContextExtension
{
    public static ICollector<TEntity> CreateCollector<TEntity>(this IContext<TEntity> context,
        params TriggerOnEvent<TEntity>[] triggers)
    {
        IGroup<TEntity>[] groups = new IGroup<TEntity>[triggers.Length];
        GroupEvent[] groupEvents = new GroupEvent[triggers.Length];
        for (int index = 0; index < triggers.Length; ++index)
        {
            groups[index] = context.GetGroup(triggers[index].matcher);
            groupEvents[index] = triggers[index].groupEvent;
        }
        return (ICollector<TEntity>) new Collector<TEntity>(groups, groupEvents);
    }
}

这里的长度其实都是1

先看GetGroup方法, 这个方法属于context,在这里我们显然应该去GameContext里找

public sealed partial class GameContext : Entitas.Context<GameEntity>
{
    private readonly Dictionary<IMatcher<TEntity>, IGroup<TEntity>> _groups 
        = new Dictionary<IMatcher<TEntity>, IGroup<TEntity>>();
    
    public IGroup<TEntity> GetGroup(IMatcher<TEntity> matcher)
    {
      IGroup<TEntity> group;
      if (!this._groups.TryGetValue(matcher, out group))
      {
        group = (IGroup<TEntity>) new Group<TEntity>(matcher);
        foreach (TEntity entity in this.GetEntities())
          group.HandleEntitySilently(entity);
        this._groups.Add(matcher, group);
        for (int index1 = 0; index1 < matcher.indices.Length; ++index1)
        {
          int index2 = matcher.indices[index1];
          if (this._groupsForIndex[index2] == null)
            this._groupsForIndex[index2] = new List<IGroup<TEntity>>();
          this._groupsForIndex[index2].Add(group);
        }
        if (this.OnGroupCreated != null)
          this.OnGroupCreated((IContext) this, (IGroup) group);
      }
      return group;
    }
}

看到这里我们应该很明白了

首先Mather只是一个Key,如果我们有两个组件,Debug和Input那么就会存在3种Mather

Mather<GameEntity>{ Debug }
Mather<GameEntity>{ Input }
Mather<GameEntity>{ Debug,Input }

此时_gourps里就存在了3种分类,而我们可以根据传入的mather,直接获取对应的groupgroup里存放这所有符合条件的GameEntity

最后再看Collector的构造函数

public class Collector
{
    private GroupChanged<TEntity> _addEntityCache;
    
    public Collector(IGroup<TEntity>[] groups, GroupEvent[] groupEvents)
    {
      this._groups = groups;
      this._groupEvents = groupEvents;
      this._addEntityCache = new GroupChanged<TEntity>(this.addEntity);
      this.Activate();
    }

    public void Activate()
    {
      for (int index = 0; index < this._groups.Length; ++index)
      {
        IGroup<TEntity> group = this._groups[index];
        switch (this._groupEvents[index])
        {
          case GroupEvent.Added:
            group.OnEntityAdded -= this._addEntityCache;
            group.OnEntityAdded += this._addEntityCache;
            break;
          case GroupEvent.Removed:
            group.OnEntityRemoved -= this._addEntityCache;
            group.OnEntityRemoved += this._addEntityCache;
            break;
          case GroupEvent.AddedOrRemoved:
            group.OnEntityAdded -= this._addEntityCache;
            group.OnEntityAdded += this._addEntityCache;
            group.OnEntityRemoved -= this._addEntityCache;
            group.OnEntityRemoved += this._addEntityCache;
            break;
        }
      }
    }
}

这里的关键作用就是当目标group发生变化时,执行相应操作了

其中GroupChanged是一个委托

public delegate void GroupChanged<TEntity>(
    IGroup<TEntity> group,
    TEntity entity,
    int index,
    IComponent component)