游戏UI框架设计(三)

---窗体的层级管理

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

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

  “普通显示”模式允许多个窗体同时显示,这种类型应用最多。例如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;
using UnityEngine.UI;
using System;
using System.Collections.Generic; namespace SUIFW
{
public class UIManager : MonoBehaviour
{
/* 字段 */
//本类实例
private static UIManager _Instance = null;
//存储所有“UI窗体预设(Prefab)”路径
//参数含义: 第1个string 表示“窗体预设”名称,后一个string 表示对应的路径
private Dictionary<string, string> _DicUIFormsPaths;
//缓存所有已经打开的“UI窗体预设(Prefab)”
//参数含义: 第1个string 表示“窗体预设”名称,后一个BaseUI 表示对应的“窗体预设”
private Dictionary<string, BaseUIForms> _DicALLUIForms;
//“栈”结构表示的“当前UI窗体”集合。
private Stack<BaseUIForms> _StaCurrentUIForms;
//当前显示状态的UI窗体集合
private Dictionary<string, BaseUIForms> _DicCurrentShowUIForms;
//UI根节点
private Transform _CanvasTransform = null;
//普通全屏界面节点
private Transform _CanTransformNormal = null;
//固定界面节点
private Transform _CanTransformFixed = null;
//弹出模式节点
private Transform _CanTransformPopUp = null;
//UI脚本节点(加载各种管理脚本的节点)
private Transform _CanTransformUIScripts = null; /// <summary>
/// 得到本类实例
/// </summary>
/// <returns></returns>
public static UIManager GetInstance()
{
if (_Instance == null)
{
_Instance = new GameObject("_UIManager").AddComponent<UIManager>();
}
return _Instance;
} void Awake()
{
//字段初始化
_DicUIFormsPaths = new Dictionary<string, string>();
_DicALLUIForms = new Dictionary<string, BaseUIForms>();
_StaCurrentUIForms = new Stack<BaseUIForms>();
_DicCurrentShowUIForms = new Dictionary<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>
public void ShowUIForms(string strUIFormName)
{
BaseUIForms baseUIForms; //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)
{
case UIFormsShowMode.Normal:
EnterUIFormsCache(strUIFormName);
break;
case UIFormsShowMode.ReverseChange:
PushUIForms(strUIFormName);
break;
case UIFormsShowMode.HideOther:
EnterUIFormstToCacheHideOther(strUIFormName);
break;
default:
break;
}
} /// <summary>
/// 关闭或返回上一个UI窗体(关闭当前UI窗体)
/// </summary>
public void CloseOrReturnUIForms(string strUIFormName)
{
BaseUIForms baseUIForms = null; //UI窗体基类 /* 参数检查 */
if (string.IsNullOrEmpty(strUIFormName)) return;
//“所有UI窗体缓存”如果没有记录,则直接返回。
_DicALLUIForms.TryGetValue(strUIFormName, out baseUIForms);
if (baseUIForms == null) return; /* 判断不同的窗体显示模式,分别进行处理 */
switch (baseUIForms.CurrentUIType.UIForms_ShowMode)
{
case UIFormsShowMode.Normal:
ExitUIFormsCache(strUIFormName);
break;
case UIFormsShowMode.ReverseChange:
PopUIForms();
break;
case UIFormsShowMode.HideOther:
ExitUIFormsFromCacheAndShowOther(strUIFormName);
break;
default:
break;
} } #region 私有方法
/// <summary>
/// 根据指定UI窗体名称,加载到“所有UI窗体”缓存中。
/// </summary>
/// <param name="strUIFormName">UI窗体名称</param>
/// <returns></returns>
private BaseUIForms LoadUIFormsToAllUIFormsCatch(string strUIFormName)
{
BaseUIForms baseUI; //UI窗体 //判断“UI预设缓存集合”是否有指定的UI窗体,否则新加载窗体
_DicALLUIForms.TryGetValue(strUIFormName, out baseUI);
if (baseUI == null)
{
//加载指定路径的“UI窗体”
baseUI = LoadUIForms(strUIFormName);
} return baseUI;
} /// <summary>
/// 加载UI窗体到“当前显示窗体集合”缓存中。
/// </summary>
/// <param name="strUIFormsName"></param>
private void EnterUIFormsCache(string strUIFormsName)
{
BaseUIForms baseUIForms; //UI窗体基类
BaseUIForms baseUIFormsFromAllCache; //"所有窗体集合"中的窗体基类 //“正在显示UI窗体缓存”集合里有记录,则直接返回。
_DicCurrentShowUIForms.TryGetValue(strUIFormsName, out baseUIForms);
if (baseUIForms != null) return; //把当前窗体,加载到“正在显示UI窗体缓存”集合里
_DicALLUIForms.TryGetValue(strUIFormsName, out baseUIFormsFromAllCache);
if (baseUIFormsFromAllCache != null)
{
_DicCurrentShowUIForms.Add(strUIFormsName, baseUIFormsFromAllCache);
baseUIFormsFromAllCache.Display();
}
} /// <summary>
/// 卸载UI窗体从“当前显示窗体集合”缓存中。
/// </summary>
/// <param name="strUIFormsName"></param>
private void ExitUIFormsCache(string strUIFormsName)
{
BaseUIForms baseUIForms; //UI窗体基类 //“正在显示UI窗体缓存”集合没有记录,则直接返回。
_DicCurrentShowUIForms.TryGetValue(strUIFormsName, out baseUIForms);
if (baseUIForms == null) return; //指定UI窗体,运行隐藏状态,且从“正在显示UI窗体缓存”集合中移除。
baseUIForms.Hiding();
_DicCurrentShowUIForms.Remove(strUIFormsName);
} /// <summary>
/// 加载UI窗体到“当前显示窗体集合”缓存中,且隐藏其他正在显示的页面
/// </summary>
/// <param name="strUIFormsName"></param>
private void EnterUIFormstToCacheHideOther(string strUIFormsName)
{
BaseUIForms baseUIForms; //UI窗体基类
BaseUIForms baseUIFormsFromAllCache; //"所有窗体集合"中的窗体基类 //“正在显示UI窗体缓存”集合里有记录,则直接返回。
_DicCurrentShowUIForms.TryGetValue(strUIFormsName, out baseUIForms);
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>
/// <param name="strUIFormsName"></param>
private void ExitUIFormsFromCacheAndShowOther(string strUIFormsName)
{
BaseUIForms baseUIForms; //UI窗体基类 //“正在显示UI窗体缓存”集合没有记录,则直接返回。
_DicCurrentShowUIForms.TryGetValue(strUIFormsName, out baseUIForms);
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>
/// <param name="strUIFormsName"></param>
private void PushUIForms(string strUIFormsName)
{
BaseUIForms baseUI; //UI预设窗体 //判断栈里是否已经有窗体,有则“冻结”
if (_StaCurrentUIForms.Count > )
{
BaseUIForms topUIForms = _StaCurrentUIForms.Peek();
topUIForms.Freeze();
} //先判断“UI预设缓存集合”是否有指定的UI窗体,有则处理。
_DicALLUIForms.TryGetValue(strUIFormsName, out baseUI);
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>
private void PopUIForms()
{
if (_StaCurrentUIForms.Count >= )
{
/* 出栈逻辑 */
BaseUIForms topUIForms = _StaCurrentUIForms.Pop();
//出栈的窗体,进行隐藏处理
topUIForms.Hiding();
//出栈窗体的下一个窗体逻辑
BaseUIForms nextUIForms = _StaCurrentUIForms.Peek();
//下一个窗体"重新显示"处理
nextUIForms.Redisplay();
}
else if (_StaCurrentUIForms.Count == )
{
/* 出栈逻辑 */
BaseUIForms topUIForms = _StaCurrentUIForms.Pop();
//出栈的窗体,进行"隐藏"处理
topUIForms.Hiding();
}
} /// <summary>
/// 加载与显示UI窗体
/// 功能:
/// 1:根据“UI窗体预设”名称,加载预设克隆体。
/// 2:预设克隆体添加UI“根节点”为父节点。
/// 3:隐藏刚创建的UI克隆体。
/// 4:新创建的“UI窗体”,加入“UI窗体缓存”中
/// </summary>
private BaseUIForms LoadUIForms(string strUIFormsName)
{
string strUIFormsPaths = null; //UI窗体的路径
GameObject goCloneUIPrefab = null; //克隆的"窗体预设"
BaseUIForms baseUIForm; //UI窗体 //得到UI窗体的路径
_DicUIFormsPaths.TryGetValue(strUIFormsName, out strUIFormsPaths); //加载指定路径的“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);
return null;
}
switch (baseUIForm.CurrentUIType.UIForms_Type)
{
case UIFormsType.Normal:
goCloneUIPrefab.transform.SetParent(_CanTransformNormal, false);
break;
case UIFormsType.Fixed:
goCloneUIPrefab.transform.SetParent(_CanTransformFixed, false);
break;
case UIFormsType.PopUp:
goCloneUIPrefab.transform.SetParent(_CanTransformPopUp, false);
break;
default:
break;
} goCloneUIPrefab.SetActive(false);
//新创建的“UI窗体”,加入“UI窗体缓存”中
_DicALLUIForms.Add(strUIFormsName, baseUIForm);
return baseUIForm;
}
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);
return null;
} /// <summary>
/// 初始化项目开始必须的资源加载
/// </summary>
private void InitRootCanvasLoading()
{
if (UnityHelper.isFirstLoad)
{
ResourcesMgr.GetInstance().LoadAsset(SysDefine.SYS_PATH_CANVAS, false);
}
} /// <summary>
/// 初始化“UI窗体预设”路径数据
/// </summary>
private void InitUIFormsPathsData()
{
//测试也成功
IConfigManager configMgr = new ConfigManagerByJson(SysDefine.SYS_PATH_UIFormConfigJson);
if (_DicUIFormsPaths != null)
{
_DicUIFormsPaths = configMgr.AppSetting;
}
} /// <summary>
/// 清空“栈”结构体集合
/// </summary>
/// <returns></returns>
private bool ClearStackArray()
{
if (_StaCurrentUIForms != null && _StaCurrentUIForms.Count >= )
{
_StaCurrentUIForms.Clear();
return true;
}
return false;
} #endregion }//Class_end
}

以上代码解释:

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

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

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

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

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

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

游戏UI框架设计(三) : 窗体的层级管理的更多相关文章

  1. 游戏UI框架设计(四) : 模态窗体管理

    游戏UI框架设计(四) --模态窗体管理 我们在开发UI窗体时,对于"弹出窗体"往往因为需要玩家优先处理弹出小窗体,则要求玩家不能(无法)点击"父窗体",这种窗 ...

  2. 游戏UI框架设计(二) : 最简版本设计

    游戏UI框架设计(二) --最简版本设计 为降低难度决定先讲解一个最简版本,阐述UI框架的核心设计理念.这里先定义三个核心功能: 1:UI窗体的自动加载功能. 2:缓存UI窗体. 3:窗体生命周期(状 ...

  3. 游戏UI框架设计(一) : 架构设计理论篇

    游戏UI框架设计(一) ---架构设计理论篇 前几天(2017年2月)看到一篇文章,国内王健林.马云等大咖们看好的未来十大最有"钱途"产业中,排名第一的就是"泛娱乐&qu ...

  4. 游戏UI框架设计(五): 配置管理与应用

    游戏UI框架设计(五) --配置管理与应用 在开发企业级游戏/VR/AR产品时候,我们总是希望可以总结出一些通用的技术体系,框架结构等,为简化我们的开发起到"四两拨千金"的作用.所 ...

  5. 游戏UI框架设计(6): 消息传递中心

    游戏UI框架设计(6) --消息传递中心 最近一直忙于一个益智类游戏的研发工作,所以博客有段时间没有更新了.经过朋友的督促,决定这两天立刻完成最后的两篇博客讲解(UI框架).说起“消息传递中心”,或者 ...

  6. 游戏UI框架设计(7): 资源国际化技术

    游戏UI框架设计(7) --资源国际化技术 说起"资源国际化"技术,个人认为可以追述到微软Window2000 PC操作系统的发布,在这之前windows98操作系统的开发都是先由 ...

  7. JavaScript框架设计(三) push兼容性和选择器上下文

    JavaScript框架设计(三) push兼容性和选择器上下文 博主很久没有更博了. 在上一篇 JavaScript框架设计(二) 中实现了最基本的选择器,getId,getTag和getClass ...

  8. 《开源框架那些事儿22》:UI框架设计实战

    UI是User Interface的缩写.通常被觉得是MVC中View的部分,作用是提供跟人机交互的可视化操作界面. MVC中Model提供内容给UI进行渲染,用户通过UI框架产生响应,一般而言会由控 ...

  9. 自己动手设计并实现一个linux嵌入式UI框架(设计)

    看了"自己动手设计并实现一个linux嵌入式UI框架"显然没有尽兴,因为还没有看到庐山真面目,那我今天继续,先来说说,我用到了哪些知识背景.如:C语言基础知识,尤其是指针.函数指针 ...

随机推荐

  1. Java 抽象类和接口与多态

    引入抽象类和接口的原因 即"针对接口编程",关键就在多态,即向上转型 当变量的的声明类型是超类型时,即抽象类或者接口,这样,只要是具体实现此超类型的类所产生的对象,都可以指定给这个 ...

  2. css--position和float

    1.元素设置position:relative或者position:absolute后,才能激活left,top,right,bottom和z-index属性,默认情况下这些属性并未激活,设置了也会无 ...

  3. GB和GBDT 算法流程及分析

    1.优化模型的两种策略: 1)基于残差的方法 残差其实就是真实值和预测值之间的差值,在学习的过程中,首先学习一颗回归树,然后将“真实值-预测值”得到残差,再把残差作为一个学习目标,学习下一棵回归树,依 ...

  4. JAVA短信验证登录

    短信验证登陆 1.点击触发,以电话号码为参数调用发送验证登录短信方法 2.默认模板为验证模板 生成6位验证码 3.将生成的验证码和手机号码放入缓存,(已经设置好缓存存放时间) 4.调用发送模板短信方法 ...

  5. http之head请求(转)

    HTTP请求方法并不是只有GET和POST,只是最常用的.据RFC2616标准(现行的HTTP/1.1)得知,通常有以下8种方法:OPTIONS.GET.HEAD.POST.PUT.DELETE.TR ...

  6. Thinkphp代码生成工具 ThinkphpHelper

    支持MySQL 和 sqlite数据库,快速构建项目原型,直接生成前后台CRUD代码片段,还可根据需要自行定制代码模板,减少重复劳动. 写这个东西的原因是因为我最近沮丧的发现很多时候我都在做重复的事情 ...

  7. iOS NSTimer

    示例: //创建 scrollTimer =[NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selecto ...

  8. LCA 倍增

    最近公共祖先 对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. #include<cstdio> #include&l ...

  9. jQuery replaceWith replaceAll end的用法

    jQuery replaceWith replaceAll end的用法 <%@ page language="java" import="java.util.*& ...

  10. quick-cocos2d-x添加到Pomelo的支持

    https://github.com/luoxinliang/pomelo_quick_x/tree/master/pomelo_quick_x