如何在Unity中實現MVC模式?

遊戲引擎 Unity 的入門易精通難體現在哪?為什麼?

在上面的問題中,鄧凱前輩提到了Unity的一大坑就是:

- 基於MonoBehaviour的腳本,用得太順手會有有很大的架構風險,你會情不自禁的A組件引用B組件,B組件又引用A組件.....項目一大寫成一團亂麻。當然用其他引擎,其他語言也有這問題,但UNITY的一些特性必須用MonoBehaviour類來使用,所以要設計一個健壯的MVC架構需要頂住很多誘惑,繞一些彎。

那麼,究竟如何在遊戲中使用MVC模式呢?應該把遊戲的那些部分分別放在Model,View,Controller中呢?


@梁偉國 老師提到的uFrame剛剛看了一下,很強大的感覺...

在Unity遊戲的開發當中,我並沒有刻意地採用MVC框架,因為不像網站開發那樣,Model,View,Controller在遊戲這個領域裡還沒有很清晰的定義。

究其原因,可能是由於不同遊戲類型本身的軟體架構可以相差很遠,而且遊戲裡面的Object之間有大量的交互,所以垂直的MVC似乎不是十分應景。

然而,某種程度的分離代碼邏輯是必要的,可以提高代碼的可維護性和重用性。

下面我說說自己的一些經驗。

假設我們在做一個馬里奧:

對於遊戲里的角色,我會採用這樣一個結構。

Character Manager,它的作用是包含這個角色的Controller(s),並提供一個黑板(Blackboard)[1]。

Controller,利用Reusable Models來處理角色在這個遊戲中的某一狀態的邏輯。

Reusable Model,是一個虛的概念,並不是一個父類,通常這類Model都負責某一個特定的功能,可以重複利用,可看做遊戲引擎的延伸。

我會將Character Manager和Reusable Model繼承MonoBehavior,這樣我們就能夠直觀地知道這個角色是什麼類型的Character,並且可以利用inspector調節Model的參數。

怎麼將上面的架構應用在馬里奧身上呢:

作為Character Manager,我們可以採用Finite State Machine或者Behavior Tree。一個好處是它們都天然地提供了「Controller」。

例如Finite State Machine,它的每一個State都可以看作一個Controller。

而Behavior Tree裡面的Action Node,也可以看作是一個Controller。

在每一個Controller裡面,都會有指針指向一些Reusable Model。

例如下圖Move State可以有一個Move Motor,專門來實現GameObject的移動,而Sprite則封裝GameObject的表現,如動畫、旋轉、位置等等。

這些Reusable Model通常都提供豐富的參數可供調整,可以用於不同遊戲當中。

用戶輸入和遊戲裡面的消息,則會暫存在Character Manager裡面的Blackboard里,供Character Manager使用,讓它決定是否需要更換Controller。

例如馬里奧裡面我按左鍵,往左行動的信息會寫在FSM的Blackboard裡面,然後通過FSM的State轉換機制 [2],從Idle State轉換到Move State。

這樣的好處是,往左的信息可以從Input Manager (圖中沒給出)那裡得來,也可以從Enemy AI Manager(圖中沒給出)那裡得來。

這樣,一個類型(如擁有Idle,Move,Jump等狀態)的FSM,就可以用在所有類似的角色身上,無論是玩家控制的還是AI控制的。

最終在Unity裡面會是這樣一個情況,FSM,Sprite,MoveMotor都作為Component,而Controllers則包含在FSM裡面。

以上方案雖然並不嚴格,但是在一定程度上提高了代碼的可復用性和可維護性。

例如現在我基本都把MoveMotor,Sprite等Model寫好,新項目就直接扔進來就能用;

MoveState,IdleState,JumpState等一些在平台遊戲里常用的狀態封裝好,留出一些可調參數,例如狀態間的轉換。

希望有幫助 :)

[1] Blackboard的本質是一個Dictionary。

[2] 比較原始的FSM會將State轉換直接放在State裡面,但這樣大大降低了State的可復用性。因此可以嘗試將State的轉換作為一個可調參數。一些可視化的FSM的原理也是這樣,利用連線將兩個State鏈接起來,然後通過定義一些轉換的條件。

----------Update 1-----------

1. 重新表述Controller的作用


有個C#的IoC框架叫StrangeIoC。

有個MVVM框架叫uFrame。

不過。。。。其實都不太好用的說。

StrangeIoC概念有點多,幾乎無法在團隊里推廣,沒有框架概念的程序員無法接受。

uFrame思路挺好的,但是代碼簡潔性還有待提高,寫代碼時對VS的依賴性太大。

目前還沒發現易用性和功能都很nb的框架。還不如自己寫的方便快捷。


僅僅用上MVC,解決不了什麼問題,或許解決了1%,剩下的99%就被挪到了MVC的C里,當你慶祝MVC竣工時,99%的問題在那裡默默的微笑的看著你。


MVC是UI專用的模式,適用於橫向鋪量的項目。遊戲(非UI部分)顯然不是這樣的項目。

拋棄MVC迷信是每個遊戲程序員的必經之路。

通常來講,遊戲項目最合理的方式是由一個主程根據項目需求制定一套新的結構和各部分的依賴關係,事先想好各部分的擴展和通信方式,上面那位說的就是一個例子。這是必須的步驟,但既然有了這個步驟,再硬套一個通用的MVC框架就是多此一舉了。

也就是說,遊戲正是因為結構不通用,所以才不能用MVC框架。或者說,由於遊戲結構複雜度遠高於MVC框架,你僅僅使用MVC框架是不能解決問題的。MVC框架里的概念當然是有用的,但只有它們是遠遠不夠的。遊戲需要用到的架構知識遠比MVC這種基礎玩意高得多得多。

而遊戲之所以不可能設計通用框架是因為一個有用的通用框架的複雜度和使用難度會超出所有人的忍耐極限。某種類型是可以有分別的框架的,比如上帝視角ARPG,比如GAL,比如格鬥,比如跑酷,比如卡牌,比如打飛機遊戲。而他們使用的遊戲架構自然也是不同的。

討論這個會沒完沒了。

我看題主的困惑在於MonoBehavior。這個東西是挺討厭。這樣,你可以把它當作Button一類的東西處理,它只做和視覺有關的特定功能,比如震動,比如變色,比如控制動畫,賦點值然後放它一邊玩去,控制好它們的依賴,讓它們擁有通用性。然後遊戲主體是一個空GameObject上的腳本,它負責管理所有,分發事件。其他地方就不要出現MonoBehavior了。

這樣的理由在於生命周期。MonoBehavior必須依附與GameObject,也就是必須要能看到,而邏輯往往會脫離於顯示部分,GameObject未創建往往就需要有邏輯了。

也就是說你創建一個人物,應該先創建一個非MonoBehavior類,然後在這個類的初始化代碼里創建一個GameObject然後再套上各式MonoBehavior,然後依然在這個非MonoBehavior類處理定時邏輯和其他。需要時再控制下其他MonoBehavior播播動畫閃一閃之類。

這樣和非Unity遊戲也就沒啥區別了。想幹什麼就幹什麼很自由,也不存在隱患。

---

另外我並沒有針對的意思啊,比如說伍一峰將Character Manager繼承於MonoBehavior其實是有問題的。它是MonoBehavior就必須依附於GameObject,那通常只能是人物資源的那個GameObject。那麼我有個需求是換狀態的時候切換為完整的另一個資源,那麼要不是Compent複製要不是創建子級GameObject,無論哪個都不太好吧。

或者,我想隱藏這個人物,包括裡面的動畫啊和純顯示用邏輯,但是基本邏輯又想留著(否則什麼時候才能恢復顯示?),本來直接將GameObject禁用就好了,現在這樣就不行了。

或者說,它雖然有狀態,但並沒有顯示,而是一個虛擬對象。

但如果它不是MonoBehavior就沒有任何問題了。

這就是我所說的使用MonoBehavior的隱患,其實很多人心中都默默的有這樣的感覺吧?這個隱患也是確實存在的。當然我也不是說MonoBehavior就完全不能用,比如受創變色(切換以及根據color對所有Renderer賦值以及恢復為原材質)肯定是可以的,還有什麼動畫順序播放,部件綁定。但是涉及到邏輯的部分,真的建議排除掉MonoBehavior。它確實是毒瘤。

此外有些人不喜歡MonoBehavior是因為start發生的不確定性,還有初始化沒有構造函數不便。那你需要端正下思路。你看unity默認的那些組件,你會在乎它們哪個先初始化嗎?你會在乎它有沒有構造函數參數嗎?你不會。因為它們設計成了你不需要考慮這些東西。所以如果你寫MonoBehavior,就應該設法按他們的模式走,讓初始化無需顧忌順序參數直接賦值就能解決,而不是拋棄start awake自己搞個create。

而MonoBehavior的主要優點就在於可以獨立使用,不依賴於環境。你建個預置拖上去設設參數就能看效果。這是它最大也是唯一的優點,它編碼上的不便也都是為了讓這一點成為可能。如果你的MonoBehavior做不到這一點,或者你認為不需要,那費老大勁搞成MonoBehavior又沒用,何苦。


何必呢。為了模式而模式,模式是死的。

iOS那套MVC,也沒見帶到SpriteKit和SceneKit里。相反,還專門引入了基於組件模式的GameplayKit。

答案是非常淺顯易見了,就是模式只是針對解決問題,可以局部實現,C#本來就提供了無限可能,沒有必要刻意去在MonoBehavior里去達到目標。


我一般都是將需要的類都設計成獨立模塊,然後使用繼承MonoBehaviour的類來和那些類對接。然後獨立模塊按照獨立代碼設計。沒做過什麼大項目,所以還沒遇到過亂成麻的情況。


給大家推薦一個新的解決方案

MarkUI, 使用xml標記語言綁定數據的MVVM架構

Asset Store

目前只支持uGUI;優化部分待作者更新;比uFrame學習曲線低,類似Android M的開發模式。

個人認為做輕量級App完全夠用;只是裡面加入3D和特效的坑需要作者去完善。


感覺做遊戲用mvc非常不合適。

本人雖然沒有參與過大項目大遊戲開發,但是單獨做過幾個小遊戲。一開始代碼也是到處用很凌亂,最後始終會把數據處理這塊和視圖處理分開的。但是視圖處理和用戶交互一直都是放在一起的,因為他們聯繫太緊密,如果量太大的話可以分離出來,但絕不等同於那個controller。

後來到處討論mvc的時候也跟風研究一下,非要把視圖和用戶交互分開。看了一些吃飽非要強行用mvc做的遊戲例子代碼,一些簡單的邏輯一下看下這邊,又要看下那邊,跳來跳去,看得我頭都暈。這樣真的好繼續好維護嗎。

後來無論做小遊戲還是應用都沒有使用mvc這種模式。

後來又出現了mvp模式,一看簡介這不就是自己一直用的習慣性模式嗎,雖然細節有部分差異。


推薦閱讀:

同名的全局變數在循環體中怎麼引用?
請問你們能熟練使用的編程語言有那些?
學編程的時候都會先學進位轉換,請問在實際編程過程中,這些東西有用么?
為什麼google編程風格指南設置縮進為2個空格?
UML 還有用嗎?

TAG:遊戲開發 | 編程 | Unity遊戲引擎 | MVC |