
洪流學(xué)堂,讓你快人幾步。你好,我是跟著大智學(xué)Unity的萌新,我叫小新,最近在跟著大智學(xué)習(xí)DOTS。
Entity之后,咱們來到了Component組件。Component是ECS架構(gòu)的三個基本元素之一。Component組件中包含了游戲或者應(yīng)用的數(shù)據(jù)。Entity是組件集合的索引,System包含具體的邏輯行為。
ECS之Component組件
ECS中的組件是一個結(jié)構(gòu)體,需要實(shí)現(xiàn)下列之一的接口:
-
IComponentData:最基本的,也是使用最多的接口,沒有特殊需求的組件都可以實(shí)現(xiàn)這個接口。用于通用的目的和chunk components。 -
IBufferElementData:給entity關(guān)聯(lián)dynamic(動態(tài)) buffer -
ISharedComponentData:可以通過數(shù)據(jù)來對entity的原型進(jìn)行分組 -
ISystemStateComponentData:給entity關(guān)聯(lián)一個系統(tǒng)的狀態(tài),可以檢測entity何時被創(chuàng)建或者銷毀 -
ISharedSystemStateComponentData:同時包含ISharedComponentData和ISystemStateComponentData中的功能。 -
Blob assets:技術(shù)上來說不是一個component,你可以使用blob assets存儲數(shù)據(jù)。Blob asset可以被一個或者多個component通過BlobAssetReference只讀引用。你可以使用blob asset共享數(shù)據(jù),也可以在C# job中訪問。
之前在ECS核心概念那一節(jié)說過,EntityManager會使用原型(archetype)來組織不同的component組合。相同原型的entity在物理內(nèi)存上都在一起,叫做一個內(nèi)存塊。同一個內(nèi)存塊中都是相同的組件原型。

上面這個圖說明了ECS是如何根據(jù)原型來存儲數(shù)據(jù)的。
下面說說例外情況:
- Shared components和chunk components是例外情況,ECS不在chunk中存儲他們。
- 你可以在chunk之外存儲dynamic buffer
雖然ECS不在內(nèi)存塊中存儲上面類型的組件,但是進(jìn)行實(shí)體查詢的時候,你還是可以使用同樣的方式對待他們。
通用目的的組件
Unity的ECS中的ComponentData是一個結(jié)構(gòu)體,僅僅包含entity的實(shí)例數(shù)據(jù)。ComponentData不能包含方法,程序所有的邏輯需要寫在System中。ComponentData有點(diǎn)類似于面向?qū)ο蟮腢nity中,一個Component僅僅包含成員變量,但是沒有成員方法、屬性等等。
ECS的API提供了一個叫IComponentData的接口,你可以使用結(jié)構(gòu)體實(shí)現(xiàn)這個接口來聲明一個通用目的的組件類型。
IComponentData
傳統(tǒng)的Unity組件是面向?qū)ο蟮腗onoBehaviour類,同時會包含數(shù)據(jù)和方法。IComponentData是一個純ECS風(fēng)格的組件,只有數(shù)據(jù),沒有方法。你應(yīng)該使用結(jié)構(gòu)體來實(shí)現(xiàn)IComponentData接口,結(jié)構(gòu)體在傳遞的時候會使用值傳遞,需要注意值傳遞在修改數(shù)據(jù)的時候有些不同,需要按照類似下面的代碼對數(shù)據(jù)進(jìn)行修改:
var transform = group.transform[index]; // 讀取
transform.heading = playerInput.move; // 修改
transform.position += deltaTime * playerInput.move * settings.playerMoveSpeed;
group.transform[index] = transform; // 寫入
IComponentData結(jié)構(gòu)體中不能包含引用到托管對象的引用類型。因?yàn)?code>ComponentData存在沒有GC的內(nèi)存塊中,對性能有很大提升。
【選學(xué)】托管IComponentData
使用托管IComponentData(即,IComponentData使用class而不是struct進(jìn)行聲明)有助于將現(xiàn)有代碼零散地移植到ECS,與托管數(shù)據(jù)進(jìn)行交互或者為數(shù)據(jù)布局進(jìn)行原型設(shè)計,ISharedComponentData不能和托管數(shù)據(jù)交互。
這些組件的使用方式與值類型IComponentData相同。但是,ECS在內(nèi)部以完全不同(而且比較慢)的方式來處理它們。如果不需要托管組件支持,可以在Player Settings里面(Edit > Project Settings > Player > Scripting Define Symbols)設(shè)置UNITY_DISABLE_MANAGED_COMPONENTS宏來禁用它。
由于托管IComponentData是托管類型,因此與值類型IComponentData相比,它具有以下性能缺點(diǎn):
- 不能與Burst編譯器一起使用
- 不能在Job結(jié)構(gòu)體中使用
- 它不能使用塊內(nèi)存
- 需要垃圾回收
你應(yīng)該盡可能不用托管組件,并盡可能使用blittable類型。
托管IComponentData必須實(shí)現(xiàn)IEquatable<T>接口并覆蓋Object.GetHashCode()方法。此外,出于序列化的目的,托管組件需要有默認(rèn)無參構(gòu)造方法。
你必須在主線程上設(shè)置組件的值,可以使用 EntityManager或EntityCommandBuffer。由于組件是引用類型,因此與ISharedComponentData不同,你可以更改組件的值而無需在塊之間移動實(shí)體。(之前咱們知道修改ISharedComponentData的值會導(dǎo)致entity所在的內(nèi)存塊發(fā)生變化)
但是,盡管在邏輯上將托管組件與值類型的組件分開存儲,但它們都會有EntityArchetype原型定義。這樣,向?qū)嶓w添加新的托管組件仍然會導(dǎo)致ECS創(chuàng)建新的原型(如果尚不存在匹配的原型),并將實(shí)體移至新的Chunk。
總結(jié)
既然用了ECS,那就從一開始忘掉MonoBehaviour的組件的寫法,擁抱ECS吧!
擴(kuò)展閱讀
【擴(kuò)展學(xué)習(xí)】在洪流學(xué)堂公眾號回復(fù)
DOTS可以閱讀本系列所有文章,更有視頻教程等著你!
呼~ 今天小新絮絮叨叨的真是夠夠的了。沒講清楚的地方歡迎評論,咱們一起探索。
我是大智(vx:zhz11235),你的技術(shù)探路者,下次見!
別走!點(diǎn)贊,收藏哦!
好,你可以走了。