洪流学堂,让你快人几步。你好,我是跟着大智学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
:同时包含ISharedComponentData
和ISystemStateComponentData
中的功能。 -
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<T>
接口并覆盖Object.GetHashCode()
方法。此外,出于序列化的目的,托管组件需要有默认无参构造方法。
你必须在主线程上设置组件的值,可以使用 EntityManager
或EntityCommandBuffer
。由于组件是引用类型,因此与ISharedComponentData不同,你可以更改组件的值而无需在块之间移动实体。(之前咱们知道修改ISharedComponentData的值会导致entity所在的内存块发生变化)
但是,尽管在逻辑上将托管组件与值类型的组件分开存储,但它们都会有EntityArchetype
原型定义。这样,向实体添加新的托管组件仍然会导致ECS创建新的原型(如果尚不存在匹配的原型),并将实体移至新的Chunk。
总结
既然用了ECS,那就从一开始忘掉MonoBehaviour的组件的写法,拥抱ECS吧!
扩展阅读
【扩展学习】在洪流学堂公众号回复
DOTS
可以阅读本系列所有文章,更有视频教程等着你!
呼~ 今天小新絮絮叨叨的真是够够的了。没讲清楚的地方欢迎评论,咱们一起探索。
我是大智(vx:zhz11235),你的技术探路者,下次见!
别走!点赞,收藏哦!
好,你可以走了。
网友评论