来源:https://www.youtube.com/watch?v=k07I-DpCcvE

一个System的基础结构

通常一个System是每一帧都运行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[BurstCompile]
public partial struct MySystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
//...
}

[BurstCompile]
public void OnDestroy(ref SystemState state)
{
//...
}

[BurstCompile]
public void OnUpdate(ref SystemState state)
{
//...
}
}
  • 每一个System实例都属于一个World
  • 基本上,一个World里的Entities只会被自己World里面的System处理。这只是一种原则,其实System内的代码和Mono Behaviour的代码可以处理任何world里的Entities。
  • SystemState 包含了此System所在的World和EntityManager

SystemGroup

我们可以使用SystemGroup来给某个World的System进行层级处理。

1
2
3
4
5
6
7
public class MySystemGroup : ComponentSystemGroup
{
protected override void OnUpdate()
{
base.OnUpdate();
}
}

System在Group内的执行顺序

SystemGroup的更新方式是可以被重载修改的,一旦修改,当前Group下的所有System更新方式都会改变。

默认情况下,一个SystemGroup内的各种System是通过伪随机数排序的,Group内部System的Update顺序也根据这个排序进行。当Group内发生一个System增删操作时,Group需要重新排序。

如果你想控制一个System在SystemGroup中的排序来保证它和其他System的Update顺序,需要添加UpdateBefore特性和UpdateAfter特性

1
2
3
4
5
6
[UpdateBefore(typeof(FooSystem))]
[UpdateAfter(typeof(BarSystem))]
public class MySystem : ISystem
{
//...
}

在WIndows——Entities——System窗口中,展示了各个SystemGroup随机后的顺序:

  • Initialization System Group:各种初始化System
  • Simulation System Group:核心游戏逻辑需要的System
  • Presentation System Group:渲染需要的System

我们一个SystemGroup中重载OnUpdate,可以让其内部的System有选择地进行更新,或者每一帧更新多次。

在Simulation System Group中,Fixed Step Simulation System Group内的System按照每秒的fixed rate进行更新,所以会出现有时其内部的System在好几帧都不更新,或者每帧更新好几次的情况。

System放在特定的Group中

当进入playmode时,Unity ECS System会有一个自动引导过程,先创建一个Default World,在向这个World中放入一套标准程序集的实例和上面的三种默认SystemGroup和各种定义的SystemGroup的实例。

如果想把自定义的System放在一个特定的SystemGroup中,可以使用UpdateInGroup特性

1
2
3
4
5
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
public class MySystem : ISystem
{
//...
}

关闭System的自动引导

如果你想完全关闭Unity ECS System的自动引导,需要在IDE中添加脚本定义:

1
# UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP

使用了这个之后,我们就能够创建任何自定义的World,在其中加入想要的System实例和SystemGroup实例。

SystemState

在一个System struct中,SystemState 除了包含此System所在的World和EntityManager外,还包含了GetEntityQueryGetComponentTypeHandle<T>两个需要介绍API,前者顾名思义,后者用来对chunks内的component arrays进行操作来获得component。

注意:

在System内只能使用SystemState提供的GetEntityQueryGetComponentTypeHandle<T>这个API来获取queries和component type handles,不能使用EntityManager。原因是:

SystemState提供的GetEntityQueryGetComponentTypeHandle<T>可以将component type注册到这个System当中去(这两个API都会进行这个操作)。

System Dependency

将component type注册到这个System当中是很重要的,想要理解这一点,我们还需要知道在SystemState当中还有一个重要的属性:Dependency。Dependency是一种Job Handle

当一个System 执行Updates之前,需要先做两件事情:

  • SystemState的Dependency调用isCompleted
  • 就像在Job System中讲的,当当前System的Dependency调用isCompleted时,联系这个Dependency的Job Handle,也就是其他System的Dependency才会执行,而其他System如何知道自己需要当前System的Dependency呢?就是通过SystemState提供的GetEntityQueryGetComponentTypeHandle<T>获取的queries和component type handles,其他System只要发现自己注册进的component type相同,就明白自己的Dependency是当前System

举个例子,如果一个Foo component注册进了My System里,那其他也被Foo Component注册的System的Dependency就会设为My System的Dependency,所以能够将这些Job Handle统一起来,这个过程是在每帧都做一次的。这样做的目的是为了我们在System内部使用Job时,这个Dependency是及时包含所有dependency信息的。

说白了,这个Dependency就是为了方便在System中使用Job System:

  • 所有在当前System中Schedule的Job必须都直接或间接地依赖Dependency属性。
  • 在System Update Returns之前,Dependeny属性必须赋予一个Job Handle,这个Job Handle是当前System所有的Schedule的Job的Job Handle的组合。
1
2
3
4
5
6
7
8
//...in a system
protected override void OnUpdate(ref SystemState state)
{
var handle = new MyJob().Schedule(state.Dependency);
var otherHandle = new OtherJob().Schedule(state.Dependency);

state.Dependency = JobHandle.CombineDependencies(handle,otherHandle);
}

当然,如果上面代码中OtherJob的dependency是MyJob,我们直接把state的Dependency设为otherHandle即可。

1
2
3
4
5
6
7
8
//...in a system
protected override void OnUpdate(ref SystemState state)
{
var handle = new MyJob().Schedule(state.Dependency);
var otherHandle = new OtherJob().Schedule(handle);

state.Dependency = otherHandle;
}