赞
踩
开源地址:
简介:
这两者的区别是:
前者创建:先创建 GameObjet 再给他挂一个控制它的脚本;
后者创建:先创建一个纯 C# 对象, 再在合适的时机创建 GameObject 交给它管理。
前者:
⑴、优点:符合Unity原本的 “go-comp” 思路。
⑵、缺点:逻辑脚本和预设资源绑在一起(至少不能以传统方式热更)。
后者:
⑴、优缺点:从类图上看,更符合控制引用关系、更便于代码设计;但需要自己维护 “引用 go”、“”“解除引用 go”、“销毁 go” 的逻辑;
⑵、优点:可为 go 挂一个通用的需操作元素收集组件,与逻辑类分开,并可随意组合复用。
我之前的一篇文章里说过,游戏界面内容应该面向对象:每个界面作为一个游戏对象,界面中,较为独立的部分/重复出现的部分,可以单作为一个游戏对象,逐层嵌套,形成父子关系。 聚合/组合关系:1~1或1~N。好处是:
⑴、可以针对各层对象写逻辑,使逻辑各归其所,适合大规模建设。
⑵、对象可以复用。界面干净整洁、统一性好,不会出现有两处相同的东西,却长得不太一样
⑶、极大加快UI开发效率,设计界面、拼界面、写界面逻辑都是对 “对象化的零件” 进行拼凑组合。
比如:商店界面:商店界面 -> 货架 -> 货架的层 -> 货架层上的物体。
比如:编队界面:编队界面 -> 队伍槽位 -> 队伍中的英雄。
比如:英雄培养界面:英雄培养界面 -> 消耗材料槽(放材料图标和拥有/需求数量) -> 材料图标。
但是,这对策划(UE设计)和美术(UI效果图设计)的规划能力要求大幅增高了。
另外,也带给程序一些困惑,主要表现为:
⑴、若设计中各处针对性强 ,则难以复用。
⑵、设计复用需求不明确时,难以判断是否应按提出组件的方式来做。
(只出了UE,未出UI效果图就让程序先做功能,常常出现UE相似但最后UI不同的情况,导致返工)。
⑶、设计的需求调整可能不易,因为文件数变多了。
(只在本界面内重复出现的对象的逻辑类,可以定义在该界面的逻辑文件内,以减少文件数)
⑷、当有很多相似组件出现时,对组件的命名成了一个问题。
整体来说,利远大于弊。
接口支持度和易用性,看似多少有点冲突。
首先,在一个项目里,面对需求,框架不应该出现 “不能支持”的情况,这就可能产生很多参数和细碎接口。
一个做事的上层方法,应该让用户自己决定怎么组合调用底层接口,而不是只提供自己臆想的 “某一种组合方式” 限制用户。
但是,如果一件事大部分情况下都是以 “某一种组合方式” 去做,每次都让用户组合,又未免过于麻烦。
以下几种手段可能解决这个问题:
⑴、将一些接口的参数改为默认形参 或 重载接口(UI框架中CreatePanel、CreateWidget等接口)
⑵、为一组细碎的接口提供默认的组合接口。
⑶、父类中提供接口的默认实现,但可在子类中重写(UI框架中播放打开/关闭Panel动画接口)。
⑴、父子类创建销毁的接口/生命周期顺序?
创建时先父后子;销毁时先子后父。
⑵、上下两层层界面的焦点焦点变化,获得焦点时,应该先触发谁的 OnFocus,丢失焦点时,应该先触发谁的 OnLostFocus?
出现时是先出现下层界面后出现上层界面;消失时是先消失上层界面后消失下层界面。
⑶、聚合/依赖关系,销毁的时候先解除引用还是先销毁自己?
创建:创建自身、建立引用;那么移除应该相反,即:解除引用、销毁自身。
⑷、对外提供接口的完成回调 和 对内完成事件的调用顺序?
对外提供接口的完成回调,应该晚于内部完成事件,即:先处理完内部,再处理外部。
一个复杂的黑盒接口,传入非法参数时,让它报错还是返回 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 {}
基本流程:
⑴、界面创建后,播放打开动画(若有)。
⑵、界面初始化时,注入或获取 Data 完成显示。
⑶、界面刷新时,注入或获取 Data 完成显示。
⑷、界面关闭时,播放关闭动画(若有)。
注意:
⑴、动画播放是异步的。动画一般都是创建时挂到预设上的,只操作预设初始节点,不依赖数据。
⑵、获取Data可能是异步的(现请求)。
⑶、某些组件的显示可能是异步的(如:为了优化脏标记异步更新)。
外部需求:
⑵、跳转连续打开多个界面时,不关心动画,但依赖数据(由数据决定是否可以依次打开,直至目标界面)。
⑶、功能解锁、红点、引导等上层系统需要能随时获取界面当前状态(如引导,要等界面完全准备好后才能执行)。
---------------------------------
其他问题:
⑴、异步请求数据,应放在Create前还是Init中?
建议后者,后者可以利用自身界面阻挡操作。但注意,必须处理好“创建后~初始化完成前”的显示。
⑵、界面显示和动画状态如何维护?
①、只维护自身状态,不考虑子Widget。
②、但在外部读取时可以考虑计入自身及所有子Widget的状态(结合实际需求)。
③、初始化/刷新方法 完全由用户自定义(可能不是最终想法),如果是同步的,默认标记为Idle;如果是异步的,可以应该在初始化/刷新开始时将显示状态改为Initing/Refreshing,并在完成时将显示状态标记为Idle。
④、在Panel创建时默认调起打开动画,播放完成时将动画状态改为Idle。
⑤、在Panel关闭时调起关闭动画,播放完成时将动画状态改为Closed。
⑶、是否将界面状态暴露到 Inspector中,便于调试?
不确定是否有必要,待定。
⑴、维护焦点变化,起到什么作用?
界面失去焦点时,可选择性地“挂起”(暂停内部耗时Update类操作),并在重新获得焦点时恢复,以此优化。另外,还可在获得焦点时做一些事件触发,比如拍脸弹窗等。
⑵、关闭界面时是否触发 OnFoucusChanged(false)?
否,焦点在打开/关闭界面之后统一计算的。
无法触发已关闭界面的 OnFoucusChanged(false)。
这意味着,OnFoucusChanged 是不完全对称的。
如果在其中做了一些创建操作(尽量不要这样做),可能需要在 OnClosing 中善后清理。
⑶、界面的打开/关闭动画对焦点变化有什么影响?
界面在上层创建时,获得焦点应该是敏感的,即:只要创建就可能立刻获得焦点,此时下层界面丢失焦点也是立刻的。
界面在上层销毁时,下层界面获得焦点应该是迟钝的,即:要等到上层界面完全销毁,下层界面才能够获得焦点。
⑴、通用背景是否是单例的?
是,一个够用。可以根据当前状态,移动背景到它应该去的界面。
⑵、通用背景是否总是出现在主要获得焦点的界面上?
不是,有些界面需要背景,但却不抢夺焦点(System类型)。
⑶、界面动画不应对预设根节点进行操作(缩放、旋转、位移)
因为背景是添加到预设根节点下的第一个物体,如果操作根节点,就会带着背景一起移动。
也是完全可以避免的:
①、PanelType 为 Underlay 的 界面,打开动画一般是 “子元素逐渐加入”(不会操作根节点)、“翻篇进入”(应该加入额外一个动画根节点)。
②、PanelType 为 Window 的 界面,打开动画一般是 “缩放、淡入淡出、飞入飞出等(应该加入额外一个动画根节点)”。
⑴、每次支持新组件,都要改哪些地方?
①、支持原组件使用的图标,
修改 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));
Alt+1:Add(Set)
Alt+2:Drelete(Remove)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。