游戏的UI系统往往会比较复杂,工作量比较庞大,需要多人协作完成,为了开发和维护方便,有必要对UI系统进行管理。

一.制作预制件

将UI的各个不同的功能面板制作为预制件,放入Resources目录下,方便加载预制件。

二.开发对应预制件的枚举类,使用json存储预制件名称和地址的对应关系,预制件放在Resources目录下方便加载

public enum UIPanelType
{
ItemMessagePanel,
KnapsackPanel,
MainManuPanel,
ShopPanel,
SkillPanel,
SystemPanel,
TaskPanel
}
{
"infoList":
[
{
"panelTypeString":"ItemMessagePanel","path":"UI/ItemMessagePanel"
},
{
"panelTypeString":"KnapsackPanel","path":"UI/KnapsackPanel"
},
{
"panelTypeString":"MainManuPanel","path":"UI/MainManuPanel"
},
{
"panelTypeString":"ShopPanel","path":"UI/ShopPanel"
},
{
"panelTypeString":"SkillPanel","path":"UI/SkillPanel"
},
{
"panelTypeString":"SystemPanel","path":"UI/SystemPanel"
},
{
"panelTypeString":"TaskPanel","path":"UI/TaskPanel"
}
]
}

三.开发UIManager类

UIManager类只能实例化一次,使用单例模式。提供解析json的方法,在对象实例化时调用这个方法并将解析出来的预制件名称和地址使用字典存储。

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class UIManager
{
//存储所有面板prefab路径的字典
private Dictionary<UIPanelType, string> panelPahtDict = new Dictionary<UIPanelType, string>(); private static UIManager _instance;
//单例模式,提供get方法
public static UIManager Instance
{
get
{
if (_instance == null)
_instance = new UIManager();
return _instance;
}
} //单例模式,将构造方法私有化
private UIManager() {
ParseUIPannelJson();
} //解析json
private void ParseUIPannelJson()
{
//读取json的内容
TextAsset textAsset = Resources.Load<TextAsset>("UIPanelType");
//解析json,存储为UIPanelTypeJson内部类对象,该对象包含UIPanelInfo的list,UIPanelInfo的成员变量和json中的数据名称相同
UIPanelTypeJson jsonObject = JsonUtility.FromJson<UIPanelTypeJson>(textAsset.text);
//遍历list将对象中的值存储到字典中
foreach(UIPanelInfo info in jsonObject.infoList)
{
panelPahtDict.Add(info.panelType,info.path);
}
} public class UIPanelTypeJson
{
public List<UIPanelInfo> infoList;
} }

提供用于实例化的对象模板类,成员变量和json的数据对应好。因为自定义的枚举类型序列化时会出现问题,所以不序列化枚举类型,转而提供对应的字符串变量,然后类继承ISerializationCallbackReceiver类,实现OnAfterDeserialize和OnBeforeSerialize方法,对应序列化前和反序列化后的行为,其中将字符串变量和枚举变量的值相互转化。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine; [Serializable]
public class UIPanelInfo : ISerializationCallbackReceiver
{
[NonSerialized]
public UIPanelType panelType; public string panelTypeString;
public string path; public void OnAfterDeserialize()
{
panelType = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), panelTypeString);
} public void OnBeforeSerialize()
{
panelTypeString = panelType.ToString();
}
}

三.管理所有面板实例化

管理面板的实例化使用多态特性

首先创建一个面板的基类BasePanel

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class BasePanel : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{ } // Update is called once per frame
void Update()
{ }
}

接下来在不同的面板上创建并挂载不同的面板脚本,但是都继承面板基类

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class ItemMessagePanel : BasePanel
{
// Start is called before the first frame update
void Start()
{ } // Update is called once per frame
void Update()
{ }
}

最后在UIManager类中管理面板:

使用字典保存实例化好的面板信息

    //保存实例化出来的物体的BasePanel组件
private Dictionary<UIPanelType, BasePanel> panelDict = new Dictionary<UIPanelType, BasePanel>();

获取画布的transform组件,面板需要设置为画布的子物体

    //画布的transform组件,用于为面板设定父子级关系等参数
private Transform cavasTransform;
private Transform CavasTransform
{
get
{
if (cavasTransform == null)
cavasTransform = GameObject.Find("Canvas").transform;
return cavasTransform;
}
}

提供获取根据面板类型获取面板上BasePanel组件的方法,如果面板不存在则创建面板

    /// <summary>
/// 根据给定的panel类型获取物体身上的BasePanel组件
/// </summary>
/// <param name="panelType"></param>
/// <returns></returns>
private BasePanel GetPanel(UIPanelType panelType)
{
BasePanel panel;
if (!panelDict.ContainsKey(panelType))
{
string path;
panelPathDict.TryGetValue(panelType, out path);
GameObject go = GameObject.Instantiate(Resources.Load(path)) as GameObject;
//设置panel的父级关系同时设置坐标为局部坐标,否则默认世界坐标显示会出现问题
go.transform.SetParent(CavasTransform,false);
panel = go.GetComponent<BasePanel>();
panelDict.Add(panelType, panel);
}
else
{
panelDict.TryGetValue(panelType, out panel);
}
return panel;
}

四.面板的显示

面板的显示遵循先显示的后移除,后显示的先移除的原则,所以使用栈管理所有面板的显示

提供存储已显示面板的栈

    //使用栈保存显示在cavas中的面板
private Stack<BasePanel> panelStack = new Stack<BasePanel>();

提供入栈的方法,即显示面板

/// <summary>
/// 入栈,即显示面板
/// </summary>
/// <param name="panelType"></param>
public void PushPanel(UIPanelType panelType)
{
BasePanel panel = GetPanel(panelType);
panelStack.Push(panel);
}

在画布上挂载UIRoot脚本,用于管理面板的显示

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class UIRoot : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
UIManager.Instance.PushPanel(UIPanelType.MainManuPanel);
} }

提供ButtonRegister脚本,用于注册按钮,按下后显示界面,要显示的界面通过公开的UIPanelTypeString变量指定

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; public class ButtonRegist : MonoBehaviour
{
public string UIPanelTypeString;
private Button btn;
private void Awake()
{
btn = gameObject.GetComponent<Button>();
}
private void Start()
{
btn.onClick.AddListener(OnButtonClick);
} private void OnButtonClick()
{
UIPanelType type = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), UIPanelTypeString);
UIManager.Instance.PushPanel(type);
}
}

五.面板的生命周期

在BasePanel中提供虚拟方法,对应面板的生命周期,在具体面板中实现不同的生命周期行为

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class BasePanel : MonoBehaviour
{
/// <summary>
/// 面板显示
/// </summary>
public virtual void OnEnter()
{ }
/// <summary>
/// 面板暂停
/// </summary>
public virtual void OnPause()
{ }
/// <summary>
/// 继续面板
/// </summary>
public virtual void OnResume()
{ }
/// <summary>
/// 退出面板
/// </summary>
public virtual void OnExit()
{ }
}

如在主菜单面板中,当其他面板显示时主菜单面板不能点击,使用CanvasGroup组件,将blocksRaycasts变量设置为false使面板不与鼠标交互

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class MainManuPanel : BasePanel
{
private CanvasGroup canvasGroup;
void Start()
{
canvasGroup = GetComponent<CanvasGroup>();
} public override void OnPause()
{
canvasGroup.blocksRaycasts = false;
}
}

在UIManager中改写入栈方法,添加暂停栈顶面板的功能

/// <summary>
/// 入栈,即显示面板
/// </summary>
/// <param name="panelType"></param>
public void PushPanel(UIPanelType panelType)
{
//判断栈顶是否有面板,有的话将栈顶面板暂停
if(panelStack.Count > 0)
{
panelStack.Peek().OnPause();
} BasePanel panel = GetPanel(panelType);
panel.OnEnter();
panelStack.Push(panel);
}

六.面板的关闭

面板的关闭需要将面板出栈,所以在出栈方法中实现

    /// <summary>
/// 出栈,移除最上层的面板
/// </summary>
public void PopPanel()
{
//判断栈顶是否有面板,没有的话直接返回
if (panelStack.Count == 0) return;
//弹出栈顶面板,调用其生命周期函数的退出方法
panelStack.Pop().OnExit();
//判断栈内是否还有面板,有的话将其激活
if (panelStack.Count == 0) return;
panelStack.Peek().OnResume();
}

修改按钮注册方法,实现面板的关闭按钮的注册,在所有面板预制件的关闭按钮上添加按钮注册脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; public class ButtonRegist : MonoBehaviour
{
public string UIPanelTypeString;
//按钮
private Button btn;
//是否是面板
public bool isPanel = false;
private void Awake()
{
btn = GetComponent<Button>();
}
private void Start()
{
//判断是否是面板,根据结果进行注册
if (isPanel)
btn.onClick.AddListener(UIManager.Instance.PopPanel);
else
btn.onClick.AddListener(OnButtonClick);
} private void OnButtonClick()
{
UIPanelType type = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), UIPanelTypeString);
UIManager.Instance.PushPanel(type);
}
}

在具体的面板上重写面板生命周期的方法,通过调整CanvasGroup组件的alpha值实现组件的显示和隐藏,通过设置CanvasGroup组件的blocksRaycasts 的值设置组件的可点击状态,如主面板的MainManuPanel组件

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class MainManuPanel : BasePanel
{
private CanvasGroup canvasGroup;
void Start()
{
if(canvasGroup == null)
canvasGroup = GetComponent<CanvasGroup>();
} public override void OnPause()
{
canvasGroup.blocksRaycasts = false;
} public override void OnEnter()
{
if (canvasGroup == null)
canvasGroup = GetComponent<CanvasGroup>();
canvasGroup.alpha = 1;
canvasGroup.blocksRaycasts = true;
} public override void OnExit()
{
canvasGroup.alpha = 0;
canvasGroup.blocksRaycasts = false;
} public override void OnResume()
{
canvasGroup.blocksRaycasts = true;
}
}

七.优化细节

1.重写按钮注册的方法,提供按钮注册基类实现按钮注册,具体的被注册方法为虚拟方法等待重写,不同的按钮只需要实现注册基类重写方法即可。

按钮注册基类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; public class ButtonRegistRoot : MonoBehaviour
{
//按钮
private Button btn; private void Awake()
{
btn = GetComponent<Button>();
}
private void Start()
{
btn.onClick.AddListener(OnButtonClick);
} public virtual void OnButtonClick()
{ }
}

关闭按钮:

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class CloseButtonRegist : ButtonRegistRoot
{
public override void OnButtonClick()
{
UIManager.Instance.PopPanel();
}
}

菜单按钮:

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class ManuButtonRegist : ButtonRegistRoot
{
public string UIPanelTypeString; public override void OnButtonClick()
{
UIPanelType type = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), UIPanelTypeString);
UIManager.Instance.PushPanel(type);
}
}

背包中物品显示信息按钮:

using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class ItemButtonRegist : ButtonRegistRoot
{
public override void OnButtonClick()
{
UIManager.Instance.PushPanel(UIPanelType.ItemMessagePanel);
}
}

2.UI动画

UI动画现在就比较简单了,使用DOTWEEN设置动画,接下来哪个面板需要动画只需要在该面板的生命周期的具体方法中设置具体的动画即可。

八.总结

整个框架中的类是比较多的,具体可以做以下分类:

UIManager:单例模式,负责整个UI框架的管理。实现了从json中读取UI的预制件的地址和根据地址读取UI的预制件,因此当预制件被移动时我们只需要到json文件中作相应的修改即可,不用动代码。实现了使用栈存储显示的面板,提供相应的入栈和出栈方法对应面板的显示和隐藏。

UIPanelType:对应所有面板预制件的枚举类。

UIPanelInfo:用于读取UI面板预制件地址的包装类。

UIRoot:用于启动UI的类,调用UIManager的方法加载主菜单面板。

BasePanel:面板基类,定义面板的生命周期方法,所有面板都挂载继承这个类的脚本,并实现具体面板的生命周期方法。

ButtonRegistRoot:按钮注册方法的基类,实现了按钮注册,定义了供按钮注册的方法,每个按钮都挂载具体的按钮注册脚本,只需要实现被注册的方法即可。

UI的管理的更多相关文章

  1. Unity3d:UI面板管理整合进ToLua

    本文基于 https://github.com/chiuan/TTUIFramework https://github.com/jarjin/LuaFramework_UGUI 进行的二次开发,Tha ...

  2. iOS10 UI教程管理层次结构

    iOS10 UI教程管理层次结构 iOS10 UI教程管理层次结构,在一个应用程序中,如果存在多个层次结构,就需要对这些层次结构进行管理.在UIView类中提供了可以用来管理层次结构的方法,让开发者可 ...

  3. Android -- UI布局管理,相对布局,线性布局,表格布局,绝对布局,帧布局

    1. 相对布局 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmln ...

  4. Docker整合dockerfly实现UI界面管理(单机版)

    一.搜索镜像 docker search dockerfly 二.根据镜像使用排名(一般情况下拉取使用率最高的镜像名),我这里使用的是阿里云镜像地址 docker pull registry.cn-h ...

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

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

  6. 使用 Portainer UI 管理 Docker 主机

    Docker 使用命令行的方式来管理有时候并没有那么直观,可以使用 Portainer 的 UI 来管理 Docker 主机和 Docker Swarm 集群. 安装 Portainer 环境:cen ...

  7. [cb]NGUI事件及复杂UI管理

    事件管理 看了有些文章关于NGUI的事件管理,许多人的做法的是封装一个事件处理层,避免在每个UI控件上都绑定事件处理脚本.本文说说我们项目中的UI事件管理吧. UIEventListener 我们项目 ...

  8. python2.7 操作ceph-cluster S3对象接口 实现: 上传 下载 查询 删除 顺便使用Docker装个owncloud 实现UI管理

    python version:    python2.7 需要安装得轮子: botofilechunkio command: yum install python-pip&& pip ...

  9. Unity 游戏框架:UI 管理神器 UI Kit

    UI Kit 快速入门 首先我们来进行 UI Kit 的快速入门 制作一个界面的,步骤如下: 准备 生成代码 逻辑编写 运行 1. 准备 先创建一个场景 TestUIHomePanel. 删除 Hie ...

随机推荐

  1. 用python做youtube自动化下载器 思路

    目录 0. 思路 1.准备 i.savfrom.net 2. 探索并规划获取方式 i.总览 ii. 获取该网页取到下载url的请求 iii. 在本地获取请求 iv.解析请求结果 v.解析解密后的结果 ...

  2. uni-app阻止事件向父级冒泡

    在官网找到的就只有这个方法,但是我放在app项目里并不支持,所以就想到vue的阻止事件冒泡的方法,现在分享,免得大家踩坑     <view class="parent" @ ...

  3. 计算机考研复试真题 a+b(大数加法)

    题目描述 实现一个加法器,使其能够输出a+b的值. 输入描述: 输入包括两个数a和b,其中a和b的位数不超过1000位. 输出描述: 可能有多组测试数据,对于每组数据, 输出a+b的值. 示例1 输入 ...

  4. Selenium WebDriver 8大定位方式

    Selenium WebDriver 8大定位方式: driver.find_element_by_id() driver.find_element_by_name() driver.find_ele ...

  5. 【Oracle】Oracle中chr()的含义

    oracle中chr含义 CHR(10)和 CHR(13)--在oracle都为换行 chr(32)--表示空格 DECLARE v_a VARCHAR2(255); v_b VARCHAR2(255 ...

  6. leetcode 117. 填充每个节点的下一个右侧节点指针 II(二叉树,DFS)

    题目链接 https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/ 题目大意 给定一个二叉树 s ...

  7. window安装nvm

    先说一下背景,最近做的两个项目一个是祖传angularjs1.X版本另一个是react hooks结合tailwindcss,前者angularjs的node版本比较低,而tailwindcss的no ...

  8. mysql5.5 升级至5.7

    mysql5.5 升级至5.7 1.下载mysql5.7.32 官方下载地址 解压 tar xvf mysql.tar.gz mysql/ 2. 进入旧的mysql的bin目录下导出mysql的数据 ...

  9. Dubbo的设计理念原来就藏在这三张图中

    Dubbo在众多的微服务框架中脱颖而出,占据RPC服务框架的半壁江山,非常具有普适性,熟练掌握 Dubbo的应用技巧后深刻理解其内部实现原理,让大家能更好的掌控工作,助力职场,特别能让大家在面试中脱颖 ...

  10. apijson简单使用

    apijson简单使用 介绍 APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库.为简单的增删改查.复杂的查询.简单的事务操作 提供了完全自动化的 ...