Unity客戶端框架筆記(狀態模式和策略模式在遊戲中的應用)

        最近花了幾天時間梳理了一下新遊戲的客戶端框架,雖然本身就有相對明確的方向,但是在一開始寫的時候還是有些混亂,不過最終梳理完成後,個人感覺代碼清爽很多。

        這篇文章不是設計模式的教學,而是自己的一些想法和實踐,我把代碼梳理成自己喜歡的結構,保證邏輯和結構的清晰,但是這並不意味者它是符合所有人習慣的。

        我之前有寫過一兩篇文章討論客戶端的結構,也吐槽過一些其他人的設計。可以說我在寫代碼之初就有一個相對明確的方向,多年的經驗也可以告訴我什麼樣的代碼是漂亮的,什麼樣的代碼是有壞味道的。

        首先我把客戶端結構分成三層,一層爲顯示層Display,它處理遊戲最核心的戰鬥模塊,如ARPG中的場景、角色、技能表現都是在這一層處理。一層爲邏輯層Logic,它處理遊戲的各個系統,如資源管理、角色管理、任務管理等等都是放在這一層處理。一層爲UI層,它就是由各個界面所組成的。

        各個層之間沒有嚴格的劃分,比如邏輯層內是各個單體管理器,顯示層也會有一個如Game或者BattleScene這樣的單體類,UI層會有一個UIManager來管理。但是如果各層相互穿插可能造成客戶端的混亂。所以做了一些界限的劃分。UI層可以調用邏輯層和顯示層的東西,各個界面之間不運行相互調用,而是通過事件來交互。邏輯層幾乎只處理邏輯,不調用另外兩個層的東西。 顯示層可以調用邏輯層的管理者來獲取數據或者與服務器通信。

        以上爲大的框架或者思想,這個並不是很多人會提到的MVC框架,而是根據開發經驗總結出來的一套類似的東西。反正思想是一致的,那就是通過合理的層次劃分達到邏輯和顯示的分離,當一個地方修改的時候,不會影響到其他地方。這個就是“開閉原則”。雖然面向對象的一些原則非常教條,但是確實是代碼設計的指導方向。

        在框架確定的基礎上,還有一些細節的設計需要考量一下,基本上下面提到的一些問題都是各有優劣,所以我只作描述,不做選擇,具體看個人的習慣。

        1、是要一個全局公共的DataCenter來保存數據,還是將數據分散到各個管理器內部?  使用公共的DataCenter來保存數據,可以避免各個管理器之間的相互訪問,所有管理器都是從DataCenter來取數據,管理器之間通過事件來交互。但是不可避免的DataCenter會隨着時間的增長變得越來越龐大。  而管理器持有數據,則可能會造成各個管理者之間的相互訪問。如玩家要卸下身上裝備的武器,就要先從物品或者揹包管理器裏面獲取揹包剩餘空間,這樣兩個管理器就相互依賴了。

        2、消息是每個消息一個文件來處理其自身邏輯,還是註冊一個id給對應的管理者來處理消息數據? 

        3、是否要保持邏輯管理者之間的獨立性,以保證去掉某一個管理者,遊戲仍然可以編譯並運行?


        在上面這些結構問題梳理清楚後,就開始遊戲最核心的角色和戰鬥系統的設計了。這裏體現了開發者抽象的能力,如果抽象能力很好,就可以一步到位,否則就要看重構的能力了。

        我的設計如下(請原諒我不會畫uml圖,就純口述了。。。):

        角色是一個繼承自MonoBehaviour的類,基類爲Unit(也可以叫Actor或者Role),其下會有一些列的派生類如Player  NPC  Hero  Monster等等。  這些是最基本的角色。

        每個角色會持有一個data數據,如PlayerData   NPCData  HeroData等等,這些是邏輯層的東西,是純數據處理的,根據服務器的消息創建。如玩家的id,血量,攻擊力等等都是在這裏處理。

        Unit內部會管理一些列的Component,它們描述了某件事情該怎麼做,比如會有MoveComponent  AttackComponent  AnimationComponent   HUDComponent等等,這些Component會派生出一些列的子類,來具體實現一個行爲,如AttackComponent可以有MeleeAttackComponent  RangeAttackComponet等等。 Player和Monster可以通過這些Component組合成自己想要實現的具體效果。而當實現需要修改的時候可以只關注Component內部,添加一個新的行爲也僅僅是添加一個新的Component。同時Component是可以共享的,如Player和Hero很多Component是一樣的,但是HUDComponet不一樣,這個就是組合比繼承優越的地方。 這個設計模式就是策略模式。它通過封裝易變的算法,達到從容應對變化的效果。

        與策略模式非常類似的是狀態模式。有一些時候它們之間的界限也並不是非常明確,某些套路既可以說成是一種策略,也可以說成是一種狀態。比如搜尋敵人,這個可以是一個行爲,也可以是一個搜尋敵人的狀態(可以考慮分層狀態機,這樣就可以一邊跑一邊搜尋敵人,狀態之間可以並行)。

         我這邊的處理是每個角色類會對應一些列的狀態實現,如Player有PlayerIdleState  PlayerMoveState  PlayerAttackState,而Monster有MonsterIdleState  MonsterMoveState,State可以有公共的基類如UnitMoveState,也可以沒有,這個就看Player和Monster之間狀態的相似度有多高,如果有大量重複的代碼的話,那麼就考慮有一個基類。

         狀態模式描述了一種狀態之間的流轉,比如進入Idle狀態應該做什麼,什麼時候要進入Move狀態,通過狀態劃分,可以保證角色在某一個時刻處於一個確定的狀態,而此狀態的邏輯處理都在該狀態類裏面完成。這樣就不會有各種混亂(比如在Update內部寫各種IsInState(xxxx)),達到解耦合的目的。同時狀態之間的流轉也可以清晰的表達出來,我們的邏輯也會清晰很多。狀態模式的具體實現就是有限狀態機,它是實現AI非常重要的手段。

        總結一下我的實現就是 角色持有一些列的行爲,以及一些列的狀態,通過行爲描述具體做什麼,通過狀態描述怎麼做。這樣就可以把原本都寫在Player內部的代碼分散出來。而這個分散並不會使我們寫代碼更加麻煩,而是會讓我們的邏輯更加清晰,代碼更加容易閱讀。 如果沒有達到這樣的效果,而是感覺如果這些代碼都寫在Player內部可以更加方便的調用,那麼就一定是某個環節出了問題。

         我個人並不是很推崇設計模式,總是標榜自己是實用主義,但是回過頭來看的話,我們認爲漂亮的設計很多時候就是一種模式。幾十個設計模式我們可能只熟悉其中最常用的幾個就受用無窮了。真正在開發中吃透這幾個模式,纔可以真正瞭解面向對象、封裝,也纔會瞭解什麼是漂亮的代碼,什麼是優雅的設計。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章