遊戲UI框架設計(三) 程序員
---窗體的層級管理緩存
UI框架中UI窗體的「層級管理」,最核心的問題是如何進行窗體的顯示管理。窗體(預設)的顯示咱們前面定義了三種類型: 普通、隱藏其餘、反向切換。代碼以下:數據結構
/// <summary>
/// UI窗體顯示類型 /// </summary>
public enum UIFormsShowMode { Normal, //普通顯示
ReverseChange, //反向切換
HideOther, //隱藏其餘界面
}
「普通顯示」模式容許多個窗體同時顯示,這種類型應用最多。例如RPG中的主城界面(見下圖)。框架
「隱藏其餘界面」 模式通常應用於全局性的窗體。咱們在開發此類窗體時,爲了減小UI渲染壓力、提升Unity渲染效率,則設置被覆蓋的窗體爲「不可見」狀態。(即: this.gameObject.SetActive(false))。例如通常的登陸窗體、選擇英雄窗體等。ide
「反向切換」模式類型,通常都大量引用於「彈出窗體」中。此類窗體的特色是:顯示彈出窗體時不徹底覆蓋底層窗體,通常在屏幕的四周會露出底層窗體。之因此命名「反向切換」是由於: 程序員要維護一種「後進先出」的「棧」的數據結構特色,即咱們通常要求玩家必須先關閉彈出的頂層窗體,再依次關閉下一級窗體。以下圖所示。測試
上圖即一種典型的彈出窗體。通常咱們都要求玩家先處理彈出窗體中的信息,而後關閉此窗體。通常不容許在沒有關閉子窗體的狀況下,直接點擊父窗體。(關於彈出窗體時,不容許玩家點擊父窗體的功能實現,筆者在下節[「模態窗體管理」]一章着重講解)。this
以上說了這麼多了,咱們對於「層級管理」的核心代碼實現,基本都體如今「UI管理器腳本」 (UIManager.cs )中。如下給出具體實現代碼:spa
1 /*** 2 * Title: "SUIFW" 框架 3 * 主題: UI管理器 4 * Description: 5 * 功能:整個UI框架的核心,用戶程序經過調用本類,來調用本框架的大多數功能。 6 * 功能1:關於入「棧」與出「棧」的UI窗體4個狀態的定義邏輯 7 * 入棧狀態: 8 * Freeze(); (上一個UI窗體)凍結 9 * Display(); (本UI窗體)顯示 10 * 出棧狀態: 11 * Hiding(); (本UI窗體) 隱藏 12 * Redisplay(); (上一個UI窗體) 從新顯示 13 * 功能2:增長「非棧」緩存集合。 14 */
15 using UnityEngine; 16 using UnityEngine.UI; 17 using System; 18 using System.Collections.Generic; 19
20
21 namespace SUIFW 22 { 23 public class UIManager : MonoBehaviour 24 { 25 /* 字段 */
26 //本類實例
27 private static UIManager _Instance = null; 28 //存儲全部「UI窗體預設(Prefab)」路徑 29 //參數含義: 第1個string 表示「窗體預設」名稱,後一個string 表示對應的路徑
30 private Dictionary<string, string> _DicUIFormsPaths; 31 //緩存全部已經打開的「UI窗體預設(Prefab)」 32 //參數含義: 第1個string 表示「窗體預設」名稱,後一個BaseUI 表示對應的「窗體預設」
33 private Dictionary<string, BaseUIForms> _DicALLUIForms; 34 //「棧」結構表示的「當前UI窗體」集合。
35 private Stack<BaseUIForms> _StaCurrentUIForms; 36 //當前顯示狀態的UI窗體集合
37 private Dictionary<string, BaseUIForms> _DicCurrentShowUIForms; 38 //UI根節點
39 private Transform _CanvasTransform = null; 40 //普通全屏界面節點
41 private Transform _CanTransformNormal = null; 42 //固定界面節點
43 private Transform _CanTransformFixed = null; 44 //彈出模式節點
45 private Transform _CanTransformPopUp = null; 46 //UI腳本節點(加載各類管理腳本的節點)
47 private Transform _CanTransformUIScripts = null; 48
49
50
51
52 /// <summary>
53 /// 獲得本類實例 54 /// </summary>
55 /// <returns></returns>
56 public static UIManager GetInstance() 57 { 58 if (_Instance == null) 59 { 60 _Instance = new GameObject("_UIManager").AddComponent<UIManager>(); 61 } 62 return _Instance; 63 } 64
65 void Awake() 66 { 67 //字段初始化
68 _DicUIFormsPaths = new Dictionary<string, string>(); 69 _DicALLUIForms = new Dictionary<string, BaseUIForms>(); 70 _StaCurrentUIForms = new Stack<BaseUIForms>(); 71 _DicCurrentShowUIForms = new Dictionary<string, BaseUIForms>(); 72
73 //初始化項目開始必須的資源加載
74 InitRootCanvasLoading(); 75
76 //獲得UI根節點、及其重要子節點
77 _CanvasTransform = GameObject.FindGameObjectWithTag(SysDefine.SYS_TAG_CANVAS).transform; 78 //獲得普通全屏界面節點、固定界面節點、彈出模式節點、UI腳本節點
79 _CanTransformNormal = UnityHelper.FindTheChild(_CanvasTransform.gameObject, SysDefine.SYS_CANVAS_NORMAL_NODE_NAME); 80 _CanTransformFixed = UnityHelper.FindTheChild(_CanvasTransform.gameObject, SysDefine.SYS_CANVAS_FIXED_NODE_NAME); 81 _CanTransformPopUp = UnityHelper.FindTheChild(_CanvasTransform.gameObject, SysDefine.SYS_CANVAS_POPUP_NODE_NAME); 82 _CanTransformUIScripts = UnityHelper.FindTheChild(_CanvasTransform.gameObject, SysDefine.SYS_CANVAS_UISCRIPTS_NODE_NAME); 83
84 //把本腳本實例,做爲Canvas的子節點
85 UnityHelper.AddChildToParent(_CanTransformUIScripts, this.gameObject.transform); 86
87 //本UI節點信息,場景轉換時,不容許銷燬
88 DontDestroyOnLoad(_CanvasTransform); 89 //初始化「UI窗體預設」路徑數據
90 InitUIFormsPathsData(); 91 } 92
93 /// <summary>
94 /// 顯示UI窗體 95 /// </summary>
96 /// <param name="strUIFormName">UI窗體的名稱</param>
97 public void ShowUIForms(string strUIFormName) 98 { 99 BaseUIForms baseUIForms; //UI窗體基類 100
101 //參數檢查
102 if (string.IsNullOrEmpty(strUIFormName)) return; 103
104 //加載「UI窗體名稱」,到「全部UI窗體緩存」中
105 baseUIForms = LoadUIFormsToAllUIFormsCatch(strUIFormName); 106 if (baseUIForms == null) return; 107
108 //判斷是否清空「棧」結構體集合
109 if (baseUIForms.CurrentUIType.IsClearReverseChange) 110 { 111 ClearStackArray(); 112 } 113
114 //判斷不一樣的窗體顯示模式,分別進行處理
115 switch (baseUIForms.CurrentUIType.UIForms_ShowMode) 116 { 117 case UIFormsShowMode.Normal: 118 EnterUIFormsCache(strUIFormName); 119 break; 120 case UIFormsShowMode.ReverseChange: 121 PushUIForms(strUIFormName); 122 break; 123 case UIFormsShowMode.HideOther: 124 EnterUIFormstToCacheHideOther(strUIFormName); 125 break; 126 default: 127 break; 128 } 129 } 130
131 /// <summary>
132 /// 關閉或返回上一個UI窗體(關閉當前UI窗體) 133 /// </summary>
134 public void CloseOrReturnUIForms(string strUIFormName) 135 { 136 BaseUIForms baseUIForms = null; //UI窗體基類
137
138 /* 參數檢查 */
139 if (string.IsNullOrEmpty(strUIFormName)) return; 140 //「全部UI窗體緩存」若是沒有記錄,則直接返回。
141 _DicALLUIForms.TryGetValue(strUIFormName, out baseUIForms); 142 if (baseUIForms == null) return; 143
144 /* 判斷不一樣的窗體顯示模式,分別進行處理 */
145 switch (baseUIForms.CurrentUIType.UIForms_ShowMode) 146 { 147 case UIFormsShowMode.Normal: 148 ExitUIFormsCache(strUIFormName); 149 break; 150 case UIFormsShowMode.ReverseChange: 151 PopUIForms(); 152 break; 153 case UIFormsShowMode.HideOther: 154 ExitUIFormsFromCacheAndShowOther(strUIFormName); 155 break; 156 default: 157 break; 158 } 159
160 } 161
162 #region 私有方法
163 /// <summary>
164 /// 根據指定UI窗體名稱,加載到「全部UI窗體」緩存中。 165 /// </summary>
166 /// <param name="strUIFormName">UI窗體名稱</param>
167 /// <returns></returns>
168 private BaseUIForms LoadUIFormsToAllUIFormsCatch(string strUIFormName) 169 { 170 BaseUIForms baseUI; //UI窗體 171
172 //判斷「UI預設緩存集合」是否有指定的UI窗體,不然新加載窗體
173 _DicALLUIForms.TryGetValue(strUIFormName, out baseUI); 174 if (baseUI == null) 175 { 176 //加載指定路徑的「UI窗體」
177 baseUI = LoadUIForms(strUIFormName); 178 } 179
180 return baseUI; 181 } 182
183 /// <summary>
184 /// 加載UI窗體到「當前顯示窗體集合」緩存中。 185 /// </summary>
186 /// <param name="strUIFormsName"></param>
187 private void EnterUIFormsCache(string strUIFormsName) 188 { 189 BaseUIForms baseUIForms; //UI窗體基類
190 BaseUIForms baseUIFormsFromAllCache; //"全部窗體集合"中的窗體基類 191
192 //「正在顯示UI窗體緩存」集合裏有記錄,則直接返回。
193 _DicCurrentShowUIForms.TryGetValue(strUIFormsName, out baseUIForms); 194 if (baseUIForms != null) return; 195
196 //把當前窗體,加載到「正在顯示UI窗體緩存」集合裏
197 _DicALLUIForms.TryGetValue(strUIFormsName, out baseUIFormsFromAllCache); 198 if (baseUIFormsFromAllCache != null) 199 { 200 _DicCurrentShowUIForms.Add(strUIFormsName, baseUIFormsFromAllCache); 201 baseUIFormsFromAllCache.Display(); 202 } 203 } 204
205 /// <summary>
206 /// 卸載UI窗體從「當前顯示窗體集合」緩存中。 207 /// </summary>
208 /// <param name="strUIFormsName"></param>
209 private void ExitUIFormsCache(string strUIFormsName) 210 { 211 BaseUIForms baseUIForms; //UI窗體基類 212
213 //「正在顯示UI窗體緩存」集合沒有記錄,則直接返回。
214 _DicCurrentShowUIForms.TryGetValue(strUIFormsName, out baseUIForms); 215 if (baseUIForms == null) return; 216
217 //指定UI窗體,運行隱藏狀態,且從「正在顯示UI窗體緩存」集合中移除。
218 baseUIForms.Hiding(); 219 _DicCurrentShowUIForms.Remove(strUIFormsName); 220 } 221
222 /// <summary>
223 /// 加載UI窗體到「當前顯示窗體集合」緩存中,且隱藏其餘正在顯示的頁面 224 /// </summary>
225 /// <param name="strUIFormsName"></param>
226 private void EnterUIFormstToCacheHideOther(string strUIFormsName) 227 { 228 BaseUIForms baseUIForms; //UI窗體基類
229 BaseUIForms baseUIFormsFromAllCache; //"全部窗體集合"中的窗體基類 230
231 //「正在顯示UI窗體緩存」集合裏有記錄,則直接返回。
232 _DicCurrentShowUIForms.TryGetValue(strUIFormsName, out baseUIForms); 233 if (baseUIForms != null) return; 234
235 //「正在顯示UI窗體緩存」與「棧緩存」集合裏全部窗體進行隱藏處理。
236 foreach (BaseUIForms baseUIFormsItem in _DicCurrentShowUIForms.Values) 237 { 238 baseUIFormsItem.Hiding(); 239 } 240 foreach (BaseUIForms basUIFormsItem in _StaCurrentUIForms) 241 { 242 basUIFormsItem.Hiding(); 243 } 244
245 //把當前窗體,加載到「正在顯示UI窗體緩存」集合裏
246 _DicALLUIForms.TryGetValue(strUIFormsName, out baseUIFormsFromAllCache); 247 if (baseUIFormsFromAllCache != null) 248 { 249 _DicCurrentShowUIForms.Add(strUIFormsName, baseUIFormsFromAllCache); 250 baseUIFormsFromAllCache.Display(); 251 } 252 } 253
254 /// <summary>
255 /// 卸載UI窗體從「當前顯示窗體集合」緩存中,且顯示其餘本來須要顯示的頁面 256 /// </summary>
257 /// <param name="strUIFormsName"></param>
258 private void ExitUIFormsFromCacheAndShowOther(string strUIFormsName) 259 { 260 BaseUIForms baseUIForms; //UI窗體基類 261
262 //「正在顯示UI窗體緩存」集合沒有記錄,則直接返回。
263 _DicCurrentShowUIForms.TryGetValue(strUIFormsName, out baseUIForms); 264 if (baseUIForms == null) return; 265
266 //指定UI窗體,運行隱藏狀態,且從「正在顯示UI窗體緩存」集合中移除。
267 baseUIForms.Hiding(); 268 _DicCurrentShowUIForms.Remove(strUIFormsName); 269
270 //「正在顯示UI窗體緩存」與「棧緩存」集合裏全部窗體進行再次顯示處理。
271 foreach (BaseUIForms baseUIFormsItem in _DicCurrentShowUIForms.Values) 272 { 273 baseUIFormsItem.Redisplay(); 274 } 275 foreach (BaseUIForms basUIFormsItem in _StaCurrentUIForms) 276 { 277 basUIFormsItem.Redisplay(); 278 } 279 } 280
281 /// <summary>
282 /// UI窗體入棧 283 /// 功能1: 判斷棧裏是否已經有窗體,有則「凍結」 284 /// 2: 先判斷「UI預設緩存集合」是否有指定的UI窗體,有則處理。 285 /// 3: 指定UI窗體入"棧" 286 /// </summary>
287 /// <param name="strUIFormsName"></param>
288 private void PushUIForms(string strUIFormsName) 289 { 290 BaseUIForms baseUI; //UI預設窗體 291
292
293 //判斷棧裏是否已經有窗體,有則「凍結」
294 if (_StaCurrentUIForms.Count > 0) 295 { 296 BaseUIForms topUIForms = _StaCurrentUIForms.Peek(); 297 topUIForms.Freeze(); 298 } 299
300 //先判斷「UI預設緩存集合」是否有指定的UI窗體,有則處理。
301 _DicALLUIForms.TryGetValue(strUIFormsName, out baseUI); 302 if (baseUI != null) 303 { 304 baseUI.Display(); 305 } 306 else
307 { 308 Log.Write(GetType() + string.Format("/PushUIForms()/ baseUI==null! 核心錯誤,請檢查 strUIFormsName={0} ", strUIFormsName), Log.Level.High); 309 } 310
311 //指定UI窗體入"棧"
312 _StaCurrentUIForms.Push(baseUI); 313 } 314
315 /// <summary>
316 /// UI窗體出棧邏輯 317 /// </summary>
318 private void PopUIForms() 319 { 320 if (_StaCurrentUIForms.Count >= 2) 321 { 322 /* 出棧邏輯 */
323 BaseUIForms topUIForms = _StaCurrentUIForms.Pop(); 324 //出棧的窗體,進行隱藏處理
325 topUIForms.Hiding(); 326 //出棧窗體的下一個窗體邏輯
327 BaseUIForms nextUIForms = _StaCurrentUIForms.Peek(); 328 //下一個窗體"從新顯示"處理
329 nextUIForms.Redisplay(); 330 } 331 else if (_StaCurrentUIForms.Count == 1) 332 { 333 /* 出棧邏輯 */
334 BaseUIForms topUIForms = _StaCurrentUIForms.Pop(); 335 //出棧的窗體,進行"隱藏"處理
336 topUIForms.Hiding(); 337 } 338 } 339
340 /// <summary>
341 /// 加載與顯示UI窗體 342 /// 功能: 343 /// 1:根據「UI窗體預設」名稱,加載預設克隆體。 344 /// 2:預設克隆體添加UI「根節點」爲父節點。 345 /// 3:隱藏剛建立的UI克隆體。 346 /// 4:新建立的「UI窗體」,加入「UI窗體緩存」中 347 /// </summary>
348 private BaseUIForms LoadUIForms(string strUIFormsName) 349 { 350 string strUIFormsPaths = null; //UI窗體的路徑
351 GameObject goCloneUIPrefab = null; //克隆的"窗體預設"
352 BaseUIForms baseUIForm; //UI窗體 353
354
355 //獲得UI窗體的路徑
356 _DicUIFormsPaths.TryGetValue(strUIFormsName, out strUIFormsPaths); 357
358 //加載指定路徑的「UI窗體」
359 if (!string.IsNullOrEmpty(strUIFormsPaths)) 360 { 361 goCloneUIPrefab = ResourcesMgr.GetInstance().LoadAsset(strUIFormsPaths, false); 362 } 363
364 //設置「UI窗體」克隆體的父節點,以及隱藏處理與加入「UI窗體緩存」中
365 if (_CanvasTransform != null && goCloneUIPrefab != null) 366 { 367 baseUIForm = goCloneUIPrefab.GetComponent<BaseUIForms>(); 368 if (baseUIForm == null) 369 { 370 Log.Write(GetType() + string.Format("/LoadUIForms()/ baseUIForm==null!,請先確認克隆對象上是否加載了BaseUIForms的子類。參數 strUIFormsName='{0}' ", strUIFormsName), Log.Level.High); 371 return null; 372 } 373 switch (baseUIForm.CurrentUIType.UIForms_Type) 374 { 375 case UIFormsType.Normal: 376 goCloneUIPrefab.transform.SetParent(_CanTransformNormal, false); 377 break; 378 case UIFormsType.Fixed: 379 goCloneUIPrefab.transform.SetParent(_CanTransformFixed, false); 380 break; 381 case UIFormsType.PopUp: 382 goCloneUIPrefab.transform.SetParent(_CanTransformPopUp, false); 383 break; 384 default: 385 break; 386 } 387
388 goCloneUIPrefab.SetActive(false); 389 //新建立的「UI窗體」,加入「UI窗體緩存」中
390 _DicALLUIForms.Add(strUIFormsName, baseUIForm); 391 return baseUIForm; 392 } 393 else
394 { 395 Log.Write(GetType() + string.Format("/LoadUIForms()/‘_CanvasTransform’ Or ‘goCloneUIPrefab’==NULL! , 方法參數 strUIFormsName={0},請檢查!", strUIFormsName), Log.Level.High); 396 } 397
398 Log.Write(GetType() + string.Format("/LoadUIForms()/ 出現不可預知錯誤,請檢查! 方法參數 strUIFormsName={0} ", strUIFormsName), Log.Level.High); 399 return null; 400 } 401
402 /// <summary>
403 /// 初始化項目開始必須的資源加載 404 /// </summary>
405 private void InitRootCanvasLoading() 406 { 407 if (UnityHelper.isFirstLoad) 408 { 409 ResourcesMgr.GetInstance().LoadAsset(SysDefine.SYS_PATH_CANVAS, false); 410 } 411 } 412
413 /// <summary>
414 /// 初始化「UI窗體預設」路徑數據 415 /// </summary>
416 private void InitUIFormsPathsData() 417 { 418 //測試也成功
419 IConfigManager configMgr = new ConfigManagerByJson(SysDefine.SYS_PATH_UIFormConfigJson); 420 if (_DicUIFormsPaths != null) 421 { 422 _DicUIFormsPaths = configMgr.AppSetting; 423 } 424 } 425
426 /// <summary>
427 /// 清空「棧」結構體集合 428 /// </summary>
429 /// <returns></returns>
430 private bool ClearStackArray() 431 { 432 if (_StaCurrentUIForms != null && _StaCurrentUIForms.Count >= 1) 433 { 434 _StaCurrentUIForms.Clear(); 435 return true; 436 } 437 return false; 438 } 439
440 #endregion
441
442 }//Class_end
443 }
以上代碼解釋:設計
1: UIManager.cs 中定義的新的字段 ,「_StaCurrentUIForms」 就是一個「棧」數據類型,用於維護一種後進先出的數據結構。常見的方法以下:code
C#語言中提供 Stack<T> 泛型集合,來直接實現這種結構。
經常使用屬性與方法:
2: UIManager.cs 中的「ShowUIForms()」方法中的120行與123行,就是專門處理「反向切換」與「隱藏其餘」窗體特性的實現方法。
好了時間不早了就先寫到這吧,你們有什麼疑問能夠討論,這裏筆者也主要是想起到「拋磚引玉」的做用。
本篇就先寫到這,下篇 "遊戲UI框架設計(4)_模態窗體管理" 繼續。