当前位置: 首页 > news >正文

unity中UI管理器的详解及其优化

目录

概述

对比下有无ui管理器的效果对比

UI管理器实现的基本思路

基类实现

面板基类的实现

一定要实现的变量

还有就是每个面板都要实现的函数

最重要的一个函数

UI管理器的实现

完整代码

对于UI管理器的优化

优化代码思路如下:


概述

这套UI管理器是一个基于Unity引擎的轻量级面板管理系统,核心功能包括UI面板的动态加载、生命周期管理、淡入淡出动画以及自动资源回收。整体设计采用单例模式和泛型方法。

基本作用就是面板基类里面有show与hide方法。通过ui管理器来对ui面板统一的管理,最后面会给完整代码

对比下有无ui管理器的效果对比

// ❌ 无管理器 GameObject prefab = Resources.Load<GameObject>("UI/LoginPanel"); GameObject obj = Instantiate(prefab); obj.transform.SetParent(GameObject.Find("Canvas").transform, false);

每次对ui面板的创建都要这3行的代码(可能还有些,但是这3行是必要的)。

// ✅ 有管理器 UImanager.Instance.hideme<LoginPanel>();

对于无管理器来说,代码重复性高,对于find方法是非常消耗性能的(下面会提到解决方法)

UI管理器实现的基本思路

调用链如下,ui管理器来创建面板,面板是继承基类的可以调用基类的显示与隐藏函数

基类实现

面板基类的实现

基类是所有面板的父类 及继承概念。

所以对于基类的实现要思考每个面板都又什么一样的必须实现的方法以及变量

一定要实现的变量

这下面三个参数都是实现淡入淡出效果的。一个是组件,一个是控制淡入淡出速度,还有标志

不详细展开,这里只是为了探讨实现ui管理类的思路与方法

private CanvasGroup canvasGroup; [SerializeField] private float alphaSpeed = 5; public bool isshow;

CanvasGroup 自动管理:每个面板实例化出来后都加载这个组件控制淡入淡出

protected void Awake() { canvasGroup =this.GetComponent<CanvasGroup>(); if(canvasGroup==null) canvasGroup = this.gameObject.AddComponent<CanvasGroup>(); }

还有就是每个面板都要实现的函数

那就是面板显示与隐藏函数

public virtual void showme() { canvasGroup.alpha = 0; isshow=true; } private UnityAction hidecallback = null; public virtual void hideme(UnityAction callback) { canvasGroup.alpha = 1; isshow=false; hidecallback = callback; //这里的委托函数很关键,写了委托函数才可以先在updtae里面淡出的效果结束以后在调用callback函数。这里回调函数一般是销毁obj,如果不写回调 //就不会等待淡出而是俩边异步一起执行了 }

这里可能就有人要问,你 canvasGroup.alpha 还有hideme函数的委托函数做参数是干嘛的
我这里只实现简单的淡入淡出效果(不会立马关闭UI面板)具体实现不细写,这里函数你也可以实现你的自己的效果

下面也是为了实现淡入淡出实现的。如果你只是写个简单的ui管理器仅仅就是创建以及显示,没必要写

void Update() { if (isshow&&canvasGroup.alpha!=1) { canvasGroup.alpha += alphaSpeed * Time.deltaTime; if(canvasGroup.alpha>1) canvasGroup.alpha = 1; } else if(!isshow && canvasGroup.alpha!=0) { canvasGroup.alpha-= alphaSpeed * Time.deltaTime; if (canvasGroup.alpha <= 0) { canvasGroup.alpha = 0; if (hidecallback != null) { hidecallback.Invoke(); } } } }

最重要的一个函数

抽象的初始化函数,并且要在生命周期start中调用

因为每个面板都要初始化,但是实现不一样,比如每个面板都有按钮,每次初始化的时候都要对按钮注册监听事件。每个面板的按钮都不一样。这就是多态

所以要写成抽象函数init(),start成写虚函数是为了方便拓展好好解耦

public abstract void Init(); protected virtual void Start() { Init(); }

UI管理器的实现

首先是单例模式(不了解可以自己去了解下单例模式)

private static UImanager instance = new UImanager(); public static UImanager Instance =>instance;

不用ui管理器的时候要经常调用find去找canvas。下面用ui管理器解决方法

第一次加载管理器的时候,在构造函数里面实现画布的实例化,以及记录画布,这样就不需要find了

private Transform canvastran; private UImanager() { GameObject cnavas = GameObject.Instantiate(Resources.Load<GameObject>("UI/Canvas")); canvastran = cnavas.transform; GameObject.DontDestroyOnLoad(cnavas); }

下面就是ui管理器的show函数,基本实例是实例化面板,调用面板的父类函数

这里的函数一定是要泛型。根据面板类名来实例化面板。所以我们要做到面板名与脚本名一致

因为我们是根据传入的面板类型,去找面板预制体位置的

然后就是字典类的作用:防止重复实例化UI,不小心连续调用俩次,会实例化出俩个ui界面出来。

private Dictionary<string, Basepanel> panelDic = new Dictionary<string, Basepanel>(); public T Showpanel<T>() where T : Basepanel { string panelname = typeof(T).Name; if (panelDic.ContainsKey(panelname)) { return panelDic[panelname] as T; } else { GameObject obj = GameObject.Instantiate(Resources.Load<GameObject>("UI/"+panelname)); obj.transform.SetParent(canvastran,false);//挂载到canvas下面 T panel= obj.GetComponent<T>(); panelDic.Add(panelname, panel); panel.showme(); return panel; } }

下面就是隐藏面板的实现,隐藏后把面板从字典移除

//隐藏面板 public void hideme<T>( ) where T : Basepanel { string panelname = typeof(T).Name; if (panelDic.ContainsKey(panelname)) { //回调很关键,会使这边等待那边基类淡出实现完成后再销毁obj panelDic[panelname].hideme(() => { GameObject.Destroy(panelDic[panelname].gameObject); panelDic.Remove(panelname); }); } }

完整代码

using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; public abstract class Basepanel : MonoBehaviour { private CanvasGroup canvasGroup; [SerializeField] private float alphaSpeed = 5; public bool isshow; // Start is called before the first frame update public abstract void Init(); protected void Awake() { canvasGroup =this.GetComponent<CanvasGroup>(); if(canvasGroup==null) canvasGroup = this.gameObject.AddComponent<CanvasGroup>(); } protected virtual void Start() { Init(); } public virtual void showme() { canvasGroup.alpha = 0; isshow=true; } private UnityAction hidecallback = null; public virtual void hideme(UnityAction callback) { canvasGroup.alpha = 1; isshow=false; hidecallback = callback; //这里的委托函数很关键,写了委托函数才可以先在updtae里面淡出的效果结束以后在调用callback函数。这里回调函数一般是销毁obj,如果不写回调 //就不会等待淡出而是俩边异步一起执行了 } // Update is called once per frame protected virtual void Update() { if (isshow&&canvasGroup.alpha!=1) { canvasGroup.alpha += alphaSpeed * Time.deltaTime; if(canvasGroup.alpha>1) canvasGroup.alpha = 1; } else if(!isshow && canvasGroup.alpha!=0) { canvasGroup.alpha-= alphaSpeed * Time.deltaTime; if (canvasGroup.alpha <= 0) { canvasGroup.alpha = 0; if (hidecallback != null) { hidecallback.Invoke(); } } } } }
using System.Collections; using System.Collections.Generic; using Unity.VisualScripting.FullSerializer; using UnityEngine; public class UImanager { private static UImanager instance = new UImanager(); public static UImanager Instance =>instance; private Dictionary<string, Basepanel> panelDic = new Dictionary<string, Basepanel>(); private Transform canvastran; //showpanel private UImanager() { GameObject cnavas = GameObject.Instantiate(Resources.Load<GameObject>("UI/Canvas")); canvastran = cnavas.transform; GameObject.DontDestroyOnLoad(cnavas); } public T Showpanel<T>() where T : Basepanel { string panelname = typeof(T).Name; if (panelDic.ContainsKey(panelname)) { return panelDic[panelname] as T; } else { GameObject obj = GameObject.Instantiate(Resources.Load<GameObject>("UI/"+panelname)); obj.transform.SetParent(canvastran,false); T panel= obj.GetComponent<T>(); panelDic.Add(panelname, panel); panel.showme(); return panel; } } //隐藏面板 public void hideme<T>( ) where T : Basepanel { string panelname = typeof(T).Name; if (panelDic.ContainsKey(panelname)) { //回调很关键,会使这边等待那边基类淡出实现完成后再销毁obj panelDic[panelname].hideme(() => { GameObject.Destroy(panelDic[panelname].gameObject); panelDic.Remove(panelname); }); } } //得到面板 public T Getpanel<T>( ) where T : Basepanel { string panelname = typeof(T).Name; if(panelDic.ContainsKey(panelname)) return panelDic[panelname] as T ; return null; } }

对于UI管理器的优化

我们目前的ui管理器在面板隐藏后会销毁面板,然后从字典中移除。

但是对于频繁调出的ui(比如王者荣耀里面的商城界面,玩家要频繁的调出商城界面买装备)

我们可以不销毁ui而是控制面板的失活激活

优化代码思路如下:

创建一个新函数,只控制面板的失活激活,不移除字典。在show函数中,如果发现字典中已经存在了面板,直接激活

public void close<T>() where T : Basepanel { string panelname = typeof(T).Name; if (panelDic.ContainsKey(panelname)) { panelDic[panelname].gameObject.SetActive(false); } }

在show函数里面也要加一行代码

public T Showpanel<T>() where T : Basepanel { string panelname = typeof(T).Name; if (panelDic.ContainsKey(panelname)) { panelDic[panelname].gameObject.SetActive(true);//优化1代码 return panelDic[panelname] as T; } else { GameObject obj = GameObject.Instantiate(Resources.Load<GameObject>("UI/"+panelname)); obj.transform.SetParent(canvastran,false); T panel= obj.GetComponent<T>(); panelDic.Add(panelname, panel); panel.showme(); return panel; } }

还有就是每个函数里都要用到

string panelname = typeof(T).Name;

把这个成员变量变成全局变量

5.2月更新内容

---------------------------------------------------------------------------------------------------------------------------------

上面的优化还有点遐思

对于只控制面板的失活激活,而不销毁的面板的思路是对的。但是有个更快的方法。

我是用canvas group的阿尔法来控制面板的淡入淡出。那我可以直接可以使阿尔法=0来达到上述效果

方法对比

控制失活激活来使面板消失会触发整个的生命周期enable,没有控制阿尔法快。

优化代码部分

控制阿尔法后,要改动一部分内容。

在basepanel中

这里隐藏面板后要做的事情有:

1.取消交互不然会导致隐藏后可以交互

2.还有关闭射线阻挡,不然可能会导致后面的面板无法交互

3.设置enable=false。这样就不会导致面板隐藏后有update的空转

对比setactve

enable只会禁用当前脚本

setactive会把面板所有的组件与子物体失活,例如按钮等

下面是ai给的对比

性能量化示例(假设一个中等复杂度的 UI 面板,包含 20 个子物体)

操作this.enabled = falseSetActive(false)
隐藏耗时≈ 0.2 μs(仅脚本禁用)≈ 50~150 μs(递归禁用所有组件)
再次显示耗时≈ 0.2 μs(仅脚本启用)≈ 200~500 μs(递归激活 + 布局重建)
隐藏后每帧空转开销(假设面板不可见但未销毁)子物体若有 Update 脚本,仍会执行(但通常没有)0(所有脚本停止)
public virtual void showme() { this.enabled = true; // ✅ 新增:恢复 Update canvasGroup.alpha = 0; canvasGroup.interactable = true; // ✅ 新增:恢复交互 canvasGroup.blocksRaycasts = true; // ✅ 新增:恢复射线阻挡 isshow = true; }
public virtual void hideme(UnityAction callback) { canvasGroup.interactable = false; // ✅ 新增:关闭交互 canvasGroup.blocksRaycasts = false; // ✅ 新增:穿透射线 isshow = false; hideCallback = callback; // 不要设置 this.enabled = false,留给 Update 完成淡出后做 }
protected virtual void Update() { if (isshow && canvasGroup.alpha != 1f) { canvasGroup.alpha += alphaSpeed * Time.deltaTime; if (canvasGroup.alpha >= 1f) canvasGroup.alpha = 1f; } else if (!isshow && canvasGroup.alpha != 0f) { canvasGroup.alpha -= alphaSpeed * Time.deltaTime; if (canvasGroup.alpha <= 0f) { canvasGroup.alpha = 0f; this.enabled = false; // ✅ 新增:淡出完成,停止 Update 空转 if (hideCallback != null) { hideCallback.Invoke(); hideCallback = null; } } } }

切记这个close函数是频繁调用UI的情况才使用(王者荣耀游戏内商店界面)。这样能做到对面板打开和关闭的性能。

最后总结一下。对频繁调用UI,用阿尔法来控制隐藏,这个优化还是有必要的,但是对于担心隐藏后面板update的空转而调用this.enable.实在没有必要,因为对于隐藏后的面板的转,其实显示后阿尔法=1的时候也有空转。但是代价太小了它比一次Debug.Log轻几万倍(AI说的)。基本没有影响。除非你有好几个面板在激活状态。或者你有代码洁癖,根本就忍受不了这个无意义的调用。

http://www.jsqmd.com/news/747232/

相关文章:

  • JDK17+Project Leyden落地边缘场景:为什么92%的Java边缘项目仍用冗余JRE?揭秘3类典型资源浪费陷阱
  • 为 OpenClaw 配置 Taotoken 端点以接入统一大模型服务
  • 【AHC】HttpAsyncClient 与 async-http-client(AHC):谁是 Java 异步 HTTP 客户端的未来?
  • 为什么92%的Java低代码项目在v3.0版本崩溃?:揭秘元数据模型耦合、动态类加载泄漏与热更新失效根因
  • 外部 RFC 到 ABAP Platform 的 SNC 配置全景图,参数、认证链路与排障重点
  • OpenRocket:免费开源火箭设计与飞行仿真软件完整指南
  • 当不可能成为可能:我将 Mac OS X 移植到了 Nintendo Wii
  • 从PyTorch模型到TensorRT推理:在Windows上完整走通你的第一个加速Demo
  • 鸿蒙PC和App:都在走向 System
  • 深入浅出:图解TMS320F28377D ePWM八大子模块工作原理与配置逻辑
  • zynq7010和zynq7020的区别
  • 2026年三大AI模型深度横评:GPT-5Claude-4Gemini-2.5到底选谁
  • Hugging Face Transformers 加载模型时,那些容易被忽略但超有用的参数(cache_dir, proxies, revision 实战详解)
  • AMD锐龙处理器性能调优终极指南:如何使用SMU调试工具实现硬件级控制
  • FCN-32s/16s/8s效果差多少?用PASCAL VOC数据实测对比,聊聊语义分割的‘细节魔鬼’
  • 百度面试官:如何赋予 LLM 规划能力?
  • STM32 ADC控制器及其应用
  • 第一章-04-构造方法
  • 蚂蚁S9控制板简介(zynq-7010系列)
  • 【AI模型】高性能推理框架
  • IX6024 × DeepSeek V4@ACP#国产 24 通道 PCIe 交换芯片,中端推理与边缘集群的 IO 强芯
  • 终极RDPWrap指南:免费解锁Windows远程桌面多用户并发连接
  • 科研小白看过来:EndNote X9搭配Zotero/知网,打造你的个人文献管理流水线
  • 2026年ERP系统怎么选:6款主流产品功能与适用场景对比
  • 要实现一个工作流,选择 Agent Skills 还是 AI 表格?
  • 如何高效获取八大网盘直链:LinkSwift专业级下载助手实战指南
  • Switch大气层系统深度优化指南:从基础配置到专家级调校
  • 彻底解决Windows图形驱动兼容性问题:Mesa3D驱动安装与故障排除终极指南
  • 手把手教你解决iTextPDF的‘trailer not found’:从错误日志到PDF文件结构分析
  • 如何快速优化Windows 11:Win11Debloat终极指南