开发平台:Unity 2021 编程平台:Visual Studio 2020
前言根据项目类型与参与工时的开发者规模作为参考,为加快项目开发效率,往往采取的是定制专业化工具代替投入人力成本的方式。于是编辑扩展成为了 Unity 开发者必须掌握的技能技巧。Rainbow Folders(彩虹文件夹)以修改编辑器内部视图颜色效果,加以区分项目资源类型、场景工程内资产类型等。有助于开发者快速区分资产。本文重点探讨 Rainbow Hierarchy 的实现思路与方式。
思考:如何篡改 Hierarchy 界面 对 Hierarchy 界面进行篡改,优先考虑查询关于该界面的 API 文档。在 Unity 中文文档的解释中,对编辑器本身进行修改与扩展的 API 被归纳于 UnityEditor.DLL 中。即内部关联的 EditorApplication
类。查阅关键词 Editor、Hierarchy 与实际开发使用大致有以下 API 与其关联。
hierarchyWindowItemOnGUI
:Hierarchy 面板中各可见元素的 GUI 委托。HierarchyWindowItemCallback(int instanceID, Rect selectionRect)
:Hierarchy 面板中各可见元素的委托回调方法。
public static void OnEnable() { EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyReDraw; }
public static void OnHierarchyWindowReDraw(int instanceID, Rect selectionRect)
{
GameObject current = Selection.activeGameObject;
Debug.Log($"{current} {current.name} {current.scene.name} {selectionRect}");
}
◼ 问题点:
- 缺少可行的 Hierarchy 中单一 Element 的修改Element。 解决方法:(见下板块)
- 即使脚本存放至 Editor 目录下,
OnEnable()
并不会被执行。即使使用ExecuteAlways
/ExecuteInEditor
特性也无用。 解决方法:使用InitializeOnLoad
或InitializeOnLoadMethod
特性。该两特性应用于 EditorApplication 模式下的功能拓展。而前两种是用于期望 Application 模式在 EditorApplication 中运行。
思考:篡改 Element 的 Unity API
Rainbow Hierarchy 的最终目的是篡改 Hierarchy 面板中 Element 元素呈现方式。意图通过重写Unity编辑器底层逻辑的思想无法实行。 因为无法确认 Unity Technology 是否提供该重写方法。以及大量性使用反射易造成计算机性能降低,为达到目的而降低性能不可取。于是实现 Rainbow 方式的落点在 EditorGUI 上,覆盖原 Element 图层成为当前可行的方法。
图层文本:EditorGUI.LabelField(Rect rect, string name, GUIStyle style)
- 参数
Rect
:由HierachyWindowItemCallback
提供。 - 参数
name
:使用Selection.activeGameObject.name
获取 - 参数
GUIStyle
:创建配置表 或 配置模板。
如下图所示:
◼ 问题点:
- 覆盖的 EditorGUI 仅在发生选中时,激活与生成。 解决方法:使用
InstanceID
反向定位 GameObject 获取信息。 关联方法:EditorUtility.InstanceIDToObject(instanceID) as GameObject;
- 期望仅部分对象应用样式。 解决方法:建立
List
存储添加样式。添加、删除等方式可选择MenuItem
实现或添加 GUI.Button - 缺乏灵活的 Rainbow 样式配置。 解决方法:(见下一模块)
思考:持续化 Hierarchy Element 刷新
或许第一次使用 Rainbow Hierarchy 并未认为有什么问题,如果项目发生重启、工程迁移,亦或是希望颜色与样式发生变化。那么仅通过脚本内部识别检测是行不通的。于是,可视化配置 Rainbow Hierarchy 成为当前需要解决的难题。幸运的是 Unity 针对 Editor 提供了 ScriptableObject 持久化数据存储。
思考:ScriptableObject 的职责是什么? 答:根据目前 Hierarchy 的修改需求,其大致负责以下内容。
- 管理已有 Rainbow Element 样式信息。 标签(字体、颜色、字号、粗体、斜体、) 背景(颜色) …
- 记录各 Scene 内资产的标记信息,以期望下次打开后直接读取配置。
[System.Serializable]
public class HierarchyConfig {
public string GameObjectName;
public Color FontColor = Color.white;
public int FontSize = 15;
public Color BackgroundColor = Color.black;
public Rect InstanceRect;
}
当发生 Rainbow 标记行为时,自行检查与创建对应匹配的 ScriptableObject。并索引数据目录下是否记录有该对象的标记数据,有则读取应用,无则忽略 Rainbow 生成。每次 Hierarchy 面板更新时,均按照此逻辑运行。
◼ 问题点:
- 当场景发生变化时,InstanceID 将发生动态变化,每次加载新的场景,其场景内部资产均重新分配 InstanceID,原有记录的 InstanceID 将无效。 解决方法:(见下一模块)
- ScriptableObject 在使用
MeuItem
或其他便捷操作,在场景发生变更时,已添加的数据将丢失。 解决方法:每次完成后,对资产对象调用EditorUtility.SetDirty()
进行缓存存储,避免丢失。
思考:解决资产识别唯一码
资产识别码的作用是标识资产信息,如果场景内出现同命名的 GameObject 对象,在记录标记时会对已有对象的标记进行覆盖,或同名对象均应用同类型的标记信息。在设计上这种情况是不可取。于是 GUID 唯一码 成为当前最佳有效识别资产的参考标准。
◼ 获取:GameObject.GetType().GUID 类型:GUID
描述:该方法下的匹配规则用于甄别对象信息。 问题:Hierarchy 面板下资产信息持续返回 {0000-000000000000} 结果,无论 ScriptableObject 是否存在数据。
◼ 获取:AssetDatabase.TryGetGUIDAndLocalFileIdentifier(object, out string out ulong) 类型:string
描述:该方法下的匹配规则用于甄别对象信息。 问题:Hierarchy 面板下资产信息持续返回 {0000} 结果,无论 ScriptableObject 是否存在数据。
从上述两种获取资产识别码中,注意到返回的结果均为 000 开头的无效结果。经分析与猜想,Hierarchy 面板中的资产数据严格意义上不属于 AssetsDatabase 中存储的资产,返回的结果固然是无效的。
于是难题就来了,如何去获取这个场景内部资产的唯一码 或 是有从创建使用开始就不会因为场景变化、项目迁移导致的识别码变更。在翻遍Unity编辑器功能中,发现了一个满足要求的识别码 —— Local Identifier In File(Inspector 面板下 Debug 模式可见),如何获取该参数值,成为当前最需解决的问题。
◼ 使用反射方式获取信息 答:见 Unity 外网论坛 给出示例。
◼ GlobalObject.GetGlobalObjectIDSlow() 推荐 答:这是上述论坛最底部的评论中了解,Unity 更新了 GlobalObject 协助开发者获取 Local Identifier In File 的类。参考文档说明,内部提供获取场景内资产唯一ID的方法。确切的帮助解决了获取这一途径的方法。
于是问题得到了迎刃而解,以该ID作为辨识依据实现运行即执行的效果。无需每次启动配置的结果。
其他◼ 可优化层面
- 使用 UI Toolkit,制作编辑入口,以便利化调整参数。
- 创建样式模板,每次选择准备好的样式即可,无需逐一配置。