游戏的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. 一言不合就开始搞JDK源码

    ​Java是一门面向对象的编程语言,那什么是面向对象呢,下面将是历史上最通俗易懂的解释了,请看下图: 哈哈,解释的够清楚的了吧.闪. 从源码学编程的好处 学Java编程时,最好同时看一些Java的源码 ...

  2. 树莓派(4B)新手入门教程

    前期准备 必要物料 树莓派4B 主机 Type-C 电源 内存卡(8G+) 一般建议一步到位64G 系统镜像 镜像写入工具 下载地址 镜像下载 官方下载地址: https://www.raspberr ...

  3. 风炫安全web安全学习第三十二节课 Python代码执行以及代码防御措施

    风炫安全web安全学习第三十二节课 Python代码执行以及代码防御措施 Python 语言可能发生的命令执行漏洞 内置危险函数 eval和exec函数 eval eval是一个python内置函数, ...

  4. 设计模式之单例模式(Singleton Pattern)深入浅出

    单例模式介绍:单例模式是指确保一个类在任何情况下都绝对只有一个实例,并且提供一个全局的访问点.隐藏其所有构造方法,属于创新型模式. 常见的单例有:ServletContext.ServletConfi ...

  5. gin框架的路由源码解析

    前言 本文转载至 https://www.liwenzhou.com/posts/Go/read_gin_sourcecode/ 可以直接去原文看, 比我这里直观 我这里只是略微的修改 正文 gin的 ...

  6. 【JavaWeb】JavaScript 基础

    JavaScript 基础 事件 事件是指输入设备与页面之间进行交互的响应. 常用的事件: onload 加载完成事件:页面加载完成之后,常用于页面 js 代码初始化操作: onclick 单击事件: ...

  7. web网上书店总结(jsp+servlet)

    web网上书店总结 前端的首页.效果如下: 基本上按照页面有的内容对其实现功能.按照用户划分功能模块,有后台管理员和普通用户,登录的时候会判断账户的类别,例如0权限代表普通用户登录,1权限代表管理员登 ...

  8. Linux学习笔记 | 常见错误之无法获得锁

    问题: 当运行sudo apt-get install/update/其他命令时,会出现如下提示: E: 无法获得锁 /var/lib/dpkg/lock-frontend - open (11: 资 ...

  9. 【排序基础】5、插入排序法 - Insertion Sort

    插入排序法 - Insertion Sort 文章目录 插入排序法 - Insertion Sort 插入排序设计思想 插入排序代码实现 操作:插入排序与选择排序的比较 简单记录-bobo老师的玩转算 ...

  10. Getshell

    GetShell 常用免杀大法 一.编码大法 (1).一句话马子本身采用编码 原文:<?php @eval($_GET(a)):?> 转码后:在提交的post的时候可以直接使用\u0026 ...