ECS之Component组件

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

Entity之后,咱们来到了Component组件。Component是ECS架构的三个基本元素之一。Component组件中包含了游戏或者应用的数据。Entity是组件集合的索引,System包含具体的逻辑行为。

ECS之Component组件

ECS中的组件是一个结构体,需要实现下列之一的接口:

  • IComponentData:最基本的,也是使用最多的接口,没有特殊需求的组件都可以实现这个接口。用于通用的目的和chunk components。
  • IBufferElementData:给entity关联dynamic(动态) buffer
  • ISharedComponentData:可以通过数据来对entity的原型进行分组
  • ISystemStateComponentData:给entity关联一个系统的状态,可以检测entity何时被创建或者销毁
  • ISharedSystemStateComponentData:同时包含ISharedComponentDataISystemStateComponentData中的功能。
  • Blob assets:技术上来说不是一个component,你可以使用blob assets存储数据。Blob asset可以被一个或者多个component通过BlobAssetReference只读引用。你可以使用blob asset共享数据,也可以在C# job中访问。

之前在ECS核心概念那一节说过,EntityManager会使用原型(archetype)来组织不同的component组合。相同原型的entity在物理内存上都在一起,叫做一个内存块。同一个内存块中都是相同的组件原型。

上面这个图说明了ECS是如何根据原型来存储数据的。

下面说说例外情况

  • Shared components和chunk components是例外情况,ECS不在chunk中存储他们。
  • 你可以在chunk之外存储dynamic buffer

虽然ECS不在内存块中存储上面类型的组件,但是进行实体查询的时候,你还是可以使用同样的方式对待他们。

通用目的的组件

Unity的ECS中的ComponentData是一个结构体,仅仅包含entity的实例数据。ComponentData不能包含方法,程序所有的逻辑需要写在System中。ComponentData有点类似于面向对象的Unity中,一个Component仅仅包含成员变量,但是没有成员方法、属性等等。

ECS的API提供了一个叫IComponentData的接口,你可以使用结构体实现这个接口来声明一个通用目的的组件类型。

IComponentData

传统的Unity组件是面向对象的MonoBehaviour类,同时会包含数据和方法。IComponentData是一个纯ECS风格的组件,只有数据,没有方法。你应该使用结构体来实现IComponentData接口,结构体在传递的时候会使用值传递,需要注意值传递在修改数据的时候有些不同,需要按照类似下面的代码对数据进行修改:

var transform = group.transform[index]; // 读取

transform.heading = playerInput.move; // 修改
transform.position += deltaTime * playerInput.move * settings.playerMoveSpeed;

group.transform[index] = transform; // 写入

IComponentData结构体中不能包含引用到托管对象的引用类型。因为ComponentData存在没有GC的内存块中,对性能有很大提升。

【选学】托管IComponentData

使用托管IComponentData(即,IComponentData使用class而不是struct进行声明)有助于将现有代码零散地移植到ECS,与托管数据进行交互或者为数据布局进行原型设计,ISharedComponentData不能和托管数据交互。

这些组件的使用方式与值类型IComponentData相同。但是,ECS在内部以完全不同(而且比较慢)的方式来处理它们。如果不需要托管组件支持,可以在Player Settings里面(Edit > Project Settings > Player > Scripting Define Symbols)设置UNITY_DISABLE_MANAGED_COMPONENTS宏来禁用它。

由于托管IComponentData是托管类型,因此与值类型IComponentData相比,它具有以下性能缺点:

  • 不能与Burst编译器一起使用
  • 不能在Job结构体中使用
  • 它不能使用块内存
  • 需要垃圾回收

你应该尽可能不用托管组件,并尽可能使用blittable类型。

托管IComponentData必须实现IEquatable接口并覆盖Object.GetHashCode()方法。此外,出于序列化的目的,托管组件需要有默认无参构造方法。

你必须在主线程上设置组件的值,可以使用 EntityManagerEntityCommandBuffer。由于组件是引用类型,因此与ISharedComponentData不同,你可以更改组件的值而无需在块之间移动实体。(之前咱们知道修改ISharedComponentData的值会导致entity所在的内存块发生变化)

但是,尽管在逻辑上将托管组件与值类型的组件分开存储,但它们都会有EntityArchetype原型定义。这样,向实体添加新的托管组件仍然会导致ECS创建新的原型(如果尚不存在匹配的原型),并将实体移至新的Chunk。

总结

既然用了ECS,那就从一开始忘掉MonoBehaviour的组件的写法,拥抱ECS吧!

扩展阅读

【扩展学习】洪流学堂公众号回复DOTS可以阅读本系列所有文章,更有视频教程等着你!


呼~ 今天小新絮絮叨叨的真是够够的了。没讲清楚的地方欢迎评论,咱们一起探索。

我是大智(vx:zhz11235),你的技术探路者,下次见!

别走!点赞收藏哦!

好,你可以走了。

你可能感兴趣的:(ECS之Component组件)