在本文笔者将教各人怎样将自己所写插件的全局配置绘制到 ProjectSettings , 同时将配置文件存放在 ProjectSettings 目次下。
前言
HybridCLR 配置项均为编辑器下见效,这种配置文件放置在项目中就会对原有项目有侵入,但是放在 ProjectSettings 文件夹中就会很完善,这作用域拿捏的死死的;同时,将 HybridCLR Settings 绘制到 ProjectSettings 面板,更显优雅。在此配景下,我提了 PR,任意作此文以记之,渴望可以或许资助到必要的朋侪。
实现
- 通过继承:SettingsProvider 重载 OnTitleBarGUI 函数绘制标题栏右侧三个按钮,他们是:文档、Presets、Reset。
- 通过继承:SettingsProvider 重载 OnGUI 函数绘制设置面板主体,调用的焦点 API 如下。
m_SerializedObject.Update(); // 更新序列化 数据文件EditorGUI.BeginChangeCheck(); // 开始检查面板修改EditorGUILayout.PropertyField(m_Enable); // 使用默认字段风格绘制字段...此处省略同质代码多少...if (EditorGUI.EndChangeCheck()) // 竣事面板修改的检查{ m_SerializedObject.ApplyModifiedProperties(); // 应用修改了的属性值 HybridCLRSettings.Instance.Save(); //存储单例数据到 ProjectSettings 文件夹}
- 通过 ScriptableObject 单例实现配置数据的唯一性、实现数据存储到 ProjectSettings ,此中InternalEditorUtility.LoadSerializedFileAndForget(filePath) 函数实现了 ScriptableObject 资产的 Assets 文件夹外的加载。
InternalEditorUtility.SaveToSerializedFileAndForget(obj, filePath, saveAsText); 函数实现了 ScriptableObject 资产的 Assets 文件夹外的生存。
using System;using System.IO;using System.Linq;using UnityEditor;using UnityEditorInternal;using UnityEngine;namespace HybridCLR.Editor{ public class ScriptableSingleton<T> : ScriptableObject where T : ScriptableObject { private static T s_Instance; public static T Instance { get { if (!s_Instance) { LoadOrCreate(); } return s_Instance; } } public static void LoadOrCreate() { string filePath = GetFilePath(); if (!string.IsNullOrEmpty(filePath)) { var arr = InternalEditorUtility.LoadSerializedFileAndForget(filePath); s_Instance = arr.Length > 0 ? arr[0] as T : s_Instance??CreateInstance<T>(); } else { Debug.LogError($"{nameof(ScriptableSingleton<T>)}: 请指定单例存档路径! "); } } public void Save(bool saveAsText = true) { if (!s_Instance) { Debug.LogError("Cannot save ScriptableSingleton: no instance!"); return; } string filePath = GetFilePath(); if (!string.IsNullOrEmpty(filePath)) { string directoryName = Path.GetDirectoryName(filePath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } UnityEngine.Object[] obj = new T[1] { s_Instance }; InternalEditorUtility.SaveToSerializedFileAndForget(obj, filePath, saveAsText); } } protected static string GetFilePath() { return typeof(T).GetCustomAttributes(inherit: true) .Cast<FilePathAttribute>() .FirstOrDefault(v => v != null) ?.filepath; } } [AttributeUsage(AttributeTargets.Class)] public class FilePathAttribute : Attribute { internal string filepath; /// <summary> /// 单例存放路径 /// </summary> /// <param name="path">相对 Project 路径</param> public FilePathAttribute(string path) { if (string.IsNullOrEmpty(path)) { throw new ArgumentException("Invalid relative path (it is empty)"); } if (path[0] == '/') { path = path.Substring(1); } filepath = path; } }}
- 通过继承 PresetSelectorReceiver 实现配置的 Preset(配置预设)。
using UnityEditor;using UnityEditor.Presets;using UnityEngine;namespace HybridCLR.Editor{ public class SettingsPresetReceiver : PresetSelectorReceiver { private Object m_Target; private Preset m_InitialValue; private SettingsProvider m_Provider; internal void Init(Object target, SettingsProvider provider) { m_Target = target; m_InitialValue = new Preset(target); m_Provider = provider; } public override void OnSelectionChanged(Preset selection) { if (selection != null) { Undo.RecordObject(m_Target, "Apply Preset " + selection.name); selection.ApplyTo(m_Target); } else { Undo.RecordObject(m_Target, "Cancel Preset"); m_InitialValue.ApplyTo(m_Target); } m_Provider.Repaint(); } public override void OnSelectionClosed(Preset selection) { OnSelectionChanged(selection); Object.DestroyImmediate(this); } }}
- 为了包管外部对配置的修改见效,监测 InternalEditorUtility.isApplicationActive 属性,在编辑器重新 focus 时重新加载单例并通过监听 OnEditorFocused 重绘 ProjectSettings 面板。如果想要精准一些,可以获取文件属性,有修改则重新加载。
using HybridCLR.Editor;using System;using UnityEditor;using UnityEditorInternal;/// <summary>/// 监听编辑器状态,当编辑器重新 focus 时,重新加载实例,制止某些情形下 svn 、git 等外部修改了数据却无法同步的非常。/// </summary>[InitializeOnLoad]public static class EditorStatusWatcher{ public static Action OnEditorFocused; static bool isFocused; static EditorStatusWatcher() => EditorApplication.update += Update; static void Update() { if (isFocused != InternalEditorUtility.isApplicationActive) { isFocused = InternalEditorUtility.isApplicationActive; if (isFocused) { HybridCLRSettings.LoadOrCreate(); OnEditorFocused?.Invoke(); } } }}结语
有实验过使用 Unity 自己的 ScriptableSingleton ,但终极不得不放弃,缘故起因如下:
- 由于其 hideflag 为 dontsave ,以是绘制到 ProjectSettings 的数据无法编辑(在 HybridCLRSettingsProvider 的 OnActivate 中插入 HybridCLRSettings.Instance.hideFlags &= ~HideFlags.NotEditable; 可办理不能编辑的问题)。
2.,此单例基类的构造函数的实现与 Presets 功能不兼容,会报错。
版权全部,转载请注明出处
|