组件是实体组件系统体系结构的三个主要元素之一。它们代表您的游戏或应用程序的数据。实体是为您的组件集合建立索引的标识符,而系统则提供了行为。
组件类型
在ECS中的组件是具有以下“标记接口”之一的结构体:
- IComponentData - 作为通用组件和块(Chunk)组件
- IBufferElementData - 将动态缓冲区与实体相关联
- ISharedComponentData - 按原型中的值对实体进行分类或分组,共享组件
- ISystemStateComponentData - 将系统特定状态与实体相关联,并检测何时创建或销毁单个实体,系统状态组件
- ISharedSystemStateComponentData - 共享状态和系统状态数据的组合,共享系统状态组件
- Blob assets - Blob assets原则上不是组件,但可以用来存储数据
继承不同的接口可将组件分成不同的类型,虽然大致的组件基本上都差不多,但是在一些细节方面,不同类型有细微的差别。
在阅读下面的内容之前,有必要解释一下在ECS架构中原型(Archetypeh)和块(Chunk)的概念。
块:所有实体的组件存放的内存块
原型:所有具有相同组件的块的内存块
上图很清晰的说明了原型和块的区别,已经ECS中存储的方式,值得一提的是,有些组件并不存放在块中,包括共享组件和块组件,ECS将他们存储在块外部,这是因为这些组件的的单个实例化对象适用于块中所有的实体。此外,你也可以选择将动态缓冲区存储在块之外。即使ECS不在块内,你在查询实体时通常也可以将他们与其他组件类型等同。
IComponentData
传统的Unity组件是面向对象的类,其中包含数据和方法。继承自IComponentData的是纯ECS组件,这意味着他没有定义任何方法,仅仅定义了数据,所以应该把IComponentData实现为结构体(Struct)而不是类,这意味着默认情况下,他是值类型而不是引用类型,要修改特定的值只能通过覆盖来修改。
var transform = group.transform[index]; // Read
transform.heading = playerInput.move; // Modify
transform.position += deltaTime * playerInput.move * settings.playerMoveSpeed;
group.transform[index] = transform; // Write
ISharedComponentData
继承自ISharedComponentData的组件是共享组件,共享组件顾名思义,许多的实体会共用一个共享组件。EntityManager会将具有相同共享组件的实体放置在同一块中,相同共享组件除了组件,数据也相同。显然,共享组件特别适合作为网格和材质来使用,相同物体拥有相同的网格和材质,这样可以节省大量的资源。
注意:过度使用共享组件,可能导致块利用率低下。此外,如果从实体中添加或删除共享组件,修改共享组件的值,都会导致EntityManager将实体移动到其他块。
关于共享组件的重要说明:
- ECS将具有相同共享组件的实体分类并存储在一个块中,但并没有和共享组件存放在一起,因此在实体中,共享组件的内存开销是0,但并不意味着共享组件不存在。
- 可以使用EntityQuery对具有相同组件的实体进行遍历,也可以使用EntityQuery.SetFilter对具有特定值的共享组件的实体进行迭代,因为这些实体存放在一个块中,因此开销很低。
- You can use
EntityManager.GetAllUniqueSharedComponents
to retrieve all uniqueSharedComponentData
that is added to any alive entities.(你可以用EntityManager.GetAllUniqueSharedComponents
来获得所有添加了共享组件的实体。ps:大概这样翻译?
) - 共享组件自动开启引用计数。
- 共享组件应该很少被修改,如果要修改共享组件的值,可以使用memcpy()将该实体的所有内容复制到另一个块中。
ISystemStateComponentData
系统状态组件可以用来跟踪系统内部的资源,并根据需要创建资源和销毁,而不依赖各种回调。
系统状态组件和通用组件类似,但是在实体销毁的时候ECS不会删除系统状态组件实体,这使得系统可以根据未删除的实体去回收与该实体绑定的资源。
何时使用系统状态组件
当系统可能需要保持基于内部组件的状态,例如分配资源时。系统可以将状态进行管理,当状态变化时,添加或删除组件。
IBufferElementData
动态缓冲区组件将类似数组的数据与实体相关联,动态缓冲区可以容纳可变数量的元素,并可以自动调整大小。
要创建动态缓冲区,首先声明一个继承IBufferElementData的结构体,并在该结构体中定义存储的元素。
public struct IntBufferElement : IBufferElementData
{
public int Value;
}
要向动态缓冲区中添加组件有两种方法
- 使用EntityManager.AddBuffer()
EntityManager.AddBuffer<MyBufferElement>(entity);
- 使用原型
Entity e = EntityManager.CreateEntity(typeof(MyBufferElement));
ISharedSystemStateComponentData
共享系统状态组件,可以看成是共享组件和系统状态组件的结合。
参考:https://docs.unity3d.com/Packages/com.unity.entities@0.11/manual/ecs_components.html