歡迎大家和我在評論區理性討論,勿引戰。

## 前言

這陣子我的開源Moba專案要開始著手準備客戶端的表現工作了,後端的邏輯基本上沒有太大的問題。

如果對這個專案感興趣的可以去看一下

談及客戶端表現,UI是必不可少的一環,那麼選定一個好的UI解決方案和框架就更加重要了。

對於解決方案,我們耳熟能詳的有UGUI,FGUI,NGUI等。

對於UI框架,基本架構基本上就是MVC,MVVM這種MV*系列的框架。

在此之前,我並沒有對這些UI框架的使用經驗,所以就趁此機會好好學習一下。

## UI解決方案的選擇

### UGUI

原生的UGUI似乎是一個好的選擇,因為官方在一直維護,各個方面都有保障,但是許多功能需要自己重新造輪子,對於沒有模組積累的人來說可能有些麻煩。

### NGUI

與UGUI還是有比較大的差別的,有一些輪子,拓展模組,底層也做了一些調整和最佳化。不過近幾年勢頭越來越弱了,本身也不打算選擇,如果UGUI和NGUI選一個,我會選UGUI。

### FGUI

FGUI全名FairyGUI,是一個開源的,跨平臺UI解決方案,它包含幾乎所有遊戲UI常用功能,易於拓展,效能優秀,可以做到一次匯出,各處使用,開發效率也很高。無疑更加適合獨立遊戲團隊或者個人開發者。

所以我最終也是選定了FGUI作為我的專案UI解決方案。這是FGUI的官網:

## UI框架的選擇

UI框架個人其實早已經接觸了一些,比如GameFramework的UI框架,C#原生的MVC框架,個人覺得都已經很優秀了,但還是想找找看有沒有更好的選擇。於是就有了下面的學習歷程。

### 一個完整的UI框架所必需要包含的基本功能

完整的UI生命週期管理,完整的資料更新方案。當然如果遊戲的UI系統比較複雜,可能還需要UI組,層級管理這些功能。生命週期沒什麼好說的,直接來看資料更新部分。

### MVC

雖然大家可能都已經對這個MVC聽到耳朵生繭子了,但我這裡還是要說一下(皮一下~)

M:Model,UI的資料以及對於UI資料的操作函式。`資料部分`可以理解為UI元件的具體渲染資料。以UGUI為例,一個UI上掛載了一個Text元件,一個Button元件,那麼Model裡可能就會有一個string屬性代表Text顯示的內容,一個Action屬性表示Button被點選時會呼叫的回撥函式。`邏輯部分`就是我們在遊戲邏輯中會對UI資料進行的操作,可能還會有一些複雜的變換操作,比如一個人物最大生命值是100,並且血條本身只顯示血量百分比,那麼這個Model裡的SetHPBarValue()函式就會執行下面的操作,把傳進來的當前人物生命值(這裡假設是50)換算到一個百分比的值,即:當前人物生命值/人物最大生命值 = 0。5。然後會被Controller呼叫把這個值給View,讓它顯示50%。

V:View,UI的渲染元件,以UGUI為例,一個UI上掛載了一個Text,一個Button,那麼這兩個UI元件都將寫到View裡。也會包含一些操作,例如獲取一個UI元件,對一個UI元件進行操作。

C:Controller,本身Model和View他們是互不耦合的,所以需要一箇中介者把他們聯絡起來,從而達到資料更新和渲染狀態更新同步。它本身是沒什麼程式碼量的,程式碼幾乎都在Model和View中。

然後就是很多人說MVC會有很多冗餘程式碼,寫起來不方便,應該是他們的使用方法有問題,可能並沒有搞清楚M,V,C的分工,可能把M的工作寫到了C裡,或者把C裡的工作寫到了V裡,總之概念就搞的一團糟,更不用說寫出的程式碼如何了。

### MVVM

上面我已經提到了MVVM,下面來詳細介紹一下MVVM到底是個什麼東西。

M:Model,還是和MVC的M差不多。

V:View,還是和MVC的V差不多。

VM:雙向耦合(可能這裡叫雙向繫結更加貼切一點)M和V,什麼是繫結呢,這裡用觀察者模式來解釋可能更好懂一些。以M繫結V為例,M的更新會執行一個委託,而我們此時已經將V加入到委託鏈裡了,所以M的更新會執行我們指定的一個委託函式,從而達到V也更新的目的。V繫結M也是同理。最後就可以M更新會導致V更新,V更新也會導致M相關資料更新的效果(避免迴圈應該加一個判重機制)。

我感覺MVVM和MVC並沒有本質上的區別,他只是更加強制制定了更加明顯的規範,所以寫起來感覺MVVM更加厲害一些。

其實MVVM並沒有大家想象中的那麼神秘,可能是因為它的繫結機制讓我們摸不到頭腦?感覺yiyi大佬說的更加透徹一些,大家可以去看看:

### Flux和Redux

本來到這裡就已經結束了,MVVM是我的不二之選,但是一次偶然的機會,我認識了Flux架構,這是奶泡大佬的知乎:

大家可以去看看UFlux篇,基本把重要的點都講到了。下面我來繼續介紹Flux和Redux。

其實Flux和Redux基本上都是用來做Web應用的,只是他們架構中有一些優秀的點,可以拿到遊戲來遷移應用。

Redux中文文件中有這樣一段,我覺得非常經典:

管理不斷變化的 state 非常困難。如果一個 model 的變化會引起另一個 model 變化,那麼當 view 變化時,就可能引起對應 model 以及另一個 model 的變化,依次地,可能會引起另一個 view 的變化。直至你搞不清楚到底發生了什麼。

state 在什麼時候,由於什麼原因,如何變化已然不受控制。

當系統變得錯綜複雜的時候,想重現問題或者新增新功能就會變得舉步維艱。

然後我並不準備過於詳細的講解Flux和Redux這兩個架構,但是會提供資料,有興趣的可以去康康。

#### Flux

架構模型

Action->Dispatcher->Store->View->Action

核心是就是如上所示的`單向資料流`。

什麼是單向資料流呢?

其實我們在`MV*`系列中的資料流向是紊亂的,在MV多對多的情況下尤為明顯。這樣有什麼壞處呢?查Bug不好查,你有時候甚至不知道這個資料從哪來的,Bug自然不好找。

單向資料流就是始終保持資料只有一個流向,也就是上面那個示例,這樣出了Bug基本上很快就可以確定是哪裡出了問題。

#### Redux

其實我更傾向於Redux,雖然他脫胎於Flux,但是比Flux更加簡潔,並且在單向資料流的基礎上增加了狀態管理。

我通讀了一遍Redux的中文文件:受益良多。

它的架構是:

Action->Store->Reducer->View->Action

其中Store->Reducer->View這幾步操作的就是State,也就是狀態管理的關鍵。

說了那麼多,什麼是狀態管理?

狀態管理就是記錄每一次UI資料改變,這樣可以更加方便的回溯狀態,找Bug,重現Bug,在開發除錯的過程中有著非常重要的作用。

其實我個人更傾向於把狀態管理做成一個熱插拔的功能,只在開發時使用,正式上線不需要。

### UI框架總結

從`MV*`系列架構我學到了`邏輯與資料的分離與組合`,從Flux和Redux我學到了`單向資料流`和`狀態管理`這兩個重要的概念。但是我想各自取長補短,也就是全都要怎麼辦呢,去網上找了一大圈也沒找到,但是還是有意外收穫的,找到了城主大佬的部落格:

他從零開始搭建了一個簡易的MVVM框架,並且十分適合融合單向資料流和狀態管理。

於是我自己修修改改縫縫補補,就寫了一個簡易的UI框架,他有MVVM的繫結特性,有Flux的單向資料流和Redux的狀態管理,不依賴於Unity,用。net core 3。0寫的,只是為了更好得理解邏輯。

## 大總結

最終選定FGUI和自己改的UI框架作為最終的解決方案,然而我又發現了一個問題,FGUI本身的自動生成程式碼在開發中已經相當於做了資料繫結和單向資料流的角色。。。那我豈不是白折騰一趟。

其實也沒有啦,這個過程中也學到了很多從來沒有聽過的名詞,知識點。受益匪淺。

可能以後再回來用UGUI的時候我會重新拾起來這個小框架也說不定。。。