游戏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)_模态窗体管理" 继续。