介绍
System是ECS的核心组成部分之一,它是个单纯的逻辑处理类,本身不包含任何状态,只是在特定的时间执行系统中的逻辑,在这些逻辑中改变相关Entity身上的Component的数据与状态,这是ECS系统中关键所在。Entitas中的System根据作用的不同给我们提供了5个接口类,分别为
IInitializeSystem
,
IExecuteSystem
,
IReactiveSystem
,
ICleanupSystem
以及
ITearDownSystem
而他们都继承自
ISystem
.同时Entitas还提供了一个Systems类,用来组合不同的System,控制System的执行顺序以及生命周期.
一 Systems与Feature
systems是用来控制所有system的执行,systems内部维护了4个不用的List用来保存不同类型的系统(
_initializeSystems
,
_executeSystems
,
_cleanupSystems
,
_tearDownSystems
) ,然后我们通过
Add(ISystem system)
方法添加到不同的List中. 同时systens继承了
IInitializeSystem
,
IExecuteSystem
,
ICleanupSystem
,
ITearDownSystem
,实现
Execute()
,
Cleanup()
,
Initialize()
,
TearDown()
等接口,在这些接口中驱动对应List中的system。所以我们只需要在外部调用这4个对应的接口来驱动这个Entitas的运行。
public Systems() {
//初始化各个List
_initializeSystems = new List<IInitializeSystem>();
_executeSystems = new List<IExecuteSystem>();
_cleanupSystems = new List<ICleanupSystem>();
_tearDownSystems = new List<ITearDownSystem>();
}
//将不同的System添加到对应的列表中
public virtual Systems Add(ISystem system) {
var initializeSystem = system as IInitializeSystem;
if (initializeSystem != null) {
_initializeSystems.Add(initializeSystem);
}
var executeSystem = system as IExecuteSystem;
if (executeSystem != null) {
_executeSystems.Add(executeSystem);
}
var cleanupSystem = system as ICleanupSystem;
if (cleanupSystem != null) {
_cleanupSystems.Add(cleanupSystem);
}
var tearDownSystem = system as ITearDownSystem;
if (tearDownSystem != null) {
_tearDownSystems.Add(tearDownSystem);
}
return this;
}
4个主要的执行方法
//驱动初始化系统执行Initialize()方法
public virtual void Initialize() {
for (int i = 0; i < _initializeSystems.Count; i++) {
_initializeSystems[i].Initialize();
}
}
//驱动每帧执行的系统执行Execute()方法
public virtual void Execute() {
for (int i = 0; i < _executeSystems.Count; i++) {
_executeSystems[i].Execute();
}
}
//驱动清理系统执行Cleanup()方法
public virtual void Cleanup() {
for (int i = 0; i < _cleanupSystems.Count; i++) {
_cleanupSystems[i].Cleanup();
}
}
//驱动结束系统执行TearDown()方法
public virtual void TearDown() {
for (int i = 0; i < _tearDownSystems.Count; i++) {
_tearDownSystems[i].TearDown();
}
}
Feature是代码生成器为我们生成的一个类,主要用于在编辑器模式下开启visual debugging时收集各个系统的运行数据,同时在Unity中展示,方便我们在开发时做调试和优化。所以在开启visual debugging时,Feature继承自DebugSystems,而DebugSystems又是继承自Systems的,在里面会做一些数据收集与展示的工作,在关闭visual debugging时,Feature直接继承自Systems,与Systems功能一致。当然这所有的不同都由代码生成器生成,无须我们手动修改。
二 4个寻常的系统,1个不寻常的系统
2.1 IExecuteSystem 每帧执行的系统
如果我们需要需要一个持续执行的system,继承
IExecuteSystem
,重写
void Execute()
方法,在这个方法里面写我们这个系统中需要每帧执行的逻辑。
2.2 ICleanupSystem 清理系统
ICleanupSystem
也是每帧执行的系统。不过从逻辑上区分这个system是为了我们在执行完所有的
IExecuteSystem
之后执行的逻辑所设立的,用来做一些数据的清理的作用.需要重写
void Cleanup()
方法; 将清理逻辑写在这个方法中.
2.3 IInitializeSystem 初始化系统
IInitializeSystem
正如它的名字一样,它是用来做初始化逻辑用的。需要重写
void Initialize();
方法。类似unity中的
void Start()
方法.
2.4 ITearDownSystem销毁系统
ITearDownSystem
需要重写
void TearDown();
方法。在这个方法里面执行退出销毁逻辑。
2.5 不寻常的ReactiveSystem 反应系统
ReactiveSystem
实际上是
IExecuteSystem
系统,所以它也需要重写
void Execute()
,但是对于我们使用者来说,我们自己重写的
void Execute()
不一定每帧都会调用,这究竟是怎么回事呢?我们来看看
ReactiveSystem
的实现:
我们继承
ReactiveSystem
后需要实现它的3个抽象方法:分别是:
ICollector<TEntity> GetTrigger(IContext<TEntity> context)
:创建一个收集器,收集我们需要的同时发生了改变的
Entity
.
bool Filter(TEntity entity)
:一个筛选方法,筛选收集器收集到的Entity。
void Execute(List<TEntity> entities)
:执行方法,对收集器收集到的同时通过了筛选方法筛选的
Entity
执行具体的逻辑。entities就是当前帧符合我们需求的Entity列表了。这个列表每帧都会清空。
上面这3个方法是我们使用
ReactiveSystem
的核心方法,每个
ReactiveSystem
都必须拥有这3个方法。
因为它自己也是一个
IExecuteSystem
系统,所以它也有自己的
void Execute()
方法,我们看一下这个方法里面做了些什么:
public void Execute() {
if (_collector.count != 0) {
foreach (var e in _collector.collectedEntities) {
if (Filter(e)) {
e.Retain(this);
_buffer.Add(e);
}
}
_collector.ClearCollectedEntities();
if (_buffer.Count != 0) {
try {
Execute(_buffer);
} finally {
for (int i = 0; i < _buffer.Count; i++) {
_buffer[i].Release(this);
}
_buffer.Clear();
}
}
}
}
这个方法也十分的简单,其中有2个变量:分别是
_collector
和
_buffer
。
_collector
是
GetTrigger
的方法的返回值,也就是这个系统的收集器。
_buffer
是存储当前帧中收集器收集到的,同时通过筛选方法的Entity集合。而这个方法也就是做这些事情。筛选Entity,然后调用
void Execute(List<TEntity> entities)
方法,将筛选好的列表传过去做逻辑处理。
那么
ReactiveSystem
中包含了变量是不是不符合ECS的规则了呢?
_collector
是系统初始化的时候创建的,不会随着系统的运行发生改变的。而
_buffer
只是相当与一个临时的变量,每一帧都会清理。也就是说每一帧开始的时候它的状态都是一样的。保存这个变量只是为了避免频繁的创建List。
MultiReactiveSystem
的用法和实现与
ReactiveSystem
类似,只是可以包含多个
Context
以及多个
Collector
总结
我们可以看出Entitas的System实现非常的简单。通过Systems类控制所有的系统的执行顺序,根据系统的逻辑功能,给系统划分了4种类型,分别有自己特殊的需要重写的方法,而所有系统的主要逻辑都集中在这些方法中。Entitas为了方便我们使用,提供了一种特殊的
ReactiveSystem
。它通过收集器,只关心满足条件的Entity,当没有满足条件的Entity时,它的
Execute
方法就不会执行,所以在我们使用者看来它就变成了一种触发式的系统了。
<上一篇>ECS Entitas源码分析(四)___Entity