游戏UI框架设计(三)

---窗体的层级管理


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


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



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


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



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

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


/***

*Title: "SUIFW" 框架技术

*主题: UI管理器

*Description:

*功能:整个UI框架的核心,用户程序通过调用本类,来调用本框架的大多数功能。

*功能1:关于入“栈”与出“栈”的UI窗体4个状态的定义逻辑

* 入栈状态:

* Freeze(); (上一个UI窗体)冻结

* Display(); (本UI窗体)显示

* 出栈状态:

* Hiding(); (本UI窗体) 隐藏

* Redisplay(); (上一个UI窗体) 重新显示

*功能2:增加“非栈”缓存集合。

*/

using UnityEngine;

usingUnityEngine.UI;

using System;

usingSystem.Collections.Generic;

namespace SUIFW

{

publicclassUIManager : MonoBehaviour

{

/* 字段 */

//本类实例

privatestaticUIManager_Instance = null;

//存储所有“UI窗体预设(Prefab)”路径

//参数含义: 第1个string 表示“窗体预设”名称,后一个string 表示对应的路径

privateDictionary<string,string> _DicUIFormsPaths;

//缓存所有已经打开的“UI窗体预设(Prefab)”

//参数含义: 第1个string 表示“窗体预设”名称,后一个BaseUI 表示对应的“窗体预设”

privateDictionary<string,BaseUIForms> _DicALLUIForms;

//“栈”结构表示的“当前UI窗体”集合。

privateStack<BaseUIForms>_StaCurrentUIForms;

//当前显示状态的UI窗体集合

privateDictionary<string,BaseUIForms> _DicCurrentShowUIForms;

//UI根节点

privateTransform _CanvasTransform = null;

//普通全屏界面节点

privateTransform _CanTransformNormal = null;

//固定界面节点

privateTransform _CanTransformFixed = null;

//弹出模式节点

privateTransform _CanTransformPopUp = null;

//UI脚本节点(加载各种管理脚本的节点)

privateTransform _CanTransformUIScripts = null;

///<summary>

///得到本类实例

///</summary>

///<returns></returns>

publicstaticUIManagerGetInstance()

{

if(_Instance == null)

{

_Instance = newGameObject("_UIManager").AddComponent<UIManager>();

}

return_Instance;

}

voidAwake()

{

//字段初始化

_DicUIFormsPaths = newDictionary<string,string>();

_DicALLUIForms = newDictionary<string,BaseUIForms>();

_StaCurrentUIForms = newStack<BaseUIForms>();

_DicCurrentShowUIForms = newDictionary<string,BaseUIForms>();

//初始化项目开始必须的资源加载

InitRootCanvasLoading();

//得到UI根节点、及其重要子节点

_CanvasTransform = GameObject.FindGameObjectWithTag(SysDefine.SYS_TAG_CANVAS).transform;

//得到普通全屏界面节点、固定界面节点、弹出模式节点、UI脚本节点

_CanTransformNormal = UnityHelper.FindTheChild(_CanvasTransform.gameObject,SysDefine.SYS_CANVAS_NORMAL_NODE_NAME);

_CanTransformFixed = UnityHelper.FindTheChild(_CanvasTransform.gameObject,SysDefine.SYS_CANVAS_FIXED_NODE_NAME);

_CanTransformPopUp = UnityHelper.FindTheChild(_CanvasTransform.gameObject,SysDefine.SYS_CANVAS_POPUP_NODE_NAME);

_CanTransformUIScripts = UnityHelper.FindTheChild(_CanvasTransform.gameObject,SysDefine.SYS_CANVAS_UISCRIPTS_NODE_NAME);

//把本脚本实例,作为Canvas的子节点

UnityHelper.AddChildToParent(_CanTransformUIScripts,this.gameObject.transform);

//本UI节点信息,场景转换时,不允许销毁

DontDestroyOnLoad(_CanvasTransform);

//初始化“UI窗体预设”路径数据

InitUIFormsPathsData();

}

///<summary>

///显示UI窗体

///</summary>

///<param name="strUIFormName">UI窗体的名称</param>

publicvoid ShowUIForms(stringstrUIFormName)

{

BaseUIFormsbaseUIForms; //UI窗体基类

//参数检查

if(string.IsNullOrEmpty(strUIFormName)) return;

//加载“UI窗体名称”,到“所有UI窗体缓存”中

baseUIForms =LoadUIFormsToAllUIFormsCatch(strUIFormName);

if(baseUIForms == null) return;

//判断是否清空“栈”结构体集合

if(baseUIForms.CurrentUIType.IsClearReverseChange)

{

ClearStackArray();

}

//判断不同的窗体显示模式,分别进行处理

switch(baseUIForms.CurrentUIType.UIForms_ShowMode)

{

caseUIFormsShowMode.Normal:

EnterUIFormsCache(strUIFormName);

break;

caseUIFormsShowMode.ReverseChange:

PushUIForms(strUIFormName);

break;

caseUIFormsShowMode.HideOther:

EnterUIFormstToCacheHideOther(strUIFormName);

break;

default:

break;

}

}

///<summary>

///关闭或返回上一个UI窗体(关闭当前UI窗体)

///</summary>

publicvoid CloseOrReturnUIForms(stringstrUIFormName)

{

BaseUIFormsbaseUIForms = null; //UI窗体基类

/* 参数检查 */

if(string.IsNullOrEmpty(strUIFormName)) return;

//“所有UI窗体缓存”如果没有记录,则直接返回。

_DicALLUIForms.TryGetValue(strUIFormName, outbaseUIForms);

if(baseUIForms == null) return;

/* 判断不同的窗体显示模式,分别进行处理 */

switch(baseUIForms.CurrentUIType.UIForms_ShowMode)

{

caseUIFormsShowMode.Normal:

ExitUIFormsCache(strUIFormName);

break;

caseUIFormsShowMode.ReverseChange:

PopUIForms();

break;

caseUIFormsShowMode.HideOther:

ExitUIFormsFromCacheAndShowOther(strUIFormName);

break;

default:

break;

}

}

#region私有方法

///<summary>

///根据指定UI窗体名称,加载到“所有UI窗体”缓存中。

///</summary>

///<param name="strUIFormName">UI窗体名称</param>

///<returns></returns>

privateBaseUIForms LoadUIFormsToAllUIFormsCatch(stringstrUIFormName)

{

BaseUIFormsbaseUI; //UI窗体

//判断“UI预设缓存集合”是否有指定的UI窗体,否则新加载窗体

_DicALLUIForms.TryGetValue(strUIFormName, outbaseUI);

if(baseUI == null)

{

//加载指定路径的“UI窗体”

baseUI =LoadUIForms(strUIFormName);

}

returnbaseUI;

}

///<summary>

///加载UI窗体到“当前显示窗体集合”缓存中。

///</summary>

///<param name="strUIFormsName"></param>

privatevoid EnterUIFormsCache(stringstrUIFormsName)

{

BaseUIFormsbaseUIForms; //UI窗体基类

BaseUIFormsbaseUIFormsFromAllCache; //"所有窗体集合"中的窗体基类

//“正在显示UI窗体缓存”集合里有记录,则直接返回。

_DicCurrentShowUIForms.TryGetValue(strUIFormsName, outbaseUIForms);

if(baseUIForms != null) return;

//把当前窗体,加载到“正在显示UI窗体缓存”集合里

_DicALLUIForms.TryGetValue(strUIFormsName, outbaseUIFormsFromAllCache);

if(baseUIFormsFromAllCache != null)

{

_DicCurrentShowUIForms.Add(strUIFormsName, baseUIFormsFromAllCache);

baseUIFormsFromAllCache.Display();

}

}

///<summary>

///卸载UI窗体从“当前显示窗体集合”缓存中。

///</summary>

///<paramname="strUIFormsName"></param>

privatevoid ExitUIFormsCache(stringstrUIFormsName)

{

BaseUIFormsbaseUIForms; //UI窗体基类

//“正在显示UI窗体缓存”集合没有记录,则直接返回。

_DicCurrentShowUIForms.TryGetValue(strUIFormsName, outbaseUIForms);

if(baseUIForms == null) return;

//指定UI窗体,运行隐藏状态,且从“正在显示UI窗体缓存”集合中移除。

baseUIForms.Hiding();

_DicCurrentShowUIForms.Remove(strUIFormsName);

}

///<summary>

///加载UI窗体到“当前显示窗体集合”缓存中,且隐藏其他正在显示的页面

///</summary>

///<paramname="strUIFormsName"></param>

privatevoid EnterUIFormstToCacheHideOther(stringstrUIFormsName)

{

BaseUIFormsbaseUIForms; //UI窗体基类

BaseUIFormsbaseUIFormsFromAllCache; //"所有窗体集合"中的窗体基类

//“正在显示UI窗体缓存”集合里有记录,则直接返回。

_DicCurrentShowUIForms.TryGetValue(strUIFormsName, outbaseUIForms);

if(baseUIForms != null) return;

//“正在显示UI窗体缓存”与“栈缓存”集合里所有窗体进行隐藏处理。

foreach(BaseUIForms baseUIFormsItem in_DicCurrentShowUIForms.Values)

{

baseUIFormsItem.Hiding();

}

foreach(BaseUIForms basUIFormsItem in_StaCurrentUIForms)

{

basUIFormsItem.Hiding();

}

//把当前窗体,加载到“正在显示UI窗体缓存”集合里

_DicALLUIForms.TryGetValue(strUIFormsName,out baseUIFormsFromAllCache);

if(baseUIFormsFromAllCache != null)

{

_DicCurrentShowUIForms.Add(strUIFormsName, baseUIFormsFromAllCache);

baseUIFormsFromAllCache.Display();

}

}

///<summary>

///卸载UI窗体从“当前显示窗体集合”缓存中,且显示其他原本需要显示的页面

///</summary>

///<paramname="strUIFormsName"></param>

privatevoidExitUIFormsFromCacheAndShowOther(stringstrUIFormsName)

{

BaseUIFormsbaseUIForms; //UI窗体基类

//“正在显示UI窗体缓存”集合没有记录,则直接返回。

_DicCurrentShowUIForms.TryGetValue(strUIFormsName, outbaseUIForms);

if(baseUIForms == null) return;

//指定UI窗体,运行隐藏状态,且从“正在显示UI窗体缓存”集合中移除。

baseUIForms.Hiding();

_DicCurrentShowUIForms.Remove(strUIFormsName);

//“正在显示UI窗体缓存”与“栈缓存”集合里所有窗体进行再次显示处理。

foreach(BaseUIForms baseUIFormsItem in_DicCurrentShowUIForms.Values)

{

baseUIFormsItem.Redisplay();

}

foreach(BaseUIForms basUIFormsItem in_StaCurrentUIForms)

{

basUIFormsItem.Redisplay();

}

}

///<summary>

///UI窗体入栈

///功能1: 判断栈里是否已经有窗体,有则“冻结”

/// 2: 先判断“UI预设缓存集合”是否有指定的UI窗体,有则处理。

/// 3: 指定UI窗体入"栈"

///</summary>

///<paramname="strUIFormsName"></param>

privatevoid PushUIForms(stringstrUIFormsName)

{

BaseUIFormsbaseUI; //UI预设窗体

//判断栈里是否已经有窗体,有则“冻结”

if(_StaCurrentUIForms.Count > 0)

{

BaseUIFormstopUIForms = _StaCurrentUIForms.Peek();

topUIForms.Freeze();

}

//先判断“UI预设缓存集合”是否有指定的UI窗体,有则处理。

_DicALLUIForms.TryGetValue(strUIFormsName, outbaseUI);

if(baseUI != null)

{

baseUI.Display();

}

else

{

Log.Write(GetType()+ string.Format("/PushUIForms()/ baseUI==null! 核心错误,请检查strUIFormsName={0}", strUIFormsName), Log.Level.High);

}

//指定UI窗体入"栈"

_StaCurrentUIForms.Push(baseUI);

}

///<summary>

///UI窗体出栈逻辑

///</summary>

privatevoid PopUIForms()

{

if(_StaCurrentUIForms.Count >= 2)

{

/* 出栈逻辑 */

BaseUIFormstopUIForms = _StaCurrentUIForms.Pop();

//出栈的窗体,进行隐藏处理

topUIForms.Hiding();

//出栈窗体的下一个窗体逻辑

BaseUIFormsnextUIForms = _StaCurrentUIForms.Peek();

//下一个窗体"重新显示"处理

nextUIForms.Redisplay();

}

elseif (_StaCurrentUIForms.Count == 1)

{

/* 出栈逻辑 */

BaseUIFormstopUIForms = _StaCurrentUIForms.Pop();

//出栈的窗体,进行"隐藏"处理

topUIForms.Hiding();

}

}

///<summary>

///加载与显示UI窗体

///功能:

/// 1:根据“UI窗体预设”名称,加载预设克隆体。

/// 2:预设克隆体添加UI“根节点”为父节点。

/// 3:隐藏刚创建的UI克隆体。

/// 4:新创建的“UI窗体”,加入“UI窗体缓存”中

///</summary>

privateBaseUIForms LoadUIForms(stringstrUIFormsName)

{

stringstrUIFormsPaths = null; //UI窗体的路径

GameObjectgoCloneUIPrefab = null; //克隆的"窗体预设"

BaseUIFormsbaseUIForm; //UI窗体

//得到UI窗体的路径

_DicUIFormsPaths.TryGetValue(strUIFormsName, outstrUIFormsPaths);

//加载指定路径的“UI窗体”

if(!string.IsNullOrEmpty(strUIFormsPaths))

{

goCloneUIPrefab = ResourcesMgr.GetInstance().LoadAsset(strUIFormsPaths,false);

}

//设置“UI窗体”克隆体的父节点,以及隐藏处理与加入“UI窗体缓存”中

if(_CanvasTransform != null&& goCloneUIPrefab != null)

{

baseUIForm = goCloneUIPrefab.GetComponent<BaseUIForms>();

if(baseUIForm == null)

{

Log.Write(GetType()+ string.Format("/LoadUIForms()/ baseUIForm==null!,请先确认克隆对象上是否加载了BaseUIForms的子类。参数 strUIFormsName='{0}' ", strUIFormsName), Log.Level.High);

returnnull;

}

switch(baseUIForm.CurrentUIType.UIForms_Type)

{

caseUIFormsType.Normal:

goCloneUIPrefab.transform.SetParent(_CanTransformNormal,false);

break;

caseUIFormsType.Fixed:

goCloneUIPrefab.transform.SetParent(_CanTransformFixed, false);

break;

caseUIFormsType.PopUp:

goCloneUIPrefab.transform.SetParent(_CanTransformPopUp, false);

break;

default:

break;

}

goCloneUIPrefab.SetActive(false);

//新创建的“UI窗体”,加入“UI窗体缓存”中

_DicALLUIForms.Add(strUIFormsName, baseUIForm);

returnbaseUIForm;

}

else

{

Log.Write(GetType()+ string.Format("/LoadUIForms()/‘_CanvasTransform’ Or ‘goCloneUIPrefab’==NULL! , 方法参数strUIFormsName={0},请检查!", strUIFormsName), Log.Level.High);

}

Log.Write(GetType()+ string.Format("/LoadUIForms()/ 出现不可预知错误,请检查! 方法参数strUIFormsName={0}", strUIFormsName), Log.Level.High);

returnnull;

}

///<summary>

///初始化项目开始必须的资源加载

///</summary>

privatevoid InitRootCanvasLoading()

{

if(UnityHelper.isFirstLoad)

{

ResourcesMgr.GetInstance().LoadAsset(SysDefine.SYS_PATH_CANVAS, false);

}

}

///<summary>

///初始化“UI窗体预设”路径数据

///</summary>

privatevoid InitUIFormsPathsData()

{

//测试也成功

IConfigManagerconfigMgr = newConfigManagerByJson(SysDefine.SYS_PATH_UIFormConfigJson);

if(_DicUIFormsPaths != null)

{

_DicUIFormsPaths =configMgr.AppSetting;

}

}

///<summary>

///清空“栈”结构体集合

///</summary>

///<returns></returns>

privatebool ClearStackArray()

{

if(_StaCurrentUIForms != null&& _StaCurrentUIForms.Count >= 1)

{

_StaCurrentUIForms.Clear();

returntrue;

}

returnfalse;

}

#endregion

}//Class_end

}


以上代码解释:

1: UIManager.cs 中定义的新的字段 ,“_StaCurrentUIForms” 就是一个“栈”数据类型,用于维护一种后进先出的数据结构。常见的方法如下:

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

 Count 属性 查询栈内元素数量

 Push() 压栈

 Pop() 出栈

 Peek() 查询栈顶元素

 GetEnumerator() 遍历栈中所有元素


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


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


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