Unity UI架構(gòu)設(shè)計(jì)理念
1.以ARPG為例,多個(gè)場(chǎng)景會(huì)反復(fù)出現(xiàn)相同的“UI窗體”,造成多個(gè)場(chǎng)景中反復(fù)加載相同的UI窗體。
解決方案:
????????????????“UI框架” 需要緩存項(xiàng)目(例游戲項(xiàng)目)中常用的“UI窗體"。

2.開發(fā)商業(yè)復(fù)雜項(xiàng)目時(shí),各個(gè)UI(UI腳本)之間傳值,容易出現(xiàn)“緊耦合”(相互交錯(cuò),你中有我,我中有你)的情況。
解決方案:
????????????????各個(gè)UI的生成、銷毀、切換,都是通過(guò)框架(Manger)實(shí)現(xiàn),各個(gè)UI之間不直接聯(lián)系(傳值)。

3.卡牌、RPG等游戲類型項(xiàng)目,很多情況下會(huì)出現(xiàn)“UI窗體” 疊加現(xiàn)象。開發(fā)人員需要“手工”維護(hù)窗體中間的層級(jí)關(guān)系。
解決方案:
????????????????設(shè)計(jì)UI框架系統(tǒng),使用“?!钡臄?shù)據(jù)結(jié)構(gòu),保存與控制當(dāng)前所有需要顯示的“UI窗體”的層級(jí)關(guān)系。

4.商業(yè)開發(fā)項(xiàng)目中的多個(gè)“UI窗體” 之間疊加出現(xiàn)時(shí),必須保持“模態(tài)窗口”類型,否則容易出現(xiàn)誤操作。
解決方案??
設(shè)計(jì)的框架本身,需要對(duì)當(dāng)前顯示的窗體做“遮擋處理”,即:不允許用戶繞過(guò)當(dāng)前
“UI窗體”直接操作底層窗體,或者誤操作點(diǎn)擊項(xiàng)目中的3D游戲?qū)ο蟮?/p>

5.從以上問(wèn)題還可以推導(dǎo)出如下“UI框架”需要注意的設(shè)計(jì)問(wèn)題:
????????UI框架,需要管理加載“窗體預(yù)設(shè)”,進(jìn)行自動(dòng)加載的管理
????????UI框架,需要支持不同的語(yǔ)言環(huán)境,即語(yǔ)言的國(guó)際化。
? ????? 最后設(shè)計(jì)UI框架一個(gè)總的核心原則是:????
????????盡量讓框架本身完成與具體業(yè)務(wù)無(wú)關(guān)的事務(wù)性工作,讓開發(fā)人員只需要專注游戲業(yè)務(wù)邏輯的開發(fā)即可。(這個(gè)原則同樣適用于其他框架的設(shè)計(jì)中)



首先開發(fā)最簡(jiǎn)版本功能設(shè)計(jì):
??1:窗體自動(dòng)加載管理。
??2:緩存UI窗體。
??3:窗體生命周期管理。
UI框架的核心類設(shè)計(jì)
??1: BaseUIForms 基礎(chǔ)UI窗體(父類)
??2: UIManger.cs UI窗體管理器
??3: UIType 窗體類型
??4: SysDefine 系統(tǒng)定義類
在Unity5.5安裝目錄下,建立腳本模版。
建立必要的目錄結(jié)構(gòu)與核心類,導(dǎo)入素材。
?BaseUIForms.cs
?UIManager.cs
?UIType.cs
?SysDefine.cs?[Config目錄下]

導(dǎo)入U(xiǎn)I貼圖素材。
導(dǎo)入一些簡(jiǎn)單的UI素材即可。

建立框架中的三個(gè)重要枚舉類型,定義 UIType 類。
??????UIFormsType?? UI窗體(位置)類型
??????UIFormsShowMode? UI窗體顯示類型
??????UIFormsLucencyType? 窗體透明度類型
?[提示: SysDefine 中定義]

定義“基礎(chǔ)UI窗體” BaseUIForms
定義 “UI管理器”? UIManager
? ? ? ?1: 定義“窗體路徑”與“窗體預(yù)設(shè)”的集合字段。
? ? ? ?2:定義“窗體預(yù)設(shè)”與管理腳本加載用的節(jié)點(diǎn)對(duì)象。
???????普通節(jié)點(diǎn)、固定節(jié)點(diǎn)、彈出節(jié)點(diǎn)、管理腳本節(jié)點(diǎn)。
? ? ? ?3:Unity編輯器中,定義“Canvas 根窗體”預(yù)設(shè)。
???????????????1>在測(cè)試場(chǎng)景中建立UI Panel 。
???????????????2>共建立3個(gè)Panel,與一個(gè)掛載腳本的空對(duì)象。
???????????????3>建立UI攝像機(jī),設(shè)置參數(shù)。
???????????????4> Game視圖定義800*600 分辨率。
???????????????5>針對(duì)Canvas UI對(duì)象,設(shè)置合理參數(shù)

定義 “UI管理器”? UIManager (續(xù))
?4:定義“登陸窗體”、“選擇角色窗體”、“主窗體”等。
??????????1>建立各個(gè)窗體的Panel (注意:必須在Canvas 內(nèi)部建立)
??????????2>給各個(gè)窗體添加背景貼圖與必要按鈕等。
??????????3>定義的窗體都作為“預(yù)設(shè)”
定義 “UI管理器”? UIManager (續(xù))
?5:繼續(xù)開發(fā)UIManger 腳本,實(shí)現(xiàn)窗體的加載功能,且測(cè)試。
??????????1>主場(chǎng)景中,確保拖拽到層級(jí)視圖中的Canvas預(yù)設(shè)正確顯示。
??????????2>編寫Awake 事件函數(shù),對(duì)于常量都統(tǒng)一定義在SysDefine中。
??????????3>編寫“顯示UI窗體”公共方法。
??????????4>框架外建立測(cè)試腳本,測(cè)試UI窗體的基本加載功能。
?????????????1]建立框架外啟動(dòng)加載腳本。
?????????????2]針對(duì)每個(gè)“窗體預(yù)設(shè)”都需要建立對(duì)應(yīng)的窗體腳本(繼承BaseUIForms)
?[備注:如果出現(xiàn)“baseUIForms==null, 請(qǐng)先確認(rèn)克隆對(duì)象上是否加載了BaseUIForms的子類”,說(shuō)明“窗體預(yù)設(shè)”上必須添加BaseUIForms 的子類]
}定義 “UI管理器”? UIManager (續(xù))
?5:繼續(xù)開發(fā)UIManger 腳本,實(shí)現(xiàn)窗體的加載功能,且測(cè)試。(續(xù))
??5>? 測(cè)試以上所有步驟,成功如下圖所屬。

窗體層級(jí)管理
什么是“棧”數(shù)據(jù)結(jié)構(gòu)?
??是一種“先進(jìn)后出”的數(shù)據(jù)結(jié)構(gòu),是一種常用算法。
??生活中的“漢諾塔”游戲、“摞燒餅”、“盤子堆”都是一種典型的“棧”結(jié)構(gòu)。
C#語(yǔ)言中提供 Stack泛型集合,來(lái)直接實(shí)現(xiàn)這種結(jié)構(gòu)。
常用屬性與方法:
Count 屬性?查詢棧內(nèi)元素?cái)?shù)量
Push()?????壓棧
Pop()??????出棧
Peek()?????查詢棧頂元素
nGetEnumerator() 遍歷棧中所有元素
??演示:典型Demo示例。

開發(fā)“UI管理器”的“?!睌?shù)據(jù)結(jié)構(gòu),維護(hù)窗體的層級(jí)結(jié)構(gòu)。
定義Stack 類型字段。
顯示UI窗體 ShowUIForms() 方法中
????????1>“反向切換”屬性的窗體,定義“壓棧”方法
關(guān)閉(或返回上一個(gè)UI)窗體方法中
????????1>“普通”顯示屬性的窗體,定義關(guān)閉方法。
????????2>對(duì)于“反向切換”屬性的窗體,定義返回上一個(gè)窗體的方法(即:關(guān)閉)。
顯示UI窗體 ShowUIForms() 方法中
???????? 1>“反向切換”屬性窗體,定義“壓?!狈椒?/p>
???????? 2> “隱藏其他”屬性窗體,定義顯示業(yè)務(wù)邏輯方法
關(guān)閉(或返回上一個(gè)UI)窗體方法中
???? ????1>“普通”顯示屬性的窗體,定義關(guān)閉方法。
???????? 2>對(duì)于“反向切換”屬性的窗體,定義返回上一個(gè)窗體的方法。 (即:關(guān)閉)。
????????3>“隱藏其他”屬性窗體,定義關(guān)閉邏輯方法
在多個(gè)UI業(yè)務(wù)窗體中,有時(shí)候需要客戶端程序主動(dòng)清空“棧集合”中的當(dāng)前數(shù)據(jù),防止業(yè)務(wù)邏輯混亂。
?例如: RPG中的“商場(chǎng)系統(tǒng)”、“背包系統(tǒng)”、“任務(wù)系統(tǒng)”等。

具體代碼實(shí)現(xiàn):
??1:在UIType 類中,定義是否需要“清空反向切換”的字段(或者屬性)。
?2: 在UI管理器腳本中,關(guān)于顯示UI窗體的方法中,加入判斷清空棧中數(shù)據(jù)的業(yè)務(wù)邏輯即可。
定義如下窗體編寫代碼測(cè)試UI框架功能:
?登陸窗體:
????????注意事項(xiàng): 所有窗體腳本都要繼承BaseUIForms
????????定義本窗體的類型(位置、顯示、透明度三大屬性),不寫則采用默認(rèn)數(shù)值。
? ? `? ? 注冊(cè)窗體按鈕事件。
?選擇英雄窗體:
?主城窗體:
?商城窗體:
?商品信息窗體:???

?程序重構(gòu)發(fā)現(xiàn),在UI框架內(nèi)部與客戶調(diào)用程序中都存在一些反復(fù)被使用的技術(shù)。
????????對(duì)于層級(jí)視圖的節(jié)點(diǎn)查找。
?????????(擴(kuò)展方法)
????????獲取子節(jié)點(diǎn)(物體)的腳本
????????給子節(jié)點(diǎn)(物體)添加腳本
????????給子節(jié)點(diǎn)(物體)添加父對(duì)象

?程序重構(gòu)發(fā)現(xiàn),在客戶程序(UI框架)調(diào)用中,會(huì)反復(fù)出現(xiàn)一些常用的定義方式,例如:
????????按鈕的事件監(jiān)聽(tīng)與注冊(cè)方法????
????????打開指定窗體
????????關(guān)閉指定窗體
????????發(fā)送消息
????????顯示語(yǔ)言信息
模態(tài)窗體管理
UI窗體中,很多彈出窗體要求玩家不能點(diǎn)擊“父窗體”,這就是“模態(tài)窗體”。
這里我們?cè)O(shè)計(jì)了四種模式類型:
? ? ? ? 完全透明、半透明、低透明度、透明且可以穿透。

在Canvas根窗體預(yù)設(shè)中(PopUp節(jié)點(diǎn)下)定義“UI遮擋面板”(_UIMaskPanel)窗體。
“UI遮擋面板” 就是一個(gè)普通的Panel。

平時(shí)這個(gè)面板是“不可見(jiàn)”狀態(tài)。
當(dāng)需要進(jìn)行“模態(tài)”顯示的時(shí)候,則定義腳本,控制其在PopUp節(jié)點(diǎn)下倒數(shù)第二的位置,起到遮擋作用。
定義一個(gè)專門的控制腳本:“UIMaskMgr.cs”,以及在“窗體基類”(BaseUIForms.cs) 中控制“遮擋面板”的顯示與隱藏。

框架配置管理
所謂“配置管理”是指一個(gè)游戲項(xiàng)目(軟件項(xiàng)目),很多需要經(jīng)常變化的需求或者數(shù)據(jù),最好以配置文件的形式存在,從而代替“硬編碼”方式。
? 例如: 游戲項(xiàng)目語(yǔ)言的國(guó)際化、
???????? 日志文件的保存路徑等。

目前(2017)國(guó)際國(guó)內(nèi)普遍采用的配置管理方式主要有兩種: XML與Json 方式。
??兩者各有優(yōu)缺點(diǎn):
? ? ? ? ? ?XML:對(duì)于數(shù)據(jù)的精確表示、易讀性很高。
?????????? 微軟很多的項(xiàng)目都內(nèi)置對(duì)XML作為配置文件的支持。
?????????(例如:網(wǎng)站項(xiàng)目:ASP.Net、 WinForm 等)
?????????? 缺點(diǎn)是讀寫速度慢,這個(gè)問(wèn)題在移動(dòng)端尤其突出。
??Json: 讀寫速度快,但是易讀性沒(méi)有XML好,但是可以接受。
?????????? 所以本框架項(xiàng)目都采用Json作為配置文件。
JSON(JavaScript Object Notation) 是一種輕量級(jí)的數(shù)據(jù)交換格式。 JSON采用完全獨(dú)立于語(yǔ)言的文本格式,但是也使用了類似于C語(yǔ)言家族的習(xí)慣(包括C、C++、C#、Java、JavaScript、Perl、Python等)。這些特性使JSON成為理想的數(shù)據(jù)交換語(yǔ)言。 易于人閱讀和編寫,同時(shí)也易于機(jī)器解析和生成(一般用于提升網(wǎng)絡(luò)傳輸速率)。
JSON 語(yǔ)法 (JSON 語(yǔ)法是 JavaScript 對(duì)象表示語(yǔ)法的子集)
特點(diǎn):
?數(shù)據(jù)在鍵值對(duì)中,數(shù)據(jù)由逗號(hào)分隔。
?花括號(hào)保存對(duì)象,方括號(hào)保存數(shù)組。
JSON 數(shù)據(jù)的書寫格式是:名稱/值對(duì)。 "firstName":"John"
開發(fā)Json配置管理器
定義通用配置管理器接口
開發(fā)實(shí)現(xiàn)IConfigManager 接口的通用配置管理器

UI管理器中關(guān)于“UI窗體預(yù)設(shè)路徑”集合中,把前面“硬編碼”改為應(yīng)用Json 配置管理的方式。
?????第1步在Resources 目錄下建立關(guān)于“UIFormsConfigInfo”的Json 文件。
?????第2步對(duì)于UIManager.cs 中的“UI窗體預(yù)設(shè)路徑”集合做配置管理。

日志調(diào)試
日志調(diào)試在游戲的開發(fā)全過(guò)程中占有非常重要的作用。
自定義“日志調(diào)試”腳本插件的開發(fā)思路與具體實(shí)現(xiàn)。
??現(xiàn)在為了更好的適用于PC與移動(dòng)端調(diào)試的目的,進(jìn)行再次重構(gòu)。
??第1:對(duì)于讀寫文件內(nèi)部方法做重構(gòu)完善,使得日志文件實(shí)現(xiàn)自動(dòng)偵測(cè)與創(chuàng)建寫入操作等。
??第2: 改以前的針對(duì)XML的配置文件的讀取方式為Json 文件的讀取。
??目的一:提高讀取速度。
? ? ? ? ?二: 更好的應(yīng)用在移動(dòng)端的部署
消息傳遞中心
基于Unity技術(shù)的游戲與項(xiàng)目研發(fā),目前提供的消息傳遞方式種類少,且耦合性很高。
?1:腳本組件公共方法、字段的相互調(diào)用。
????????例如: GetComponnet().TestMethod();
?2:SendMesage 技術(shù)。
?3:單例模式數(shù)據(jù)傳遞。
開發(fā)一種低耦合,無(wú)需考慮被傳遞對(duì)象(腳本名稱、組件名稱)的技術(shù)非常有價(jià)值。
“消息傳遞中心”:
??基于觀察者模式,利用委托與事件的基本機(jī)制原理,進(jìn)一步封裝重構(gòu)的技術(shù)實(shí)現(xiàn)。
定義MessageCenter
?基本原理:



窗體基類(BaseUIForms) 中對(duì)于“消息傳遞中心”類常用方法的封裝。
?????發(fā)送消息 SendMessage()
?????接收消息 ReceiveMessage()
客戶程序消息傳遞多組數(shù)據(jù)的演示。
客戶程序建立“系統(tǒng)常量”類,方便程序復(fù)用與集中化管理
資源國(guó)際化
“資源國(guó)際化”對(duì)于游戲項(xiàng)目開發(fā)是指:語(yǔ)言、語(yǔ)音、貼圖、模型等國(guó)際化問(wèn)題。
對(duì)于游戲項(xiàng)目,最常見(jiàn)的是針對(duì)不同國(guó)家的多語(yǔ)言版本的開發(fā),也就是“語(yǔ)言的國(guó)際化”。??
多語(yǔ)言版本的實(shí)現(xiàn),最基本的原理就是根據(jù)ID去讀取語(yǔ)言配置表,不同的語(yǔ)言新建一個(gè)語(yǔ)言配置表。

定義“語(yǔ)言管理器”(LanguageMgr)
基本原理:
??????1: 使用配置管理器腳本(繼承 IConfigManager接口),讀取不同語(yǔ)言的Json配置文件。
? ? ? 2: 使用 Dictionary 集合緩存“語(yǔ)言鍵值對(duì)”。
????? 3:定義顯示方法,根據(jù)ID查詢出對(duì)應(yīng)的語(yǔ)言信息

UI窗體基類(BaseUIForms) 對(duì)顯示語(yǔ)言的重構(gòu)。