Table of Contents
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对象和组件映射到实体上。