Unity C# Inheritance vs Composition 繼承與組件式設計之戰鬥系統經驗談
基於一些機緣而回憶之前身為初心者所掉過的坑,到底應該是使用繼承 (Inheritance) 抑或是組合 (Composition) 方式來設計遊戲中的戰鬥系統,哪一種比較適合持續變動的遊戲開發?
故事從菜鳥開始
一切的開始得從開發的巨獸浩劫 (Monter Eye) 遊戲回憶起,一款大型機台上的光槍遊戲,玩家在包廂機台內,控制槍枝硬體瞄準遊戲內的怪物,開槍射擊擊倒怪物,便隨著劇情起伏有著噴氣與機台平台震動等效果。
當初團隊在開發這款專案時,技術主力從 OGRE C++ 轉換到使用 Unity C# 開發,軟體團隊對於 Unity 也都只是剛起步學習,身為一位職場新鮮人,也是跟著從頭開始學習起。
一邊進行遊戲架構的開發設計,一點跟著團隊一起學習如何使用 Unity,想當然爾在沒有任何資深前輩可以傳承經驗的情況下,依賴過往在研究所以及大學的專案經驗,將遊戲企劃的設計需求拆解,大量使用物件繼承 (Object inheritance) 來設計整個戰鬥系統架構。
(如果要問為什麼一個職場菜鳥,剛開始就能負責接近遊戲核心的邏輯,主要原因是自找的,相信自己可以 Carry 全團隊,承接各種工作內容,細節可以見這篇文章:在鈊象遊戲業工作三年心得)
物件繼承 (Object inheritance) 設計
一開始,根據遊戲企劃的初始規劃設計出以下架構(這是一個演示的範例,實際上更加複雜),分別為一般怪物 Monster
以及魔王 Boss
:
經過企劃們幾次的討論與規格修改,修改關卡以及世界觀,又新增新的怪物類型,會游泳的魔王 SwimBoss
以及飛行的一般怪物 FlyMonster
,嘗試使用物件繼承來實作:
在展示給大老闆看後,發現游泳的遊戲效果很好,因此追加新的會游泳怪物,依賴物件繼承設計多出 SwimBoss
以及 SwimMonster
,開始產生錯亂,這兩個差別在哪裡:
又不知道哪個企劃突發奇想,想要追加游泳又會飛的一般怪物,原先的繼承設計都不知道該怎麼修改了:
但隨著專案時程的進程,漸漸發現遊戲企劃在每個階段後,都會冒出新的想法,規劃出新的需求,而這些要求在以物件繼承的設計架構裡,根本是難以修改與實現,十分痛苦……。
物件組合 (Object composition) 設計-採用 Components
得感謝主管帶我去參加 IGD Share 的分享會,從石川将光先生的分享中,認知到有新的設計可能,以及團隊的包容給我至少一個月的時間 (有點忘記確切花多少時間),重新翻修打掉之前花了之前至少六個月磨合的戰鬥系統,將原先採用物件繼承的設計架構,全部改用物件組合 (Object composition) 的設計架構,大量使用 Unity 組件設計模式 (Components pattern)。
把每一項功能設計成組件運作,利用多個組件在遊戲物件中 (GameObject) 來組合出想要的怪物敵人,不管是要游泳或是飛行,甚至要升級成 Boss,新增移除組件來達成,或是暫關閉開啟其組件 (MonoBehaviour.enabled = false
) 來達到暫時的能力效果:
之後專案新功能都採取如此設計,為了讓遊戲企劃編輯方便,也得為這些新功能加入客製化的 Unity 編輯器,當然使用繼承 (Inheritance) 的類別設計還是會有,但不像以前那樣的完全倚賴,使得程式開發較能夠跟上專案的時程,一直不斷得修改功能。
有設計過的組件,其修改彈性才會高,怎麼根據需求設計有彈性的組件架構,那又一個很長的故事與經驗,但大體還是依據高內聚與低耦合 (high cohesion and low coupling) 的原則來設計,這裡簡單筆記幾個點:
- 不會有互相耦合的組件 (耦合)
- 例如不該出現:組件 A 需要呼叫組件 B 播放動畫的函數,當組件 B 播放動畫完後,會呼叫組件 A 的函數通知動畫已結束
- 使用 event 或是 interface 來降低不同組件間的耦合度
- 單一組件功能過於太複雜 (內聚)
- 組件程式碼不會包山包海,什麼功能都會放在一起
- 過度設計 (Over-design)
- 一件功能不需要設計太多的組件來完成
- 例如:一個扣血的功能,設計需要數十個組件運作才能完成…
Reference
- IGD Share: Unity 進階遊戲開發研討會
- 有 Component 設計的範例解說 (t=1018)
- Unity帶來的新型遊戲開發方式 by 石川将光
沒有留言: