ECS之Component組件

洪流學(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:同時包含ISharedComponentDataISystemStateComponentData中的功能。
  • 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è)置組件的值,可以使用 EntityManagerEntityCommandBuffer。由于組件是引用類型,因此與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)贊,收藏哦!

好,你可以走了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容