游戏UI框架设计(三) : 窗体的层级管理

游戏UI框架设计(三)

---窗体的层级管理

 

  UI框架中UI窗体的“层级管理”,最核心的问题是如何进行窗体的显示管理。窗体(预设)的显示我们前面定义了三种类型: 普通、隐藏其他、反向切换。代码如下:

    /// <summary>
    /// UI窗体显示类型
    /// </summary>
    public enum UIFormsShowMode
    {
        Normal,             //普通显示
        ReverseChange,      //反向切换      
        HideOther,          //隐藏其他界面 
    }

  “普通显示”模式允许多个窗体同时显示,这种类型应用最多。例如RPG中的主城界面(见下图)。

 

  “隐藏其他界面” 模式一般应用于全局性的窗体。我们在开发此类窗体时,为了减少UI渲染压力、提高Unity渲染效率,则设置被覆盖的窗体为“不可见”状态。(即: this.gameObject.SetActive(false))。例如一般的登录窗体、选择英雄窗体等。

  

  “反向切换”模式类型,一般都大量引用于“弹出窗体”中。此类窗体的特点是:显示弹出窗体时不完全覆盖底层窗体,一般在屏幕的四周会露出底层窗体。之所以命名“反向切换”是因为: 程序员要维护一种“后进先出”的“栈”的数据结构特点,即我们一般要求玩家必须先关闭弹出的顶层窗体,再依次关闭下一级窗体。如下图所示。

 

  上图即一种典型的弹出窗体。一般我们都要求玩家先处理弹出窗体中的信息,然后关闭此窗体。一般不允许在没有关闭子窗体的情况下,直接点击父窗体。(关于弹出窗体时,不允许玩家点击父窗体的功能实现,笔者在下节[“模态窗体管理”]一章着重讲解)。

  以上说了这么多了,我们对于“层级管理”的核心代码实现,基本都体现在“UI管理器脚本” (UIManager.cs )中。以下给出具体实现代码:

 

  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” 就是一个“栈”数据类型,用于维护一种后进先出的数据结构。常见的方法如下:

      C#语言中提供 Stack<T> 泛型集合,来直接实现这种结构。
常用属性与方法:

  •  Count 属性  查询栈内元素数量
  •  Push()      压栈
  •  Pop()       出栈
  •  Peek()      查询栈顶元素
  •  GetEnumerator() 遍历栈中所有元素

 

   2: UIManager.cs 中的“ShowUIForms()”方法中的120行与123行,就是专门处理“反向切换”与“隐藏其他”窗体特性的实现方法。

 

好了时间不早了就先写到这吧,大家有什么疑问可以讨论,这里笔者也主要是想起到“抛砖引玉”的作用。

 

本篇就先写到这,下篇 "游戏UI框架设计(4)_模态窗体管理" 继续。

 

posted @ 2017-02-27 20:14  刘国柱老师  阅读(20022)  评论(2编辑  收藏  举报