ET介紹——元件式設計(最佳化版的ECS)

Flamesky發表於2023-05-19

元件式設計

在程式碼複用和組織資料方面,物件導向可能是大家第一反應。物件導向三大特性繼承,封裝,多型,在一定程度上能解決不少程式碼複用,資料複用的問題。不過物件導向不是萬能的,它也有極大的缺陷:

1. 資料結構耦合性極強

一旦父類中增加或刪除某個欄位,可能要影響到所有子類,影響到所有子類相關的邏輯。這顯得非常不靈活,在一套複雜的繼承體系中,往父類中改變欄位會變得越來越麻煩,比方說ABC是D的子類,某天發現需要增加一個AB都有的資料,但是C沒有,那麼這個資料肯定不好放到父類中,只能將AB抽象出來一個父類E,E繼承於D,AB共有的欄位加到E中,一旦繼承結構發生了變化,可能介面也要改變,比方說之前有個介面傳入引數型別是E,當AB不再需要共用的那個欄位,那麼需要調整繼承關係,讓AB重新繼承D,那麼這個介面的傳入引數型別需要改成D,其中的邏輯程式碼很可能也要發生調整。更可怕的是遊戲邏輯變化非常複雜,非常頻繁,可能今天加了個欄位,明天又刪掉了,假如每次都要去調整繼承結構,這簡直就是噩夢。繼承結構面對頻繁的資料結構調整感覺很無力。

2. 難以熱插拔

繼承結構無法執行時增加刪除欄位,比如玩家Player平常是走路,使用坐騎後就騎馬。問題是坐騎的相關資訊就需要一直掛在Player物件上面。這就顯得很不靈活,我不騎馬的時候記憶體中為啥要有馬的資料?介面也有同樣的問題,一個類實現了一個介面,那麼這個介面就永遠粘在了這個類身上,你想甩掉她都不行,還是以騎馬為例,玩家Player可以進行騎行,那麼可能繼承一個騎行的介面,問題是,當我這個Player從坐騎上下來時,玩家Player身上還是有騎行的介面,根本沒法動態刪掉這個介面!可能例子舉得不是很對,但是道理表述的應該很清楚了。

使用物件導向可能導致災難性後果,遊戲開發中有新人有老人,有技術好的,有技術差的。人都是喜歡偷懶的,當你發現調整繼承關係麻煩的時候,有可能AB中增加一個欄位為了省事直接就放到父類D中去了。導致C莫名奇妙的多了一個無用的欄位。關鍵還沒法發現,最後導致父類D越來越大,到最後有可能乾脆就不用ABC了,直接讓所有物件都變成D,方便嘛!是的,很多遊戲就是這麼幹的,開發到最後根本就不管繼承關係了,因為想管也管不了了。

3. 方法與資料耦合

傳統物件導向都是class中帶有方法,並且特別提倡虛擬函式多型。方法跟資料放在一起帶來了特別多耦合的問題。為了解決這些耦合,大家想出了大量的設計模式,比如依賴介面,依賴轉置。說實話,這就是脫褲子放屁,為了解耦合,把類做成介面,然後繼承介面,難道這就不叫依賴了?這些做法導致,程式碼中到處是介面,程式碼閱讀極其困難。寫起程式碼來也沒有個標準,高手跟菜雞寫出來的程式碼完全是兩回事。大部分碼農都是邏輯仔,誰有時間天天想這個類要怎麼設計啊?隨著邏輯越來越複雜類裡面的方法將越來越龐大,可怕的是,這是這個類的方法,極其難以重構,很多專案中能看到類裡面存在上萬行程式碼的虛擬函式。天哪!

物件導向在面對複雜的遊戲邏輯時很無力,所以很多遊戲開發者又倒退了回去,使用程式導向進行開發遊戲,程式導向,簡單粗暴,不考慮複雜的繼承,不考慮抽象,不考慮多型,是開發屆的freestyle,挽起袖子就開擼,但同時,程式碼邏輯的複用性,資料的複用性也大大降低。程式導向也不是一種好的遊戲開發模式。

元件模式很好的解決了物件導向以及程式導向的種種缺陷,在遊戲客戶端中使用非常廣泛,Unity3d,虛幻4,等等都使用了元件模式。元件模式的特點: 1.高度模組化,一個元件就是一份資料加一段邏輯
2.元件可熱插拔,需要就加上,不需要就刪除
3.型別之間依賴極少,任何型別增加或刪除元件不會影響到其它型別。

但是目前只有極少有服務端使用了元件的設計,守望先鋒服務端應該是使用了元件的設計,守望先鋒的開發人員稱之為ECS架構,其實就是元件模式的一個變種,E就是Entity,C就是Component,S是System,其實就是將元件Component的邏輯與資料剝離,邏輯部分叫System,話題扯遠了,還是回到ET框架來把。

ET框架使用了元件的設計。一切都是Entity和Component,任何類繼承於Entity都可以掛載元件,例如玩家類:

public sealed class Player : Entity
{
    public string Account { get; private set; }
    public long UnitId { get; set; }
    
    public void Awake(string account)
    {
        this.Account = account;
    }
    
    public override void Dispose()
    {
        if (this.Id == 0)
        {
            return;
        }
        base.Dispose();
    }
}

 

給玩家物件掛載個移動元件MoveComponent,這樣玩家就可以移動了,給玩家掛上一個揹包元件,玩家就可以管理物品了,給玩家掛上技能元件,那麼玩家就可以施放技能了,加上Buff元件就可以管理buff了。

player.AddComponent<MoveComponent>();
player.AddComponent<ItemsComponent>();
player.AddComponent<SpellComponent>();
player.AddComponent<BuffComponent>();

 

元件是高度可以複用的,比如一個NPC,他也可以移動,給NPC也掛上MoveComponent就行了,有的NPC也可以施放技能,那麼給它掛上SpellComponent,NPC不需要揹包,那麼就不用掛ItemsComponent了

ET框架模組全部做成了元件的形式,一個程式也是由不同的元件拼接而成。比方說Loginserver需要對外連線也需要與伺服器內部進行連線,那麼login server掛上

// 內網網路元件NetInnerComponent,處理對內網連線  
Game.Scene.AddComponent<NetInnerComponent, string, int>(innerConfig.Host, innerConfig.Port);
// 外網網路元件NetOuterComponent,處理與客戶端連線
Game.Scene.AddComponent<NetOuterComponent, string, int>(outerConfig.Host, outerConfig.Port);

 

比如battle server就不需要對外網連線(外網訊息由gateserver轉發),那麼很自然的只需要掛載一個內網元件即可。 類似Unity3d的元件,ET框架也提供了元件事件,例如Awake,Start,Update等。要給一個Component或者Entity加上這些事件,必須寫一個輔助類。比如NetInnerComponent元件需要Awake跟Update方法,那麼新增一個這樣的類即可:

[ObjectEvent]
public class NetInnerComponentEvent : ObjectEvent<NetInnerComponent>, IAwake, IUpdate
{
    public void Awake()
    {
        this.Get().Awake();
    }

    public void Update()
    {
        this.Get().Update();
    }
}

 

這樣,NetInnerComponent在AddComponent之後會呼叫其Awake方法,並且每幀呼叫Update方法。 ET沒有像Unity使用反射去實現這種功能,因為反射效能比較差,而且這樣實現的好處是這個類可以放到熱更dll中,這樣元件的Awake Start,Update方法以及其它方法都可以放到熱更層中。將Entity和Component做成沒有方法的類,方法都放到熱更層,方便熱更修復邏輯bug。

元件式開發最大的好處就是不管菜鳥還是高手,開發一個功能都能很快的知道怎麼組織資料怎麼組織邏輯。可以完全放棄物件導向。使用物件導向開發最頭疼的就是我該繼承哪個類呢?之前做過最恐怖的就是虛幻三,虛幻三的繼承結構非常多層,完全不知道自己需要從哪裡開始繼承。最後可能導致一個非常小的功能,繼承了一個及其巨大的類,這在虛幻三開發中屢見不鮮。所以虛幻4改用了元件模式。元件模式的模組隔離性非常好,技術菜鳥某個元件寫得非常差,也不會影響到其它模組,大不了重寫這個元件就好了。

ET的元件設計有所創新,方法跟資料分離,完全解除耦合,不用絞盡腦汁去想怎麼解除耦合,隨意寫靜態方法即可,根本不存在耦合,即使是菜鳥寫的程式碼也很容易重構。

正是因為ET使用了可拆卸的元件模式,ET可以將所有伺服器元件都裝到同一個程式上,那麼這一個程式就可以當作一組分散式伺服器使用。從此用vs除錯分散式伺服器成為了可能。正因為這樣,平常開發只使用一個程式,釋出的時候釋出成多個程式就行了。說實在的,不是吹牛,這是一個偉大的發明,這一發明解決了分散式遊戲伺服器開發中的大大大難題,極大的提高了開發效率。

ET開源地址地址:egametang/ET: Unity3D Client And C# Server Framework (github.com)   qq群:474643097

相關文章