UI的管理
游戏的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的管理的更多相关文章
- Unity3d:UI面板管理整合进ToLua
本文基于 https://github.com/chiuan/TTUIFramework https://github.com/jarjin/LuaFramework_UGUI 进行的二次开发,Tha ...
- iOS10 UI教程管理层次结构
iOS10 UI教程管理层次结构 iOS10 UI教程管理层次结构,在一个应用程序中,如果存在多个层次结构,就需要对这些层次结构进行管理.在UIView类中提供了可以用来管理层次结构的方法,让开发者可 ...
- Android -- UI布局管理,相对布局,线性布局,表格布局,绝对布局,帧布局
1. 相对布局 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmln ...
- Docker整合dockerfly实现UI界面管理(单机版)
一.搜索镜像 docker search dockerfly 二.根据镜像使用排名(一般情况下拉取使用率最高的镜像名),我这里使用的是阿里云镜像地址 docker pull registry.cn-h ...
- 游戏UI框架设计(四) : 模态窗体管理
游戏UI框架设计(四) --模态窗体管理 我们在开发UI窗体时,对于"弹出窗体"往往因为需要玩家优先处理弹出小窗体,则要求玩家不能(无法)点击"父窗体",这种窗 ...
- 使用 Portainer UI 管理 Docker 主机
Docker 使用命令行的方式来管理有时候并没有那么直观,可以使用 Portainer 的 UI 来管理 Docker 主机和 Docker Swarm 集群. 安装 Portainer 环境:cen ...
- [cb]NGUI事件及复杂UI管理
事件管理 看了有些文章关于NGUI的事件管理,许多人的做法的是封装一个事件处理层,避免在每个UI控件上都绑定事件处理脚本.本文说说我们项目中的UI事件管理吧. UIEventListener 我们项目 ...
- python2.7 操作ceph-cluster S3对象接口 实现: 上传 下载 查询 删除 顺便使用Docker装个owncloud 实现UI管理
python version: python2.7 需要安装得轮子: botofilechunkio command: yum install python-pip&& pip ...
- Unity 游戏框架:UI 管理神器 UI Kit
UI Kit 快速入门 首先我们来进行 UI Kit 的快速入门 制作一个界面的,步骤如下: 准备 生成代码 逻辑编写 运行 1. 准备 先创建一个场景 TestUIHomePanel. 删除 Hie ...
随机推荐
- Spark的dataframe转rdd通用工具类
需求解决问题 当每次读取hive表或者其他数据源,获取数据,相对其进行rdd操作,遇到任何类都需要df.rdd(row>row.getstring(0))去获取,就很麻烦,所以可以实现个通用的转 ...
- 在Termux(非root的安卓Linux模拟器)中安装和使用ftp服务器(pure-ftpd)(原创)[简单极致]
Termux是单用户的linux模拟器,所以应用的专属文件夹下面.usr/etc/下面没有passwd和group文件对多用户组配置,只在.usr/bin/下面有passwd二进制应用可以改变当前用户 ...
- Lesson_strange_words4
mount on 安装 arc 弧 actuator 马达,致动器:调节器 roughly 大致,大约 radially 径向,放射状 stepper 步进机 motor 电机,发动机 sequent ...
- 【SpringBoot1.x】SpringBoot1.x 入门
SpringBoot1.x 入门 文章源码 简介 传统的 JavaEE 开发,十分笨重且配置繁琐,开发效率很低,而且有很复杂的部署流程,对于第三方技术的集成也很困难. Sring 全家桶时代则解决了上 ...
- Linux性能相关命令
Linux性能相关命令 目录 Linux性能相关命令 1. 查看硬盘相关信息 2. 查看CPU相关信息 3. 查看内存相关信息 4. 查看进程运行的信息 1. 查看硬盘相关信息 cat /proc/s ...
- zabbix自定义监控nginx
nginx配置ngx_status 1.编译安装时带上--with-http_stub_status_module参数 2.vi nginx.conf location ~* ^/ngx_status ...
- 【Linux】nohup和&的区别
同样都是后台执行进程,但是nohup和&有什么区别呢? & 是指后台运行: nohup 的功能和& 之间的功能并不相同. 其中,nohup 可以使得命令永远运行下去和用户终端没 ...
- undefined和null区别
undefined类型只有一个值就是undefined,没有必要显式地声明一个变量为undefined. null类型其实就是一个对象的空指针,所以用typeof null 才会显示为object. ...
- 在HTML中改变input标签中的内容
在HTML中改变input标签的内容 1.使用js自带的方法: document.getElementById('roadName').value='武汉路';//通过标签选择器来选择标签,然后设置值 ...
- pandas高级操作
pandas高级操作 import numpy as np import pandas as pd from pandas import DataFrame,Series 替换操作 替换操作可以同步作 ...