Unity UI 框架相关的一些思考

news2025/7/15 6:56:54

 开源地址: 

GitHub - NRatel/NRFramework.UI: 基于 Unity UGUI 的 UI 开发框架基于 Unity UGUI 的 UI 开发框架. Contribute to NRatel/NRFramework.UI development by creating an account on GitHub.https://github.com/NRatel/NRFramework.UI

简介:

Unity UI 框架_NRatel的博客-CSDN博客Unity UI 框架组件化、树状聚合设计提供面板创建/销毁/显隐藏接口、显示状态维护提供控件创建/动态逻辑绑定/销毁接口提供元素半自动收集、代码自动生成层级管理焦点管理内置自动添加背景,及背景点击响应逻辑内置自动播放打开/关闭动画、动画状态维护内置返回键回退逻辑其他(自定义组件、屏幕适配、多语言支持等)https://blog.csdn.net/NRatel/article/details/127902181

1、UI 类应该使用一个 Monobehaviour 子类进行逻辑控制,还是用纯 C# 类?

这两者的区别是:

前者创建:先创建 GameObjet 再给他挂一个控制它的脚本;
后者创建:先创建一个纯 C# 对象, 再在合适的时机创建 GameObject 交给它管理。

前者:更符合Unity原本的 “go-comp” 思路。
后者:⑴、从类图上看,更符合控制引用关系、更便于代码设计。但需要自己维护 引用、解除、销毁 go 的逻辑;⑵、组件收集与逻辑分开,可随意组合复用;⑶、逻辑脚本和预设资源绑在一起难以传统方式(Lua)热更。

2、为什么要组件式、树状聚合设计?

之前的文章里说过,游戏界面内容应该面向对象:每个界面作为一个游戏对象,界面中,较为独立的部分/重复出现的部分,可以单作为一个游戏对象,逐层嵌套,形成父子关系。 聚合/组合关系:1~1或1~N。好处是:

⑴、可以针对各层对象写逻辑,使逻辑各归其所,适合大规模建设。

⑵、对象可以复用。界面干净整洁、统一性好,不会出现有两处相同的东西,却长得不太一样

⑶、极大加快UI开发效率,设计界面、拼界面、写界面逻辑都是对 “对象化的零件” 进行拼凑组合。

比如:商店界面:商店界面 -> 货架 -> 货架的层 -> 货架层上的物体。
比如:编队界面:编队界面 -> 队伍槽位 -> 队伍中的英雄。
比如:英雄培养界面:英雄培养界面 -> 消耗材料槽(放材料图标和拥有/需求数量) -> 材料图标。

但是,这也一定程度上使系统制作和修改的便宜度增加。
同时,对策划(UE设计)和美术(UI效果图设计)的设计要求大幅增高。
带给程序的困惑,主要表现为:

⑴、设计中各处针对性强,复用度不高 ,难以复用。

⑵、设计复用需求不明确时,难以判断是否应按提出组件的方式来做。
(只出了UE,未出UI效果图就让程序先做功能,常常出现UE相似但最后UI不同的情况,导致返工)。

⑶、设计的需求调整可能不易,因为文件数变多了。
(只在本界面内重复出现的对象的逻辑类,可以定义在该界面的逻辑文件内,以减少文件数)

⑷、当有很多相似组件出现时,对组件的命名成了一个问题。

整体来说,利远大于弊。

3、支持度和易用性的思考

支持度和易用性,看似是冲突的,实则不冲突。

首先,需求不应该出现不能支持的情况,这就产生了很多参数和细碎接口。

不确定的事,应该让用户自己决定怎么去做,而不是YY式的限制用户只能以某种方式去做。但,如果一个大概率都要那样去做的事,每次都要重复去做,又未免过于麻烦。

以下几种手段可能解决这个问题:

⑴、将一些接口的参数改为默认形参 或 重载接口(UI框架中CreatePanel、CreateWidget等接口)

⑵、为一组细碎的接口提供默认的组合接口。

⑶、父类中提供接口的默认实现,但可在子类中重写(UI框架中播放打开/关闭Panel动画接口)。

4、操作/生命周期顺序的思考

⑴、父子类创建销毁的接口/生命周期顺序?

创建时先父后子;销毁时先子后父。

⑵、上下两层层界面的焦点焦点变化,获得焦点时,应该先触发谁的 OnFocus,丢失焦点时,应该先触发谁的 OnLostFocus?

出现时是先出现下层界面后出现上层界面;消失时是先消失上层界面后消失下层界面。

⑶、聚合/依赖关系,销毁的时候先解除引用还是先销毁自己?

创建:创建自身、建立引用;那么移除应该相反,即:解除引用、销毁自身。

⑷、对外提供接口的完成回调 和 对内完成事件的调用顺序?

对外提供接口的完成回调,应该晚于内部完成事件,即:先处理完内部,再处理外部。

5、复杂黑盒接口的异常处理思考

一个复杂的黑盒接口,传入非法参数时,让它报错还是返回 null?

尤其是封装成了 dll,别人不能查看源码时,无论报错还是光返回 null 都会让使用者感到疑惑。

仔细想想,异常,通常应该在外层调用处进行处理,那就应该把错误的情况列表并传出来。

可以为其定义一系列错误码,然后返回,若方法原来有返回值,改成 out 传出。如下:

static public class FindCompErrorCode
{
    //UIView中
    public const int OK = 0;
    public const int ERROR_CAST_TYPE = 1001;                //错误的组件转换类型
    public const int COMP_DEFINE_IS_NULL_OR_EMPTY = 1002;   //compDefine为null或""
    public const int NOT_EXIST_THIS_COMPONENT = 1003;       //View中不存在此组件定义
    public const int NOT_EXIST_ANY_CHILD_WIDGET = 1004;     //View中不存在任何子Widget(不存在此Widget)
    public const int WIDGETS_ID_IS_NULL_OR_EMPTY = 1005;    //widgetIds为null或""
    public const int NOT_EXIST_THIS_CHILD_WIDGET = 1006;    //View不存在此Widget

    //UIRoot
    public const int PANEL_ID_IS_NULL_OR_EMPTY = 1007;      //panelId为null或""
    public const int NOT_EXIST_THIS_PANEL = 1008;           //Root中不存在此Panel

    //UIManager中
    public const int NOT_EXIST_THIS_ROOT = 1009;            //UIManager中不存在此Root
    public const int VIEW_PATH_IS_NULL_OR_EMPTY = 1010;     //viewPath为null或""
    public const int VIEW_PATH_IS_TOO_SHORT = 1011;         //ViewPath应该至少包含一个rootId和一个panelId
}


public int FindComponentByPath<T>(string path, string compDefine, out T comp) where T : Component {}

6、界面显示及状态相关问题的思考

基本流程:
⑴、界面创建后,播放打开动画(若有)。
⑵、界面初始化时,注入或获取 Data 完成显示。
⑶、界面刷新时,注入或获取 Data 完成显示。
⑷、界面关闭时,播放关闭动画(若有)。
注意:
⑴、动画播放是异步的。动画一般都是创建时挂到预设上的,只操作预设初始节点,不依赖数据。
⑵、获取Data可能是异步的(现请求)。
⑶、某些组件的显示可能是异步的(如:为了优化脏标记异步更新)。
外部需求:
⑵、跳转连续打开多个界面时,不关心动画,但依赖数据(由数据决定是否可以依次打开,直至目标界面)。
⑶、功能解锁、红点、引导等上层系统需要能随时获取界面当前状态(如引导,要等界面完全准备好后才能执行)。
---------------------------------
其他问题:
⑴、异步请求数据,应放在Create前还是Init中?
     建议后者,后者可以利用自身界面阻挡操作。但注意,必须处理好“创建后~初始化完成前”的显示。
⑵、界面显示和动画状态如何维护? 
     ①、只维护自身状态,不考虑子Widget。
     ②、但在外部读取时可以考虑计入自身及所有子Widget的状态(结合实际需求)。
     ③、初始化/刷新方法 完全由用户自定义(可能不是最终想法),如果是同步的,默认标记为Idle;如果是异步的,可以应该在初始化/刷新开始时将显示状态改为Initing/Refreshing,并在完成时将显示状态标记为Idle。
     ④、在Panel创建时默认调起打开动画,播放完成时将动画状态改为Idle。
     ⑤、在Panel关闭时调起关闭动画,播放完成时将动画状态改为Closed。  
⑶、是否将界面状态暴露到 Inspector中,便于调试?
     不确定是否有必要,待定。

7、维护焦点变化的一些思考

⑴、维护焦点变化,起到什么作用? 

界面失去焦点时,可选择性地“挂起”(暂停内部耗时Update类操作),并在重新获得焦点时恢复,以此优化。另外,还可在获得焦点时做一些事件触发,比如拍脸弹窗等。

⑵、关闭界面时是否触发 OnFoucusChanged(false)?

否,焦点在打开/关闭界面之后统一计算的。
无法触发已关闭界面的 OnFoucusChanged(false)。
这意味着,OnFoucusChanged 是不完全对称的。
如果在其中做了一些创建操作(尽量不要这样做),可能需要在 OnClosing 中善后清理。

⑶、界面的打开/关闭动画对焦点变化有什么影响?

界面在上层创建时,获得焦点应该是敏感的,即:只要创建就可能立刻获得焦点,此时下层界面丢失焦点也是立刻的。

界面在上层销毁时,下层界面获得焦点应该是迟钝的,即:要等到上层界面完全销毁,下层界面才能够获得焦点。

8、维护通用背景的一些思考

⑴、通用背景是否是单例的?

是,一个够用。可以根据当前状态,移动背景到它应该去的界面。

⑵、通用背景是否总是出现在主要获得焦点的界面上?

不是,有些界面需要背景,但却不抢夺焦点(System类型)。

⑶、界面动画不应对预设根节点进行操作(缩放、旋转、位移)

因为背景是添加到预设根节点下的第一个物体,如果操作根节点,就会带着背景一起移动。

也是完全可以避免的:

①、PanelType 为 Underlay 的 界面,打开动画一般是 “子元素逐渐加入”(不会操作根节点)、“翻篇进入”(应该加入额外一个动画根节点)。

②、PanelType 为 Window 的 界面,打开动画一般是 “缩放、淡入淡出、飞入飞出等(应该加入额外一个动画根节点)”。

9、组件收集的一些实现问题解决和思考

⑴、每次支持新组件,都要改哪些地方?

①、支持原组件使用的图标, 
    修改 UIEditorUtility.GetIconByType 方法。
②、支持新组件脚本生成 生命周期、事件修改
    适当修改 UIView 或 为其增加 partial class,添加 “含事件组件” 的事件绑定、解除绑定和生命周期方法。
    适当修改 UIViewBehaviourEditor 的 canBindEventCompSet,增加 “含事件组件” 的组件名。
    适当修改 UIEditorUtility.kUITemporaryCode,增加事件生命周期方法。
③、支持组件推测。
    适当修改 SetAsUIOpElement。

⑵、TMP 的组件图标获取问题

①、TMP 是怎么做到脚本图标自定义的?

只要将图标资源按照其命名规则放在 Gizmos 目录中即可
可在 TMP 包的以下目录中找到:Packages/TextMeshPro/Edutor Resources/Gizmos/

②、那我要怎么才能加载?

访问包内资源的官方文档:https://docs.unity3d.com/cn/2020.3/Manual/upm-assets.html
(注意,路径中的包名不带版本号、空格、“-”都要保留)

(Texture2D)AssetDatabase.LoadAssetAtPath("Packages/com.unity.textmeshpro/Editor Resources/Gizmos/TMP - Input Field Icon.psd", typeof(Texture2D));

//实测,这样也可:
(Texture2D)EditorGUIUtility.Load("Packages/com.unity.textmeshpro/Editor Resources/Gizmos/TMP - Input Field Icon.psd")

⑵、在以下三种情况中,如何在 OnInspector 中准确获得当前关联预设的资源路径?

1点击预设时,2双击预设并选择预设时、3预设拖入Hierarchy时

解决:
双击预设并选择预设时、预设拖入Hierarchy时的 "Select" 是怎么做到的呢?
在Unity源码中全局搜索源码 "Select" ,找到 GameObjectInspector 了解具体情况。

PrefabAssetType singlePrefabType = PrefabUtility.GetPrefabAssetType(target);
PrefabInstanceStatus singleInstanceStatus = PrefabUtility.GetPrefabInstanceStatus(target);

在三种情境下测试(1点击预设时,2双击预设并选择预设时、3预设拖入Hierarchy时)
在其 OnInspectorGUI 中输出:
PrefabAssetType singlePrefabType = PrefabUtility.GetPrefabAssetType(target);
PrefabInstanceStatus singleInstanceStatus = PrefabUtility.GetPrefabInstanceStatus(target);
Debug.Log("singlePrefabType: " + singlePrefabType);
Debug.Log("singleInstanceStatus: " + singleInstanceStatus);
输出结果如下:
1、singlePrefabType: Regular;  singleInstanceStatus: NotAPrefab
2、singlePrefabType: NotAPrefab;  singleInstanceStatus: NotAPrefab
3、singlePrefabType: Regular;  singleInstanceStatus: Connected

发现一个方法,但是 internal 方法,不能用。。
GameObject prefabGo = PrefabUtility.GetOriginalSourceOrVariantRoot(targets[i]);   

但又发现一个调用了它的 public 方法:
GetPrefabAssetPathOfNearestInstanceRoot

在三种情境下测试(1点击预设时,2双击预设并选择预设时、3预设拖入Hierarchy时)
在其 OnInspectorGUI 中输出:
string prefabAssetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(target);            
Debug.Log("prefabAssetPath: " + prefabAssetPath);
1、3,可以获得预设路径,2不行(输出为"")

全局搜索 "Canvas (Environment)" 找线索,发现以下调用堆栈:

PrefabStageUtility.GetOrCreateCanvasGameObject,
PrefabStageUtility.HandleUIReparentingIfNeeded
PrefabStageUtility.HandleReparentingIfNeeded
PrefabStage.LoadStage
PrefabStage.OpenStage
StageNavigationManager.SwitchToStage
PrefabStageUtility.OpenPrefabMode
PrefabStageUtility.OpenPrefab

可知:
点击预设 Inspector 右上角的 "Open"、鼠标右键点击预设再点击"Open"、双击预设等,
都是调用的 PrefabStageUtility.OpenPrefab

而我要的答案就是:
获取当前预设操作的Stage:PrefabStageUtility.GetCurrentPrefabStage() 或 PrefabStageUtility.GetPrefabStage(GameObject gameObject)
然后取其预设资源路径:pfabStage.prefabAssetPath(已弃用)或 prefabStage.assetPath(改为使用它)

⑶、如何在生成脚本后默认定位脚本位置

EditorGUIUtility.PingObject(AssetDatabase.LoadAssetAtPath<TextAsset>(scriptAssetPath));

⑷、为 操作元素Add(Set)、Delete(Remove) 增加快捷键

大多方便用的快捷键,已经被占用了。。如下:

Ctrl+A:全选;Ctrl+D:Unity复制;Ctrl+S:保存;Ctrl+R:刷新工程/游戏对象
Alt+A:微信截屏;Ctrl+Alt+A:QQ截屏
------------------------
所以,暂时采用 Alt+S 和 Alt+R。(可改)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/36575.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EMR-StarRocks 与 Flink 在汇量实时写入场景的最佳实践

作者&#xff1a; 刘腾飞 汇量后端开发工程师 阿里云开源OLAP研发团队 EMR-StarRocks介绍 阿里云EMR在年初推出了StarRocks服务&#xff0c;StarRocks是新一代极速全场景MPP&#xff08;Massively Parallel Processing&#xff09;数据仓库&#xff0c;致力于构建极速和统一分…

帝国cms后台登录系统限制次数,60分钟过后重新登录解决办法

帝国cms后台登录系统一不小心登录频繁就提示: 系统限制的登录次数不得超过5次,请等60分钟过后,方可重新登录 主要原因就是频繁的输错用户名或者密码导致登录受限 解帝国cms后台登录系统限制次数方法一:等待60分钟,然后再尝试登录 解帝国cms后台登录系统限制次数方法二:修改…

Hive之DQL操作

Hive系列第六章 &#xff08;实际是第七篇&#xff0c;就不改目录序号了&#xff0c;大家知道就行&#xff0c;后续的篇章类推即可&#xff09; 第六章 DQL查询数据 DDL&#xff1a; Data Definition Language 数据定义语言 DML&#xff1a; Data Manipulation Language …

【科学文献计量】GC.networkCoInvestigator()和GC.networkCoInvestigator()中的参数解释

@TOC 1 数据 使用官网提供的基金数据导入到python环境中 2 GC.networkCoInvestigator()中的参数解释 GC.networkCoInvestigator()中的参数解释: targetTagsL: [list]数据类型。默认为None,可以指定为Grant中研究者的标签构成的列表,很多基金中作者没有已知的标签,需要自…

最新版本EasyRecovery15个人免费版电脑数据恢复工具

最新版本EasyRecovery15是一款是款恢复率高、速度快的数据恢复软件&#xff0c;Ontrack EasyRecovery (易恢复) 跨平台支持 Windows 以及 Mac 系统&#xff0c;能能够顺利找回因各种原因丢失的文件&#xff0c;比如文件误删除、误格式化、分区丢失等&#xff0c;且EasyRecovery…

一种获得离散型周期数据的变化周期的算法

400个数据像这样&#xff1a; 152 155 155 237 24 27 27 109 152 155 155 237 24 27 27 109 152 155 155 237 24 27 27 109 152 155 155 237 24 27 27 109 152 155 155 237 24 27 27 109 152 155 155 237 24 27 27 109 152 155 155 237 24 27 27 109 152 155 155 237 24 27 27 …

【Android 开发】 面试官刨根问底?教你如何避免翻车沟通表达能力

很久以前&#xff0c;凭借四大组件、Java基础等知识&#xff0c;便可开开心心的开发&#xff0c;轻松的上岗&#xff1b; 而随着Android的不断发展完善&#xff0c;各种组件库越来越成熟&#xff0c;学习资料越来越多&#xff0c;我们却慢慢的看不到了方向&#xff1b;信息爆炸…

Servlet(Cookie和Session)

目录 &#x1f432; 1. Cookie 的工作流程 &#x1f432; 2. Servlet中操作 Cookie 和 Session 的api &#x1f432; 3. 案例1: 模拟登录 &#x1f432; 4. 上传文件 &#x1f432; 5. 案例2: 上传文件 &#x1f432; 1. Cookie 的工作流程 Cookie 是浏览器在本地持久化保…

[附源码]SSM计算机毕业设计朋辈帮扶系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[毕业设计]opencv机器学习双目测距精度测量系统

前言 &#x1f4c5;大四是整个大学期间最忙碌的时光,一边要忙着备考或实习为毕业后面临的就业升学做准备,一边要为毕业设计耗费大量精力。近几年各个学校要求的毕设项目越来越难,有不少课题是研究生级别难度的,对本科同学来说是充满挑战。为帮助大家顺利通过和节省时间与精力投…

Flutter高仿微信-第43篇-群聊列表

Flutter高仿微信系列共59篇&#xff0c;从Flutter客户端、Kotlin客户端、Web服务器、数据库表结构、Xmpp即时通讯服务器、视频通话服务器、腾讯云服务器全面讲解。 详情请查看 效果图&#xff1a; 实现代码&#xff1a; /*** Author : wangning* Email : maoning20080809163.c…

泰克Tektronix 信号发生器AFG31022 ,2频道,25MHz

AFG31022 泰克函数发生器 AFG31022 是 Tektronix 的 25 MHz 函数发生器。 产品特征&#xff1a; 2个频道 25 MHz 正弦波 输出幅度范围&#xff1a;1 mVP-P 至 10 VP-P&#xff0c;50 Ω 负载 14 位垂直分辨率 内置波形包括正弦波、方波、斜波、脉冲、噪声和其他常用波形…

数据库设计规范

一、概述 系统数据库表的设计如果有问题&#xff0c;可能造成数据冗余、信息重复、存储空间浪费、数据插入更新异常等。设计一个好的数据表可尽量避免上述问题的发生&#xff0c;如何设计一个好的数据库是有一定的规范的。而这些设计表的基本原则规范称为范式 二、范式 1、范…

实战渗透--一次对后台登录系统的简单渗透测试

某网站后台登录界面 发现有验证码框 猜想会不会存在验证码绕过的漏洞 首先随意输入用户名密码&#xff08;用于抓包&#xff09; 打开burp抓包 分析数据包后 找到对应的传参点 即输入的账号密码还有验证码 这里可以看到 账号和密码全都是明文传输 并没有进行加密 所以更改起来还…

【微服务】CORS跨越问题网关请求转发时进行路径重写问题

一、跨域 CORS 简述 跨域官方文档&#xff1a; https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS 浏览器跨越问题的英文简写为CORS&#xff0c;其出现问题的截图如下&#xff1a; 浏览器拒绝跨域&#xff0c;是通过同源策略限制的。同源策略是指&#xff0c;发送请求…

文献学习03_GloVe: Global Vectors for Word Representation_20221124

论文信息 Subjects:《2014年自然语言处理经验方法会议论文集》&#xff08;EMNLP&#xff09;&#xff0c;第1532–1543页&#xff0c;2014年10月25日至29日&#xff0c; &#xff08;1&#xff09;题目&#xff1a;GloVe: Global Vectors for Word Representation &#xff0…

本地GitLab服务器搭建

一、简介 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。安装方法是参考GitLab在GitHub上的Wiki页面。Gitlab是被广泛使用的基于git的开源代码管理平台, 基于Ruby on Rails构建, 主要针对软件开发过…

把握性能测试重点,5步解决问题!

一、引言 很多做性能测试的同学都问过我这样一个问题&#xff1a;鱼哥(Carl_奕然)&#xff0c;你说性能测试的重点是什么? 我的回答很简单&#xff1a;瓶颈分析与问题定位。 在性能项目的整个周期&#xff0c;不管是脚本设计&#xff0c;脚本编写还是脚本执行&#xff0c;都…

什么时候可以用到强化学习?强化学习怎么用?

我相信很多像我一样的初学者在学习强化学习的的过程会有一种困惑&#xff1a;强化学习内容搞懂了&#xff0c;算法流程也明白了&#xff0c;但是怎么用在自己的研究领域或者应用上呢&#xff1f;换句话说&#xff0c;什么样的情况可以用强化学习解决呢&#xff1f; 什么是强化…

基于MxNet实现目标检测-CenterNet【附部分源码及模型】

文章目录前言目标检测发展史及意义一、数据集的准备1.标注工具的安装2.数据集的准备3.标注数据4.解释xml文件的内容二、网络结构的介绍三、代码实现0.工程目录结构如下1.导入库2.配置GPU/CPU环境3.数据加载器4.模型构建5.模型训练1.学习率设置2.优化器设置3.损失设置4.循环训练…