------------ 以下是废话
唔,可能我这个教程的目录看起来有点糟糕,偶尔讲一下筛选数据,偶尔又讲一下概念,偶尔又讲一下创建实体的方式。
这不,现在突然又跳到什么System执行顺序了。
其实是这样的,我在写这些教程之前,对ECS也是一窍不通的。
我更多的是按照官方示例走下去,在走的过程中,会发现很多不懂的点。
所以,我的教程顺序看起来会非常乱,但其实,这更符合一个完全不懂ECS的人的学习进程。
按照我的教程顺序,你会发现自己学到的都是刚好需要、刚好不懂的,前一篇学过的刚好就用在下一篇。
这样大家学起来可能会轻松一点,教程顺序虽然很乱,但大家的心千万不要乱。
ECS要做的事情就是——写组件、创建实体、写系统处理逻辑,只不过在这过程中为了解决某些特定的问题,我们需要引入各种用法和概念。
现在最重要的是入门,入门之后就可以抱着官方手册自己浪了。
好了,我们继续吧。
------------ 废话结束
1.逻辑的先后顺序
我们已经知道,System的OnUpdate函数会在每一帧自动被调用。
唔,实际上,更准确的,应该分两种情况:
ComponentSystem的OnUpdate:每帧都会被调用。
JobComponentSystem的OnUpdate:取决于Job里筛选出来的实体数量是否大于0,如果是,则每帧都会调用OnUpdate;如果筛选出来的实体数量是0,那么,OnUpdate不会被调用。
这个不太重要。
重要的是,我们的System是处理游戏逻辑,游戏逻辑肯定就会涉及到先后顺序。
比如…好吧,我比不出来…
再比如一下吧,比如…玩家走到某个地方后,我才能做一些操作。假设【走动的逻辑】和【做一些操作的逻辑】分别在两个System里。那么,【走动的逻辑】的应该在【做一些操作的逻辑】前面执行。
这时候就涉及到先后顺序了,没错,为了解决这类问题,System的OnUpdate函数的执行是有先后顺序的。
2.系统分组(ComponentSystem Group)
在了解系统如果控制先后顺序之前,需要先了解系统的分组。
唔,其实很简单,就是分组…
比如,SystemA和SystemB都属于一个叫做SystemGroupHello的分组(名字无所谓),SystemC和SystemD属于SystemGroupOther分组。
就是这么简单,就是我们所认知的普通的分组概念。
3.System的执行顺序(System Update Order)
好,接下来我要贴很长的一串东西,大家不要吓到:
InitializationSystemGroup(负责初始化工作的系统分组)
SimulationSystemGroup(负责逻辑运算的系统分组)
PresentationSystemGroup(负责图形与渲染工作的系统分组)
好吧,并没有什么很长的东西,我随便吓吓大家的。
以上三个是ECS默认的系统分组,很重要,这三个分组里的System从上到下按照顺序被执行。(实际上SystemGroup本身也属于System,也会被调用OnUpdate函数)。
(以上三个分组的中文翻译参考来源:https://connect.unity.com/p/unity-ecs-wu-liao-jie-systemzhi-xing-shun-xu)
接下来又是一长串的东西,这次是真的:
-
InitializationSystemGroup
- BeginInitializationEntityCommandBufferSystem
- CopyInitialTransformFromGameObjectSystem
- SubSceneLiveLinkSystem
- SubSceneStreamingSystem
- EndInitializationEntityCommandBufferSystem
-
SimulationSystemGroup
- BeginSimulationEntityCommandBufferSystem
-
TransformSystemGroup
- EndFrameParentSystem
- CopyTransformFromGameObjectSystem
- EndFrameTRSToLocalToWorldSystem
- EndFrameTRSToLocalToParentSystem
- EndFrameLocalToParentSystem
- CopyTransformToGameObjectSystem
- LateSimulationSystemGroup
- EndSimulationEntityCommandBufferSystem
-
PresentationSystemGroup
- BeginPresentationEntityCommandBufferSystem
- CreateMissingRenderBoundsFromMeshRenderer
- RenderingSystemBootstrap
- RenderBoundsUpdateSystem
- RenderMeshSystem
- LODGroupSystemV1
- LodRequirementsUpdateSystem
- EndPresentationEntityCommandBufferSystem
以上全部都是ECS自带的System,这是基于0.3.0版本,以后的版本可能会发生变化(实际上我发现有些System压根找不到,魂淡,是官方手册没有及时更新么)。
先别抖,别害怕,注意粗体部分的内容:InitializationSystemGroup,SimulationSystemGroup,PresentationSystemGroup。
这三个是我们刚刚提到的默认系统分组,实际当中,我们大部分人可能都会在默认的分组下搞事情。
每个分组下有很多个System,而某些System下又可能包含子System。
至于这些System有什么用,大家当然是先不用管了。
以上贴出的所有System,都会按照罗列的顺序执行OnUpdate函数,如果System又包含了子System,那么就子System也会执行OnUpdate函数。
这个有什么用呢?大家可以理解为"生命周期"(实际上官方没有提到这个东西),这些按顺序执行的System,会影响到我们的某些决策。
比如,我们想在初始化之前搞事情,那就看看初始化的第一个System是谁,然后让我们自己的System在它之前执行。
如果大家之前有看过Entity Debugger(Window->Analysis->Entity Debugger)的话,应该就会发现,上面列出的部分System就在Debugger里:
Debugger里的System也是按照执行顺序逐一罗列的,未来我们新增的System也会出现在这里。
4.控制System的执行顺序
等等,怎么样可以让我们的System在别的System之前搞事情呢?
我们来看看三个特性(Attribute)
UpdateInGroup:指定当前System在哪个分组下
UpdateBefore:指定当前System在哪个System之前执行
UpdateAfter:指定当前System在哪个System之后执行
我们直接看代码,随便创建一个场景,然后场景一个System类:
using Unity.Entities;
[UpdateBefore(typeof(SceneSystem))]
public class MutouSystem : ComponentSystem
{
protected override void OnUpdate () { }
}
我创建了一个很简单的System,但是附加了UpdateBefore特性,并指定了SceneSystem类型。
这样的话,我们的MutouSystem肯定就会在SceneSystem之前执行了。
-
然后,什么都不用做,直接运行,然后看看Entity Debugger:
怎么样,MutouSystem是不是出现在SceneSystem之前了…
对…没错…才怪啊喂!竟然不是!(好丢人怎么办
很奇怪,MutouSystem出现的地方和我们想象的不一样,这是为什么呢?
因为…爱情(旁白:闭嘴!)
唔,很重要的一点,System的执行顺序首先是按照System Group排序的,比如InitializationSystemGroup一定是在SimulationSystemGroup之前执行,那么,InitializationSystemGroup下的所有System肯定也是在SimulationSystemGroup下所有System之前执行。
有点绕,换句话说,先按分组排序,然后分组内部自己再排序,无论你内部怎么排序,都不可能跨越分组的排序。
所以,我们要指定System的执行顺序,首先就得确认System所属的分组,然后System就在分组内部调整排序。
如果我们不知道System所属分组,那么,它就会默认分配到SimulationSystemGroup分组下。
所以,我们的MutouSystem是属于SimulationSystemGroup分组的,而这个分组下不存在SceneSystem,因此,我们的UpdateBefore特性是无效的。
于是,改改代码:
using Unity.Entities;
[UpdateInGroup(typeof(InitializationSystemGroup))]
[UpdateBefore(typeof(SceneSystem))]
public class MutouSystem : ComponentSystem
{
protected override void OnUpdate () {}
}
我在MutouSystem顶部又加了一个UpdateInGroup特性,指定MutouSystem所属分组,于是,再次运行:
好了,正常了。
不过,MutouSystem和SceneSystem中间还隔了很多System,也就是说,UpdateBefore只能保证在某个System之前执行,而不能保证是严格地在某个System的上一个执行。
我们再改改,新增一个System:
[UpdateInGroup(typeof(InitializationSystemGroup))]
[UpdateAfter(typeof(MutouSystem))]
public class LoveSystem : ComponentSystem
{
protected override void OnUpdate () {}
}
这次我们指定LoveSystem在MutouSystem之后执行,运行看看效果:
结果和我们预想一样。
我们再改改,把MutouSystem放到SceneSystem之后执行:
[UpdateInGroup(typeof(InitializationSystemGroup))]
[UpdateAfter(typeof(SceneSystem))]
public class MutouSystem : ComponentSystem
{
protected override void OnUpdate () {}
}
运行看看效果:
好,依旧完美,LoveSystem永远跟在MutouSystem身后了。
看,这就是我所说的…爱情(旁白:滚!)
好了,关于System的执行顺序,就介绍这么多吧。
注意,本系列教程基于DOTS相关预览版的Package包,是预览版,不代表正式版的时候也适用。
转发本系列文章的朋友请注意,带上原文链接和原文日期,避免误导未来使用正式版的开发者。