隨着游戲開發的完整度提升,技能系統的設計復雜性也越來越高,導致了用模板方式的配置方法和處理方法會導致以下幾個問題:
- 代碼冗余
- 排錯困難
- 配置項冗余
- 熟悉業務流程時間長
- 擴展性低
經過我思考決定重寫之。分析以下幾個觀點,由於早期設計上的局限,和實際開發預期的不符,技能系統也必然會成為策划腦洞大開的一個點,並且也會成為MOBA游戲體驗的深度核心項之一。於是一個成熟的MOBA技能系統應該包含一下幾點:
- 代碼流程清晰
- 錯誤定位精確
- 配置項定位精確
- 熟悉業務流程時間短
- 擴展性強
應該還有一些我沒有想到或者沒有記錄到的點,在此就說明以上幾個。
有一些程序在設計一些高擴展性同時又是核心要素的系統時,不出意外的也會遇到以上的幾個問題。
這里的核心關鍵就是:
在設計之初對未來的需求是未知且不可預測的。
那么我是怎么解決以上的幾個問題的呢?
因為我一直在使用 Unity 做前端開發。深知Unity的ECS (實體組件系統) 架構體系帶來的便利。
於是我打算根據ECS的架構方式的模子去設計,但不完全根據ECS的架構來,保持對具體項目需求的貼切。
於是我設計了如下三個層:
- 流程控制層
- 原子函數層(技能)
- 邏輯層(技能)
有時候我認為這個設計很像行為樹。好吧確實有點像,但又不是那么像。這里不深究,留疑。核心還是留在解決需求上。
流程控制層具備以下幾個組件:
- CtrlBase -> 作為所有流程控制的基類
- CtrlBreak -> 用於中斷所有流程控制組件
- CtrlCondition -> 用於流程控制中的分支操作
- CtrlDelayTime -> 用於流程控制中的延時執行操作
- CtrlDuration -> 用於在一段時間內執行一組動作
- CtrlSequence -> 用於序列執行一組動作
- CtrlTimeLine 組件 -> 用於創建一個時間軸,讓所有流程組件在這個時間軸上執行
原子函數層(技能)具備以下幾個組件:
- SkillCondition -> 提供技能條件的判定
- SkillEntity -> 提供技能實體的操作
- SkillFightObjMap -> 提供戰斗對象查詢獲取等操作
- SkillInOutValueToPlayer -> 提供對戰斗對象角色的數值輸入輸出
- SkillPlayuerCtrl -> 提供對角色的控制操作
- SkillTarget -> 提供技能對象的具體信息
邏輯層(技能):
- SkillBase -> 所有邏輯層的基類 定義所有的技能邏輯層數據
- Hero1/skill_1 -> 英雄1的技能1邏輯對象
- Hero2/skill_2 -> 英雄2的技能2邏輯對象
- ...類推
從以上的結構中可以直接看出目前的冗余層在邏輯層,而邏輯層的支撐在控制層和原子函數層,隨着開發深度的越來越高,控制層和原子函數層的操作組件會越來越多或功能性越來越強。則為邏輯層提供的操作/組合方式更為豐富,則可實現的動作會更為強大。
並且從排錯來說只要控制層和原子層確保無誤(實際也必須無誤),基本錯誤定位能直接找到對應技能的邏輯層,且邏輯層沒有多余代碼,每一句都和技能的具體邏輯有所關聯,線性排錯。難度低。如若錯誤出在原子或控制層則是一批技能同時出現問題,也好定位。
所以這里已經做到代碼流程清晰,錯誤定位精確。
同時因為每個技能有獨立邏輯層則配置的定義也可以獨立,這里也做到了配置項定位精確。
基於以上幾點精確定位的特性導致熟悉業務的時間就因此變短了。
又因為原子函數層/控制層的支持性可擴展,具體技能的業務邏輯可定制,所以擴展性強了。
基於這種設計模式,帶來的好處可見是非常大的,但是同時也導致了必須要讓程序長期來維護或定制具體的技能模塊。所以我一直認為策划是可以具備一些腳本功底的,只要我們保證原子函數層和控制層提供的是安全接口。則可以對策划放開腳本編寫,甚至可以用弱類型解析類腳本語言來提供具體的技能邏輯定制。
還有一種方案是開發一款能夠生成邏輯層的流程編輯器,將原子函數層和控制層反射導入。生成邏輯層代碼。不過這個成本太高而且規則不好定制。有可能還沒程序直接編程性價比更高。所以我沒有選擇開發流程編輯器的方式。
這個架構在我實際運用中,感覺還是非常好的,因為大多的技能其實相似性還是蠻高的,如果技能難度不高,原子函數不需要迭代添加,則進行邏輯組合的時候實際效率很高。我剛剛開發完這套系統,重構現有的技能(10個左右)也就花了3個小時左右吧。相比模板開發的方式我認為在定制和排錯擴展的方面效率要高的多,而且對開發者的友好度更高。
總結下來我認為所有的設計都應該建立在更貼切實際開發需求上,我認為所有的系統設計都應該建立在需求的不可預知和靈活性擴展上,同時也需要衡量它的性價比,不做過度設計,不墨守成規。
