在本文筆者將教大家如何將自己所寫(xiě)插件的全局配置繪制到 ProjectSettings , 同時(shí)將配置文件存放在 ProjectSettings 目錄下。
前言
HybridCLR 配置項(xiàng)均為編輯器下生效胚想,這種配置文件放置在項(xiàng)目中就會(huì)對(duì)原有項(xiàng)目有侵入,但是放在 ProjectSettings 文件夾中就會(huì)很完美旬迹,這作用域拿捏的死死的悦荒;同時(shí)茫打,將 HybridCLR Settings 繪制到 ProjectSettings 面板,更顯優(yōu)雅拜秧。在此背景下痹屹,我提了 PR,隨便作此文以記之枉氮,希望能夠幫助到需要的朋友志衍。
Settings For HybridCLR
實(shí)現(xiàn)
- 通過(guò)繼承:
SettingsProvider
重載OnTitleBarGUI
函數(shù)繪制標(biāo)題欄右側(cè)三個(gè)按鈕暖庄,他們是:文檔、Presets楼肪、Reset培廓。
- 通過(guò)繼承:
SettingsProvider
重載OnGUI
函數(shù)繪制設(shè)置面板主體,調(diào)用的核心 API 如下春叫。
m_SerializedObject.Update(); // 更新序列化 數(shù)據(jù)文件
EditorGUI.BeginChangeCheck(); // 開(kāi)始檢查面板修改
EditorGUILayout.PropertyField(m_Enable); // 使用默認(rèn)字段風(fēng)格繪制字段
...
此處省略同質(zhì)代碼若干
...
if (EditorGUI.EndChangeCheck()) // 結(jié)束面板修改的檢查
{
m_SerializedObject.ApplyModifiedProperties(); // 應(yīng)用修改了的屬性值
HybridCLRSettings.Instance.Save(); //存儲(chǔ)單例數(shù)據(jù)到 ProjectSettings 文件夾
}
- 通過(guò)
ScriptableObject
單例實(shí)現(xiàn)配置數(shù)據(jù)的唯一性肩钠、實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)到 ProjectSettings ,其中InternalEditorUtility.LoadSerializedFileAndForget(filePath)
函數(shù)實(shí)現(xiàn)了ScriptableObject
資產(chǎn)的 Assets 文件夾外的加載。
InternalEditorUtility.SaveToSerializedFileAndForget(obj, filePath, saveAsText);
函數(shù)實(shí)現(xiàn)了ScriptableObject
資產(chǎn)的 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>)}: 請(qǐng)指定單例存檔路徑价匠! ");
}
}
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">相對(duì) 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;
}
}
}
- 通過(guò)繼承
PresetSelectorReceiver
實(shí)現(xiàn)配置的 Preset(配置預(yù)設(shè))。
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);
}
}
}
- 為了保證外部對(duì)配置的修改生效呛每,監(jiān)測(cè)
InternalEditorUtility.isApplicationActive
屬性踩窖,在編輯器重新 focus 時(shí)重新加載單例并通過(guò)監(jiān)聽(tīng)OnEditorFocused
重繪 ProjectSettings 面板。如果想要精準(zhǔn)一些晨横,可以獲取文件屬性洋腮,有修改則重新加載。
using HybridCLR.Editor;
using System;
using UnityEditor;
using UnityEditorInternal;
/// <summary>
/// 監(jiān)聽(tīng)編輯器狀態(tài)颓遏,當(dāng)編輯器重新 focus 時(shí)徐矩,重新加載實(shí)例滞时,避免某些情景下 svn 叁幢、git 等外部修改了數(shù)據(jù)卻無(wú)法同步的異常。
/// </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();
}
}
}
}
結(jié)語(yǔ)
有嘗試過(guò)使用 Unity 自己的 ScriptableSingleton
,但最終不得不放棄坪稽,原因如下:
- 因?yàn)槠?hideflag 為 dontsave 曼玩,所以繪制到 ProjectSettings 的數(shù)據(jù)無(wú)法編輯(在
HybridCLRSettingsProvider
的OnActivate
中插入HybridCLRSettings.Instance.hideFlags &= ~HideFlags.NotEditable;
可解決不能編輯的問(wèn)題)。
2.窒百,此單例基類(lèi)的構(gòu)造函數(shù)的實(shí)現(xiàn)與 Presets 功能不兼容黍判,會(huì)報(bào)錯(cuò)。
版權(quán)所有篙梢,轉(zhuǎn)載請(qǐng)注明出處