前幾天寫了《開源分享 Unity3d客戶端與C#分佈式服務端遊戲框架》,受到不少人關注,QQ羣幾天就加了80多我的。開源這個框架的主要目的也是分享本身設計ET的一些想法,因此我準備寫一系列的文章,介紹下本身的思路跟設計,每篇一個主題,此次介紹的是組件設計。html
在代碼複用和組織數據方面,面向對象多是你們第一反應。面向對象三大特性繼承,封裝,多態,在必定程度上能解決很多代碼複用,數據複用的問題。不過面向對象不是萬能的,它也有極大的缺陷:git
一旦父類中增長或刪除某個字段,可能要影響到全部子類,影響到全部子類相關的邏輯。這顯得很是不靈活,在一套複雜的繼承體系中,往父類中改變字段會變得愈來愈麻煩,比方說ABC是D的子類,某天發現須要增長一個AB都有的數據,可是C沒有,那麼這個數據確定很差放到父類中,只能將AB抽象出來一個父類E,E繼承於D,AB共有的字段加到E中,一旦繼承結構發生了變化,可能接口也要改變,比方說以前有個接口傳入參數類型是E,當AB再也不須要共用的那個字段,那麼須要調整繼承關係,讓AB從新繼承D,那麼這個接口的傳入參數類型須要改爲D,其中的邏輯代碼極可能也要發生調整。更可怕的是遊戲邏輯變化很是複雜,很是頻繁,可能今天加了個字段,明天又刪掉了,假如每次都要去調整繼承結構,這簡直就是噩夢。繼承結構面對頻繁的數據結構調整感受很無力。
繼承結構沒法運行時增長刪除字段,好比玩家Player日常是走路,使用坐騎後就騎馬。問題是坐騎的相關信息就須要一直掛在Player對象上面。這就顯得很不靈活,我不騎馬的時候內存中爲啥要有馬的數據?接口也有一樣的問題,一個類實現了一個接口,那麼這個接口就永遠粘在了這個類身上,你想甩掉她都不行,仍是以騎馬爲例,玩家Player能夠進行騎行,那麼可能繼承一個騎行的接口,問題是,當我這個Player從坐騎上下來時,玩家Player身上仍是有騎行的接口,根本無法動態刪掉這個接口!可能例子舉得不是很對,可是道理表述的應該很清楚了。
使用面向對象可能致使災難性後果,遊戲開發中有新人有老人,有技術好的,有技術差的。人都是喜歡偷懶的,當你發現調整繼承關係麻煩的時候,有可能AB中增長一個字段爲了省事直接就放到父類D中去了。致使C莫名奇妙的多了一個無用的字段。關鍵還無法發現,最後致使父類D愈來愈大,到最後有可能乾脆就不用ABC了,直接讓全部對象都變成D,方便嘛!是的,不少遊戲就是這麼幹的,開發到最後根本就無論繼承關係了,由於想管也管不了了。github
面向對象在面對複雜的遊戲邏輯時很無力,因此不少遊戲開發者又倒退了回去,使用面向過程進行開發遊戲,面向過程,簡單粗暴,不考慮複雜的繼承,不考慮抽象,不考慮多態,是開發屆的freestyle,挽起袖子就開擼,但同時,代碼邏輯的複用性,數據的複用性也大大下降。面向過程也不是一種好的遊戲開發模式。c#
組件模式很好的解決了面向對象以及面向過程的種種缺陷,在遊戲客戶端中使用很是普遍,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