ECS的核心概念

洪流学堂,让你快人几步。你好,我是跟着大智学Unity的萌新,我叫小新,最近在跟着大智探索DOTS。

通过前几节的学习咱们已经知道了ECS包含三个部分:实体(entities)、数据(components)、行为(system)。ECS架构的核心是数据,这也是为什么Unity会将这一套技术栈命名为DOTS。System会读取entity上面的component的数据流,并处理数据。实体在这里其实更像是索引,它本身并不包含任何数据和逻辑。

具体看下图:

这个图中,System读取了多个实体的TranslationRotation组件,然后经过计算处理,将结果更新到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方法,还包含了IJobChunkSchedule()ScheduleParallel()方法,可以在子线程中处理数据。Entities.ForEach是最简单的方法,只需要几行代码就能实现。IJobChunk可以用来处理比较复杂的情况。

ECS会在主线程调度Job,根据System的顺序。当job调度后,ECS会追踪哪些job在读写哪些组件。需要读权限的job需要等待前面写权限的job执行完,反之亦然。Job调度器会使用job依赖来决定哪些job可以并行,哪些必须串行。

System的组织

ECS通过Worldgroup来组织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),你的技术探路者,下次见!

别走!点赞收藏哦!

好,你可以走了。

你可能感兴趣的:(ECS的核心概念)