[Unity 3D] 将自定义配置整合到 ProjectSet
2022-10-17 本文已影响0人
雨落随风
在本文笔者将教大家如何将自己所写插件的全局配置绘制到 ProjectSettings , 同时将配置文件存放在 ProjectSettings 目录下。
前言
HybridCLR 配置项均为编辑器下生效,这种配置文件放置在项目中就会对原有项目有侵入,但是放在 ProjectSettings 文件夹中就会很完美,这作用域拿捏的死死的;同时,将 HybridCLR Settings 绘制到 ProjectSettings 面板,更显优雅。在此背景下,我提了 PR,随便作此文以记之,希望能够帮助到需要的朋友。
Settings For HybridCLR实现
- 通过继承:
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 功能不兼容,会报错。
版权所有,转载请注明出处