ECS的核心概念
洪流学堂,让你快人几步。你好,我是跟着大智学Unity的萌新,我叫小新,最近在跟着大智探索DOTS。
通过前几节的学习咱们已经知道了ECS包含三个部分:实体(entities)、数据(components)、行为(system)。ECS架构的核心是数据,这也是为什么Unity会将这一套技术栈命名为DOTS。System会读取entity上面的component的数据流,并处理数据。实体在这里其实更像是索引,它本身并不包含任何数据和逻辑。
具体看下图:
这个图中,System读取了多个实体的Translation
和Rotation
组件,然后经过计算处理,将结果更新到LocalToWorld
组件中。
从图中你可以看到,实体A和B还有Renderer组件,但是C并没有。不过这并不会影响System的计算逻辑,因为这个系统不关心Renderer
组件。
你还可以写一个系统,需要处理Renderer组件,这样系统就会忽略实体C。你还可以写一个系统排除包含Renderer组件的实体,这样系统就会忽略实体A和B。
下面对ECS中比较重要的几个核心概念做一个梳理:
原型 Archetypes
多个组件的组合叫做一个原型。
比如一个3D物体可能会包含用于transform的组件,包括移动、旋转、渲染,每个3D物体对应一个实体,但是他们都有同样的组件,所以ECS会把他们分类成是一类原型。
在上图中,实体A和B的原型都是M,实体C的原型是N。
你也可以通过在运行时添加或者移除component来改变一个实体的原型。例如:如果将实体B的Renderer组件移除,实体B的原型就会变成N。
内存块 Memory Chunks
为什么要先讲原型的概念呢,因为一个实体的原型是什么,决定了ECS会将实体的components也就是数据存在什么地方。ECS按块分配内存,每块用一个ArchetypeChunk
对象表示。
一个块只包含一种原型,可以包含的多个实体的数据。如果一个块的内存满了,ECS会分配一个新的块来存储新的实体的components。
如果你修改了实体的组件,那就相当于修改了实体的原型,这时候ECS会将实体的组件数据移到另外一个块中。
原型和内存块的关系是一对多的关系。这就意味着,如果想查询给定的一组component类型的所有实体,只需要在这些原型中搜索即可。这样会比在所有的实体中查找效率高很多。
ECS在存储实体到内存块中没有特殊的排序,当创建一个实体或者实体的原型发生变化时,ECS会将它放到对应原型的第一个还有空间的内存块中。内存块中的数据会紧密排列。如果一个实体要被移出当前原型的内存块,这时候会有个空位,ECS会把这个内存块最后的实体数据移动到这个空位中。
注意:原型中的共享组件(后面会具体说这是个什么东东)的数据也会影响实体会被存在哪个内存块。同一个内存块中的所有实体的共享组件中的数据值都是相同的。如果你修改了共享组件中的数据,这个实体会被移到另外一个块中,有点类似修改了实体的原型。
将共享组件的实体分到一个内存块中会提高处理他们的速度。比如Hybird Renderer(混合渲染)定义了RenderMesh组件来达成这个目的。
实体查询
一个System根据什么来决定处理哪些实体呢?这时候会用到一个叫实体查询(Entity Query)的东西。实体查询首先需要一些组件类型,然后根据你传入的组件类型的组合,在包含这些组件的原型中查询符合要求的实体。查询时可以指定下面三种类型:
- All 必须包含All中所有的组件类型
- Any 必须包含Any中至少一个组件类型
- None 不能包含None中任意一个组件类型
一次实体查询的结果会返回所有符合查询要求的内存块,你可以使用IJobChunk
来迭代遍历所有的组件。(IJobChunk后面会讲。)
Jobs 作业
之前说过,ECS配合Job使用才能发挥多线程的威力。ECS提供了SystemBase
类,其中包含Entities.ForEach
方法,还包含了IJobChunk
的Schedule()
和ScheduleParallel()
方法,可以在子线程中处理数据。Entities.ForEach
是最简单的方法,只需要几行代码就能实现。IJobChunk
可以用来处理比较复杂的情况。
ECS会在主线程调度Job,根据System的顺序。当job调度后,ECS会追踪哪些job在读写哪些组件。需要读权限的job需要等待前面写权限的job执行完,反之亦然。Job调度器会使用job依赖来决定哪些job可以并行,哪些必须串行。
System的组织
ECS通过World
和group
来组织system。默认情况下,ECS会创建一个默认的World,包含一些预定义的group组。它会找到工程中所有的System,实例化他们,并添加到预定义的group中。
你可以指定同一个group中system的Update的执行顺序。Group也是一种system,所以你可以将一个group添加到另外一个group中。如果你没有指定顺序,system的执行顺序会不太确定,并不会按照它们创建的顺序。不过,同一个group中的所有system都会比下一个group中的system先执行。
System的Update是在主线程中执行的,不过可以使用Job将工作分配到子线程中。
ECS世界的可视化创建(authoring)
在Unity中没办法直接可视化地创建DOTS世界,但是,可以先用GameOjbect和MonoBehaviour来创建,然后通过转换系统将GameObject转换成实体和组件。这个我们后面也会细讲。
总结
这节讲述了ECS中多个核心概念,到这,你可能对上一节的代码就有更深入的理解了。
扩展阅读
【扩展学习】在洪流学堂公众号回复
DOTS
可以阅读本系列所有文章,更有视频教程等着你!
呼~ 今天小新絮絮叨叨的真是够够的了。没讲清楚的地方欢迎评论,咱们一起探索。
我是大智(微信:zhz11235),你的技术探路者,下次见!
别走!点赞,收藏哦!
好,你可以走了。