ECS中ReactiveSystem原理

1,010 阅读3分钟

ECS的Systems主要分为以下五类:

  • InitializeSystem,需要继承IInitializeSystem接口,实现Initialize()方法,该方法在程序刚开始运行时触发一次,类似于Unity的Start方法。
public class InitPlayerSystem : IInitializeSystem
{
    private GameContext game; 
    //获取GameContext
    public InitPlayerSystem(Contexts contexts)
    {
        game = contexts.game;
    }
    //创建一个entity,并为entity添加某些component
    public void Initialize()
    {
        var entity = game.CreateEntity();
        entity.AddAssetName("Player");
        entity.AddPosition(Vector3.zero);
        entity.AddSpeed(0,0.03f);
        entity.isSpecial = true;
    }
}

  • ExecuteSystem,需要继承IExecuteSystem接口,实现Execute()方法,在这个方法里的代码需要每帧执行,类似于Unity的Update方法。通常用于判断用户输入(Unity.Input)行为,主角移动等情况。
public class InputSystem : IExecuteSystem
{
    private InputContext input;
    //获取InputContext
    public InputSystem(Contexts contexts)
    {
        input = contexts.input;
    }
    //每帧执行,此功能是按下鼠标左键
    public void Execute()
    {
        input.isPlayerMoving = Input.GetMouseButton(0);
    }
}
  • CleanupSystem,需要继承ICleanupSystem接口,实现Cleanup()方法,这个方法里的代码会在每帧结束时执行,通常用于清理只存在一帧里的Entity等,用法与ExecuteSystem类似。
  • TearDownSystem,需要继承ITearDownSystem接口,实现TearDown()方法,这个方法会在程序结束时执行一次,类似与Unity的OnDestroy方法,一般用于释放某些资源等等,用法与InitializeSystem类似。
  • ReactiveSystem该System继承自IReactiveSystem接口,是一种特殊的ExecuteSystem,可以说是ExecuteSystem和Collector的结合体。

如何使用ReactiveSystem

下面是一个典型的ReactiveSystem使用

public class RemoveViewSystem : ReactiveSystem<GameEntity>
{
    public RemoveViewSystem(Contexts contexts) : base(contexts.game)
    {
    }

    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
    {
        return context.CreateCollector(new TriggerOnEvent<GameEntity>(GameMatcher.Destroy,GroupEvent.Added));
    }

    protected override bool Filter(GameEntity entity)
    {
        return entity.hasView && entity.isDestroy; 
    }
    
    protected override void Execute(List<GameEntity> entities)
    {
        foreach (var e in entities)
        {
            Object.DestroyImmediate(e.view.obj);
            e.RemoveView();
        }
    }
}

其中GetTrigger,Filter,Execute是父类ReactiveSystem的抽象方法,子类继承是需要实现,同时父类还包含了一个带参数的构造函数,所以子类必须显示定义构造函数。 接下来看一下弗雷的代码:

public abstract class ReactiveSystem<TEntity> : IReactiveSystem, IExecuteSystem, ISystem where TEntity : class, IEntity
  {
    private readonly ICollector<TEntity> _collector;
    private readonly List<TEntity> _buffer;
    private string _toStringCache;

    protected ReactiveSystem(IContext<TEntity> context)
    {
      this._collector = this.GetTrigger(context);
      this._buffer = new List<TEntity>();
    }

    protected ReactiveSystem(ICollector<TEntity> collector)
    {
      this._collector = collector;
      this._buffer = new List<TEntity>();
    }

    /// Specify the collector that will trigger the ReactiveSystem.
    protected abstract ICollector<TEntity> GetTrigger(IContext<TEntity> context);

    /// This will exclude all entities which don't pass the filter.
    protected abstract bool Filter(TEntity entity);

    protected abstract void Execute(List<TEntity> entities);

    /// Activates the ReactiveSystem and starts observing changes
    ///             based on the specified Collector.
    ///             ReactiveSystem are activated by default.
    public void Activate()
    {
      this._collector.Activate();
    }

    /// Deactivates the ReactiveSystem.
    ///             No changes will be tracked while deactivated.
    ///             This will also clear the ReactiveSystem.
    ///             ReactiveSystem are activated by default.
    public void Deactivate()
    {
      this._collector.Deactivate();
    }

    /// Clears all accumulated changes.
    public void Clear()
    {
      this._collector.ClearCollectedEntities();
    }

    /// Will call Execute(entities) with changed entities
    ///             if there are any. Otherwise it will not call Execute(entities).
    public void Execute()
    {
      if (this._collector.count == 0)
        return;
      foreach (TEntity collectedEntity in this._collector.collectedEntities)
      {
        if (this.Filter(collectedEntity))
        {
          collectedEntity.Retain((object) this);
          this._buffer.Add(collectedEntity);
        }
      }
      this._collector.ClearCollectedEntities();
      if (this._buffer.Count == 0)
        return;
      try
      {
        this.Execute(this._buffer);
      }
      finally
      {
        for (int index = 0; index < this._buffer.Count; ++index)
          this._buffer[index].Release((object) this);
        this._buffer.Clear();
      }
    }

    public override string ToString()
    {
      if (this._toStringCache == null)
        this._toStringCache = "ReactiveSystem(" + this.GetType().Name + ")";
      return this._toStringCache;
    }

    ~ReactiveSystem()
    {
      this.Deactivate();
    }
  }
  • 父类的构造函数首先会调用GetTrigger方法,该方法是一个抽象函数,需要我们自行实现,并且返回值是一个Collecotr,关于Collector的使用,查看上一篇Collector的简介。
  • 然后是非抽象函数Execute(),该方法应该是每帧都会调用,与ExecuteSystem类似,该方法首先遍历了Collector收集的额entities,然后清空了Collector收集的entities,最后执行了我们实现的抽象方法,带参数的Execute(List entities)。
  • 所以ReactiveSystem和Collector结合使用非常强大。