Unity ECS系列(三)组件Component

Posted by LioMiss on July 17, 2022 阅读()

ECS组件不同于Unity组件,主要区别如下: | ECS 组件 | Unity 组件 | | :—– | :————– | | 通常是结构体实例,非托管类型,也可以是类的实例 | 类的实例 | | 与一个实体关联(如果是共享组件或块组件的话会与多个实体关联) | 由gameObject包含 | |通常不包括行为(方法) | 通常包括行为 | |实现以下接口之一: IComponentData ISharedComponentData ISystemStateComponentData ISystemStateSharedComponentData IBufferElementData | 继承自Component |

非托管IComponentData组件

IComponentData将结构体标记为非托管类型,这是最常见的组件。
一个IComponentData结构的字段只能是以下类型:

  • Blittable types
  • bool
  • char
  • BlobAssetReference
  • Collections.FixedString
  • Collections.FixedList
  • Fixed array
  • 符合这些限制的其他结构

托管IComponentData组件

IComponentData将类标记为托管类型,相比与托管类型用得少得多。
IComponentData类的字段可以是任何类型,包括NativeContainer或任何托管类型,与非托管组件不同,托管组件:

  • 在Jobs中无法访问
  • 不能在Burst编译代码中使用
  • 需要垃圾回收
  • 必须是默认可构造的

由于它们的性能缺陷,能用非托管组件就用非托管组件。

原型和块

实体的组件类型集被称为它的原型。
实体及其组件存储在成为块的内存块中,块只能存储相同原型的实体。

  • 每个原型==0个或多个块
  • 每个块==一个或多个实体

每个块的大小为16KiB,分为并行数组:每个组件类型一个,另外还有一个用于存储实体ID的数组,块的第一个实体的ID和组件存储在这些数组的索引0处;块的第二个实体存储在索引1处;第三个实体存储在索引2处,以此类推。
块的存储由ECS系统管理,并将实体放在与其原型相匹配的块中,你无法直接控制块的创建和销毁,只能在需要创建或销毁实体或添加删除实体组件时告诉系统。
存储在块中的实体的数量取决于组件类型的数量和大小,例如:如果组件A、B、C的大小加起来最多92字节,那么此原型的单个实体需要100字节的存储空间(包括实体ID的8字节),因此这个原型的一个块可以存储约163个实体(16384字节除以100)。
块头包含块中实体的当前计数,块的数组的已占用插槽总是在空插槽之前,没有间隙穿插:

  • 当一个Entity被添加到一个块中时,它将被放置在第一个空闲槽中。
  • 当从块中删除一个实体时,块中的最后一个实体将被移动以填补空白。

添加或删除组件会改变实体的原型,实体会因此被移动到与其新原型相匹配的不同块中。
XgGUud.png

标记组件

没有字段的IComponentData结构成为标记组件,因为标记组件没有数据,所以块不存储其标记组件的组件数组。

托管组件存储

与非托管组件不同,托管组件不直接存储在块中,相反,托管组件类实例都是在一个大数组中,为整个World引用,块中的托管类型组件数组只是将索引存储到改数组中,访问实体的托管组件需要额外的索引查找,这使得托管组件的性能不如非托管组件。

在实体中添加和删除组件

在主线程,可以使用World的EntityManager添加和删除World实体的组件,如上所述,添加和删除组件会更改实体的原型,这意味着必须将实体移动到不同的块。
结构变化包括:

  • 创建一个块
  • 销毁一个块
  • 向块中添加实体
  • 从块中删除实体
  • 设置实体的ISharedComponent数据

但是,设置实体的IComponentData值不被认为是结构性更改。
结构更改只能在主线程上执行,而不能从Jobs执行。解决方法是在作业中使用EntityCommandBuffer来标记更改,之后再在主线程上“回访”EntityCommandBuffer,以执行记录的更改。

读取和写入实体的组件值

读写单个组件

如果需要一次读写一个实体的单个组件,可以在主线程上请求EntityManager读取或写入单个实体的组件值。

读写多个组件

大多数情况下需要读写块中所有实体的组件:

  • ArchetypeChunk允许您直接读写块的组件数组
  • EntityQuery可以有小弟检索与查询匹配的块集
  • Entity.ForEach可以方便地为您处理EntityQuery的创建和使用,同时还可以更方便地在主线程或Jobs中迭代块的实体

延迟更改组件的值

某些情况下可能需要延迟更改组件值,可以使用EntityCommandBuffer来记录写入组件值的意图,这些更改只有在稍后在主线程上回访EntityCommandBuffer时才会生效。

尽可能使用小组件

将数据分割成许多小型组件类型是最佳策略,事实上,只有一个字段的组件是常态,但是有时可能会考虑将多个组件合并到一起。

避免往组件中添加方法

与GameObject组件不同,ECS组件的目的是只有数据,而不是代码,所以通常应该将代码放在系统的方法中,而不是ECS组件的方法中。但是,可以给ECS组件提供小访问器方法。