Unity UI框架
一、简介
最近在各大网站看了一下 Unity3d 的 UI 框架,各种 UI 框架已经有很多的版本了,各有千秋,有的功能虽然写的完善,但用起来太复杂,有的框架功能不完善,搞个课程就上架了,还有什么 MVC 框架,绕来绕去的看的头都大了,这些根本不想用。
于是我自己就写了一个 UI 框架,只有两个脚本,不用向 UI 预制体上挂载脚本,所有的组件访问都可以通过 UIManager 来控制,常用的几个方法:显示界面,关闭界面,查找子物体,就这么多。
二、UI 框架
下面的两个脚本是 UI 框架的核心部分,具体的用法在下面的章节有介绍
UIBase
using UnityEngine;public class UIBase
{#region 字段/// <summary>/// Prefabs路径/// </summary>public string PrefabsPath { get; set; }/// <summary>/// UI面板的名字/// </summary>public string UIName { get; set; }/// <summary>/// 当前UI所在的场景名/// </summary>public string SceneName { get; set; }/// <summary>/// Type 的全名/// </summary>public string FullName { get; set; }/// <summary>/// 当前UI的游戏物体/// </summary>public GameObject UIGameObject { get; set; }#endregion/// <summary>/// 面板实例化时执行一次/// </summary>public virtual void Start() { }/// <summary>/// 每帧执行/// </summary>public virtual void Update() { }/// <summary>/// 当前UI面板销毁之前执行一次/// </summary>public virtual void Destroy() { }/// <summary>/// 根据名称查找一个子对象/// </summary>/// <param name="name"></param>/// <returns></returns>public GameObject GetObject(string name){Transform[] trans = UIGameObject.GetComponentsInChildren<Transform>();foreach (var item in trans){if (item.name == name)return item.gameObject;}Debug.LogError(string.Format("找不到名为 {0} 的子对象", name));return null;}/// <summary>/// 根据名称获取一个子对象的组件/// </summary>/// <typeparam name="T"></typeparam>/// <param name="name"></param>/// <returns></returns>public T GetOrAddCommonent<T>(string name) where T : Component{GameObject child = GetObject(name);if (child){if (child.GetComponent<T>() == null)child.AddComponent<T>();return child.GetComponent<T>();}return null;}protected UIBase() { }
}
UIManager
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;public class UIManager : MonoBehaviour
{public static UIManager Instance;//存储场景中的UI信息private Dictionary<string, UIBase> UIDic = new Dictionary<string, UIBase>();//当前场景的 Canvas 游戏物体private Transform CanvasTransform = null;//当前字典中UI的个数public int UICount{get { return UIDic.Count; }}private void Awake(){Instance = this;}private void Start(){}private void Update(){if (UIDic.Count > 0){foreach (var key in UIDic.Keys){if (UIDic[key] != null)UIDic[key].Update();}}}/// <summary>/// 显示面板/// </summary>/// <typeparam name="T"></typeparam>/// <returns></returns>public UIBase ShowUI<T>() where T : UIBase{Type t = typeof(T);string fullName = t.FullName;if (UIDic.ContainsKey(fullName)){Debug.Log("当前面板已经显示了,名字:" + fullName);return UIDic[fullName];}GameObject canvasObj = GameObject.Find("Canvas");if (canvasObj == null){Debug.LogError("场景中没有Canvas组件,无法显示UI物体");return null;}CanvasTransform = canvasObj.transform;UIBase uiBase = Activator.CreateInstance(t) as UIBase;if (string.IsNullOrEmpty(uiBase.PrefabsPath)){Debug.LogError("Prefabs 路径不能为空");return null;}GameObject prefabs = Resources.Load<GameObject>(uiBase.PrefabsPath);GameObject uiGameOjbect = GameObject.Instantiate(prefabs, CanvasTransform);uiGameOjbect.name = uiBase.PrefabsPath.Substring(uiBase.PrefabsPath.LastIndexOf('/') + 1);uiBase.UIName = uiGameOjbect.name;uiBase.SceneName = SceneManager.GetActiveScene().name;uiBase.UIGameObject = uiGameOjbect;uiBase.FullName = fullName;uiBase.Start();UIDic.Add(fullName, uiBase);return uiBase;}/// <summary>/// 移除面板/// </summary>/// <typeparam name="T"></typeparam>public void RemoveUI<T>(){Type t = typeof(T);string fullName = t.FullName;if (UIDic.ContainsKey(fullName)){UIBase uIBase = UIDic[fullName];uIBase.Destroy();GameObject.Destroy(uIBase.UIGameObject);UIDic.Remove(fullName);return;}Debug.Log(string.Format("当前的UI物体未实例化,名字:{0}", fullName));}/// <summary>/// 清除所有的UI物体/// </summary>public void ClearAllPanel(){foreach (var key in UIDic.Keys){UIBase uIBase = UIDic[key];if (uIBase != null){uIBase.Destroy();GameObject.Destroy(uIBase.UIGameObject);}}UIDic.Clear();}/// <summary>/// 找到指定的UI面板/// </summary>/// <typeparam name="T"></typeparam>/// <param name="name"></param>/// <returns></returns>public GameObject GetGameObject<T>(string name){Type t = typeof(T);string fullName = t.FullName;UIBase uIBase = null;if (!UIDic.TryGetValue(fullName, out uIBase)){Debug.Log("没有找到对应的UI面板,名字:" + fullName);return null;}return uIBase.GetObject(name);}private UIManager() { }
}
三、UI 框架的用法
我用了两个 场景来测试框架,start 和 main 场景
start 场景如下:
在 Start 场景 GameRoot 上挂上 StartSceneRoot 脚本

这个脚本就调用框架在场景中显示一个UI
using UnityEngine;public class StartSceneRoot : MonoBehaviour { void Start () {UIManager.Instance.ShowUI<Panel_MainUI>();}
}
在 GameManager 游戏物体上有两个脚本,这个是要跟随着场景一起跳转的。

UIManager 的代码在上一节,下面是 GameManager 代码
using UnityEngine;
using UnityEngine.SceneManagement;public class GameManager : MonoBehaviour {public static GameManager Instance;private static bool origional = true;private void Awake(){if (origional){Instance = this as GameManager;origional = false;DontDestroyOnLoad(this.gameObject);}else{Destroy(this.gameObject);}}void Start () {SceneManager.sceneUnloaded += SceneManager_sceneUnloaded;SceneManager.sceneLoaded += SceneManager_sceneLoaded;}private void SceneManager_sceneLoaded(Scene arg0, LoadSceneMode arg1){//Debug.Log("场景加载了,场景名:" + arg0.name);}private void SceneManager_sceneUnloaded(Scene arg0){//Debug.Log("场景卸载了,场景名:" + arg0.name);//注意:切换场景要清除掉 UIManager 中保存的 UI 数据UIManager.Instance.ClearAllPanel();}public void LoadScene(string sceneName){if (SceneManager.GetActiveScene().name != sceneName){SceneManager.LoadScene(sceneName);}}private GameManager() { }
}
在 start 场景中挂载到游戏物体上的脚本就这三个:StartSceneRoot,GameManager,UIManager
下面就准备要显示的 UI 了,在这里,我做了三个 UI 界面,并做成预制体

界面 Panel_MainUI

界面 Panel_Setting

界面 Panel_Affiche

这三个预制体对应的也是三个脚本,但不用挂在游戏物体上,作用是UI的逻辑部分。
Panel_MainUI
using UnityEngine;
using UnityEngine.UI;public class Panel_MainUI : UIBase
{public Panel_MainUI(){PrefabsPath = "Prefabs/UI/Panel_MainUI";}public override void Start(){GetOrAddCommonent<Button>("Button_Setting").onClick.AddListener(() =>{//显示设置面板UIManager.Instance.ShowUI<Panel_Setting>();});GetOrAddCommonent<Button>("Button_Task").onClick.AddListener(() =>{//清除所有的面板//UIManager.Instance.ClearAllPanel(); //跳转到 main 场景GameManager.Instance.LoadScene("main");});GetOrAddCommonent<Button>("Button_Equipage").onClick.AddListener(() =>{Debug.Log("UIManager 中 UI 面板的个数:" + UIManager.Instance.UICount);});}public override void Update(){//Debug.Log("我是 Panel_MainUI Update 方法");}public override void Destroy(){//Debug.Log("我是 Panel_MainUI Destroy 方法");}
}
Panel_Setting
using UnityEngine;
using UnityEngine.UI;public class Panel_Setting : UIBase
{public Panel_Setting(){PrefabsPath = "Prefabs/UI/Panel_Setting";}public override void Start(){//Debug.Log("我是 Panel_Setting Start 方法");GetOrAddCommonent<Button>("Button_Close").onClick.AddListener(() =>{//移除自己UIManager.Instance.RemoveUI<Panel_Setting>();});GetOrAddCommonent<Button>("Button_Test").onClick.AddListener(() =>{//访问其他的面板的游戏物体 GameObject obj = UIManager.Instance.GetGameObject<Panel_MainUI>("Button_Map");if (obj != null)obj.transform.Find("Text").GetComponent<Text>().text = "Map";});GetOrAddCommonent<Button>("Button_Test1").onClick.AddListener(() =>{Debug.Log("UIManager 中 UI 面板的个数:" + UIManager.Instance.UICount);});}public override void Update(){//Debug.Log("我是 Panel_Setting Update 方法");}public override void Destroy(){//Debug.Log("我是 Panel_Setting Destroy 方法");}
}
Panel_Affiche
using UnityEngine;
using UnityEngine.UI;public class Panel_Affiche : UIBase
{public Panel_Affiche(){PrefabsPath = "Prefabs/UI/Panel_Affiche";}public override void Start(){GetOrAddCommonent<Button>("Button_Close").onClick.AddListener(() =>{GameManager.Instance.LoadScene("start");});}public override void Update(){//Debug.Log("我是 Panel_Affiche Update 方法");}public override void Destroy(){//Debug.Log("我是 Panel_Affiche Destroy 方法");}
}
main 场景只挂了一个脚本


MainSceneRoot 脚本同样也是只是用来显示UI用的
using UnityEngine;public class MainSceneRoot : MonoBehaviour {void Start () {UIManager.Instance.ShowUI<Panel_Affiche>();}
}
运行后就能看到,显示了 Panel_MainUI 的UI界面,点击设置,就会打开设置的界面,点击任务按钮,就会跳转到 main 场景(这里只是测试)
跳转到 main 场景后,点击关闭按钮,又会返回到 start 场景。
演示就这些了,写这个框架我也就用了几个小时而已,当然还有待继续完善和改进的,另外,我用的 Unity版本是 Unity 5.6.7f1 ,C# 的一些高级语法用不了,不然可以写的更优雅一些。
源码:点击跳转
结束
如果这个帖子对你有所帮助,欢迎 关注 + 点赞 + 留言
end
相关文章:
Unity UI框架
一、简介 最近在各大网站看了一下 Unity3d 的 UI 框架,各种 UI 框架已经有很多的版本了,各有千秋,有的功能虽然写的完善,但用起来太复杂,有的框架功能不完善,搞个课程就上架了,还有什么 MVC 框…...
vue2提取vue-router的title单独存放,使用i18n实现
成品效果 首先引入i18n(vue-i18n官网文档) 依赖包 npm install vue-i18n8然后单独在src目录下新建一个文件夹lang,存放相对应的变量名称,我这里只做显示中文所以其他引入我都注释了,具体目录如下: src\lang/zh.js部分代码 export…...
【Linux操作系统】【综合实验三 用户帐号、文件系统与系统安全管理】
文章目录一、实验目的二、实验要求三、实验内容四、实验报告要求一、实验目的 要求掌握Linux系统用户的创建、删除与管理操作;熟悉Linux文件系统的管理模式,学会创建用户文件系统并装载和卸载文件系统;掌握超级用户的管理方式与权限…...
sqlite3数据库-sqlite语句1(五)
DML(Data Manipulation Language,数据操作语言) SELECT:查询表中的数据;SELECT语句中使用WHERE子句SELECT <列名>,... FROM <表名> WHERE <条件表达式>;SELECT id,name,purchase_price FROM Product; /*使用逗号分隔查询多列,顺序同子句顺序*/ SELECT * FROM…...
【图像分类】卷积神经网络之LeNet5网络模型实现MNIST手写数字识别
写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 在上一篇博文中我们对LeNet5网络模型的结构进行了剖析,本篇博文,我们将使用PyTorch搭建LeNet5实现MNIST手写数字…...
前端开发环境搭建
文章目录Node.js是什么安装查看版本入门示例NPM使用 npm 命令安装模块常见命令使用淘宝 NPM 镜像TypeScript安装入门示例从github拉取构建项目如何从零创建一个TypeScript项目规划目录结构新建项目Web App运行服务添加依赖打包使用browserify打包使用webpack打包推荐流程目录配…...
学习Flask之四、网页表单
第二章介绍的request对象,使用了客户端请求的所有信息。特别地,request.form提供了对POST请求提交的表单数据的访问。尽管Flask请求对象的支持足于处理网页单,但是还有很多作务很繁锁和重复。两个很好的例子是产生HTML表单代码和验证表单数据…...
CenterMask paper笔记
CenterMask是一个anchor free的实例分割模型, 来自paper: CenterMask: Real-Time Anchor-Free Instance Segmentation 提起anchor free, 会想到FCOS模型,是用来目标检测的, 那么这里就用到了FCOS, 不过换了backbone, 在FCOS检测出目标框后&…...
06- OpenCV查找图像轮廓 (OpenCV基础) (机器视觉)
知识重点 灰度图转换: gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)二值化: 返回两个东西,一个阈值, 一个是二值化的图: thresh, binary cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)查找轮廓: 返回两个结果,分别是轮廓和层级: c…...
OpenGL学习日记之模型绘制
自己编译运行过程中遇到的一些问题 下载Assimp已编译的lib(因为我们公司的电脑有很多权限和限制,也不能自己安装一些没有报备的软件,所以愁方便我就没有用cMake自己编译了)找到一位免费分享的博主的。 https://blog.csdn.net/lady_killer9/article/deta…...
Springboot接口多个DTO入参的Postman上传方式
在Java中使用Spring Boot框架时,可以同时使用多个DTO作为方法参数。 TO(Data Transfer Object)是一个常见的设计模式,用于封装数据传输对象。它通常用于将数据从一个层传递到另一个层,例如将数据从服务层传递到控制器…...
软考各科目考核内容详细介绍,看这里
新手在准备报考软考时,都会遇到这样的一个问题——科目这么多,我适合考什么?要想知道自己适合报什么科目,就需要了解每个科目是什么,考什么等一系列的问题。 接下来,就为大家介绍一下软考的各个科目&#…...
连续时间信号与离散时间信号
前言 《信号与系统》是一门很难的课,也是许多学校考研要考的专业课,由于每周只有两节课,所以每次上完都要及时的去复习,这里参考的教材是奥本海姆著作,刘海棠译,北京:电子工业出版社࿰…...
TPM密钥管理、使用
前面讲过证书相关内容,除了在软件方面有所应用外,在硬件方面也有很多应用。本次讲一下TPM相关的内容。 一、TPM介绍 1.1背景 TCG基于硬件安全的架构是为应对1990s后期日益增多的复杂恶意软件攻击应用而生的。当时以及现在,抵御PC客户端网络…...
return和finally执行顺序、运行时异常与一般异常异同、error和exception区别、Java异常处理机制原理与应用
文章目录1.try {}里有一个return语句,那么紧跟在这个try后的finally{}里的code会不会被执行,什么时候被执行,在return前还是后?2.运行时异常与一般异常有何异同?3.java 程序中的错误有三种类型分别是什么4.error和exception有什么…...
我为什么放弃WinUI3
基于WinUI3开发HiNote已经有一个多月的时间了,算是做出来一个简单能用的C端软件。 基于个人的经历,说说其中的开发体验。 UI设计语言 无论是否抄袭苹果,WinUI3给人的感觉都是眼前一亮的。简洁美观,现代化,毛玻璃的美…...
2023年全国最新安全员精选真题及答案2
百分百题库提供安全员考试试题、建筑安全员考试预测题、建筑安全员ABC考试真题、安全员证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。 21.(单选题)静作用压路机在施工过程,要求实际含水量…...
计算机408考研先导课---C语言难点
以下为小编在重温C语言时,容易犯错的一些点,希望列出来对大家有一定帮助! 一、整型变量数的范围 类型说明符长度(字节)数的范围int4/2(有些为4字节,有些为2字节)-32768~32767short2…...
K8S 部署 Redis-Cluster 集群
本文使用 bitnami 镜像部署 redis-cluster 官方文档:https://github.com/bitnami/charts/tree/main/bitnami/redis-cluster 添加 bitnami 仓库 helm repo add bitnami https://charts.bitnami.com/bitnami自定义 values.yaml storageClass:集群的存储…...
[oeasy]python0089_大型机的衰落_Dec小型机崛起_PDP_VAX网络
编码进化 回忆上次内容 上次 回顾了 计算机存储单位的演变 最小的读写单位 是 bit 8-bit 固定下来 成为了字节(Byte) 位数容量8-bit1Byte1024Byte1 KB1024 KB1 MB1024 MB1 GB1024 GB1 TB 存储字符时 第1位 是 标志位后7位 是 ascii具体的值 可以用 1Byte 存储 计算机之间 …...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
