[Unity 3D] 将自界说配置整合到 ProjectSettings

计算机软件开发 2024-9-27 12:30:36 27 0 来自 中国
在本文笔者将教各人怎样将自己所写插件的全局配置绘制到 ProjectSettings , 同时将配置文件存放在 ProjectSettings 目次下。
前言

HybridCLR 配置项均为编辑器下见效,这种配置文件放置在项目中就会对原有项目有侵入,但是放在 ProjectSettings 文件夹中就会很完善,这作用域拿捏的死死的;同时,将 HybridCLR Settings 绘制到 ProjectSettings 面板,更显优雅。在此配景下,我提了 PR,任意作此文以记之,渴望可以或许资助到必要的朋侪。
1.png 实现


  • 通过继承: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 功能不兼容,会报错。
版权全部,转载请注明出处
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-11-22 01:26, Processed in 0.190894 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表