Unity3d&C#分布式游戲服務器ET框架介紹-組件式設計



前幾天寫了《開源分享 Unity3d客戶端與C#分布式服務端游戲框架》,受到很多人關注,QQ群幾天就加了80多個人。開源這個框架的主要目的也是分享自己設計ET的一些想法,所以我准備寫一系列的文章,介紹下自己的思路跟設計,每篇一個主題,這次介紹的是組件設計。

在代碼復用和組織數據方面,面向對象可能是大家第一反應。面向對象三大特性繼承,封裝,多態,在一定程度上能解決不少代碼復用,數據復用的問題。不過面向對象不是萬能的,它也有極大的缺陷:

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,方便嘛!是的,很多游戲就是這么干的,開發到最后根本就不管繼承關系了,因為想管也管不了了。

面向對象在面對復雜的游戲邏輯時很無力,所以很多游戲開發者又倒退了回去,使用面向過程進行開發游戲,面向過程,簡單粗暴,不考慮復雜的繼承,不考慮抽象,不考慮多態,是開發屆的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可以將所有服務器組件都裝到同一個進程上,那么這一個進程就可以當作一組分布式服務器使用。從此用vs調試分布式服務器成為了可能。正因為這樣,平常開發只使用一個進程,發布的時候發布成多個進程就行了。說實在的,不是吹牛,這是一個偉大的發明,這一發明解決了分布式游戲服務器開發中的大大大難題,極大的提高了開發效率。

代碼地址:https://github.com/egametang/Egametang
QQ群:474643097


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM