Categories

Tags

Table of Contents

  1. Unity ECS原理
    1. 原型
    2. 内存块
    3. 实体查询
    4. Jobs
    5. 系统的组织
    6. ECS Authoring

Unity ECS原理

Entity Component System (ECS)架构划分为 个体(实体),数据(组件),以及行为(系统)。此架构专注于数据,系统通过读取根据实体索引的组件数据流,将数据从输入状态转换为输出状态。

下图说明了这三个基础部分是如何协作的

在这个图中,系统读取Translation 和 Rotation 组件,将它们相乘然后更新相应的LocalToWorld 组件。

实际上,实体A和B 上有Renderer组件,而实体C没有,并不会影响该系统,因为系统不关心Renderer组件。(或者你也可以设置该系统需要Renderer组件,这样一来,系统就会忽略掉实体C的组件,因为该实体没有完全拥有系统所需的一组组件。同样,你还可以设置系统不包含Renderer组件,这样,实体A和B就会被相同忽略)

原型

组件的独特组合被称为原型。如,一个3D对象可能有其世界变换的组件,一个平移变换组件,一个旋转变换组件,以及一个渲染组件。这些3D对象的每一个实例对应着一个实体,但是由于它们共享相同的组件集,因此可以将它们分类为一个原型。

上面图示中,实体A和B共享同一个原型M,而实体C属于原型N。

你可以通过添加或移除组件,在运行时流畅的更改实体的原型。例如,如果你移除B的渲染组件,则B会移动到原型N中。

内存块

一个实体的原型决定了它的组件将存放在哪里。ECS以块的形式分配内存,它们由ArchetypeChunk对象表示。一个块始终只包含某个原型的实体。当一块的内存已满时,将为使用相同原型创建的实体分配新的内存块。如果通过添加或删除组件来更改某个实体的原型,那么它的组件会移动到其他相应的块中(如果有相应的原型的话),或者创建新的原型和块(没有现存的相应原型时)

这种组织方案在原型和块之间提供了一对多的关系。同时也意味着,要知道具有给定组件集的所有实体,仅需要搜索通常数量很少的现有原型就行了,而不是去搜索通常数量很大的所有实体。

块中的实体没有按照特定顺序存储。创建实体或者改变其原型时,该实体会进入该原型第一个具有空间的块中。但是,原型的大块之间保持的紧凑,在块内删除一个实体时,会把该块最后的一个实体的组件移动到新腾出的空位中。

注意: 原型中共享组件的值还确定了哪些实体存储在哪个块中。对于给定块中的所有实体,它们的任何共享组件都具有完全相同的值。 如果修改某个实体的共享组件上的任何字段的值,就会将该实体移动到其他块,就像修改实体的原型一样。只不过,它是在同一个原型的各个块内进行移动,如果有必要就会分配一个新块(例如,战斗单位原型,共享组件为兵种,怎样就会把所有相同的兵种的实体放在同一个块当中)。 如果将它们一起处理时更加有效,就可以所有共享组件对原型中的实体进行分类。例如,Hybrid Renderer 定义其RenderMesh组件。

实体查询

你可以使用EntityQuery 来定义系统应该处理哪些实体。实体查询在现有的原型中搜索匹配拥有所需组件的实体。你也可以在查询中指定以下组件要求

  • All —— 原型必须包含所有的组件
  • Any —— 原型至少包含这些组件中的其中一个
  • None —— 原型不能包含这些组件中的任何一个

实体查询提供了一个块列表,这些块都包含了查询所需组件。你可以使用IJobChunk(专用的ECS Job 之一)直接遍历这些块中的组件,也可以隐式使用IJobForEach 或 非Job 的 for-each 循环

注意: IJobForEach基于Job参数和属性隐式的创建实体查询,你也可以重写在Job调度时隐式查询。

Jobs

你可以使用Unity C# Job System 来利用多线程。Unity 提供 JobComponentSystem, 以及专门的工作 Job类型:IJobForEach 和 IJobChunk,来在主线程外转换数据。

IJobForEach(和IJobForEachWhitEntity) 通常用起来是最简单的。 IJobChunk可用于IJobForEach无法处理的更复杂的情况。

这些ECS Job 所有 EntityQuery 对象,该对象不仅定义了Job访问的组件,还指定了该访问是只读还是可读写。通过该访问类型信息,Job调度程序以此来确定它可以并行运行哪些Job,哪些Job必须按顺序运行。读取相同数据的Job可以同时运行,但是,当一个Job写入另一个Job访问的数据时,这些Job必须按顺序运行。

运行这类顺序Job的顺序由你设置的Job依赖项来确定。当核心ECS代码调用你的某个JobComponent.OnUpdate() 函数时,它会传入一个JobHandle参数,该参数封装了现有的Job依赖关系。当你调度Job时,Job.Schedule() 函数 将返回一个新的JobHandle,其中包括新的Job依赖项。

系统的组织

ECS 先按照 World 然后按照 Group 来组织系统。一般情况下,ECS 将使用一组预定义的组来创建默认的World。它找到所有可用的系统,实例化它们,并将它们添加到默认世界中的预设模拟组(simulation group)中。

你可以指定同一组中系统的Update顺序。Group 本身就是一组系统,因此,你可以像其他系统一样将Group添加到另一个Group中,并指定其顺序。Group中的所有系统在下一个系统或Group之前Update。如果未指定顺序,则系统将以确定性的方式而不是依赖于创建顺序插入到Update顺序中。也就是说,即使你没有明确的指定顺序,同一组系统总是在它们的Group内以相同的顺序进行Update。

系统的Update发生在主线程中。但是,系统可以使用Jobs将工作分流到其他线程。 有关系统的创建,更新顺序以及可用于组织系统的属性参考另一篇文章ECS —— 系统更新顺序

ECS Authoring

当在Unity Editor中创建游戏或应用时,你可以使用GameObjects 和 MonoBehaviours 以及创建Conversion System 来将那些UnityEngine对象和组件映射到实体上。

具体参见ECS —— Creating Gameplay


官方文档