Unity 编辑器之 SettingsProvider、Hierarchy、Project、SearchField

1. SettingProvider

1. 概念

Preference 是 user 级别的,不进 git,存储方式可以用 EditorPref;ProjectSetting 是项目级别的,进 git,通过创建 ScriptableObject 来生成 .asset 文件,供团队共用。

2. 与 .asset 的关系

3. 示例

分为两种形式:

一种是通过手动创建 ScriptableObject,把路径设置为 ProjectSettings/custom_plugin.asset,然后直接通过 File.ReadAllText 读取,使用 JsonUtility 类写入。此时 asset 文件里面是 JSON 格式,不是 YAML 格式。

另一种是通过 Unity 封装的方法自动创建 ScriptableObject,asset 文件里面是 YAML 格式(推荐)。

3.1 第一种
cs 复制代码
// Assets/Scripts/MySDKSettings.cs
using UnityEngine;

[System.Serializable]
public class MySDKSettings : ScriptableObject
{
    public const string k_Path = "ProjectSettings/MySDKSettings.asset";

    public bool enableDebug = false;
    public string endpoint = "https://prod.example.com";
    public int maxRetry = 3;

    // ===== 运行时访问 =====
    private static MySDKSettings s_Instance;
    public static MySDKSettings Instance
    {
        get
        {
            if (s_Instance == null)
                s_Instance = LoadOrCreate();
            return s_Instance;
        }
    }

#if UNITY_EDITOR
    public static MySDKSettings LoadOrCreate()
    {
        MySDKSettings settings;

        if (System.IO.File.Exists(k_Path))
        {
            settings = CreateInstance<MySDKSettings>();
            var json = System.IO.File.ReadAllText(k_Path);
            JsonUtility.FromJsonOverwrite(json, settings);
        }
        else
        {
            settings = CreateInstance<MySDKSettings>();
            Save(settings);
        }

        return settings;
    }

    public static void Save(MySDKSettings settings)
    {
        var json = JsonUtility.ToJson(settings, true);
        System.IO.File.WriteAllText(k_Path, json);
        UnityEditor.AssetDatabase.Refresh();
    }
#else
    // 运行时:不允许访问
    public static MySDKSettings LoadOrCreate()
    {
        throw new System.NotSupportedException("MySDKSettings cannot be loaded at runtime from ProjectSettings");
    }
#endif
}
cs 复制代码
// Assets/Editor/MySDKSettingsProvider.cs
using UnityEditor;
using UnityEngine;

[SettingsProvider]
static SettingsProvider CreateMySDKSettingsProvider()
{
    return new SettingsProvider("Project/My SDK", SettingsScope.Project)
    {
        label = "My SDK",
        guiHandler = (searchContext) =>
        {
            var settings = MySDKSettings.Instance;
            var so = new SerializedObject(settings);

            using (var scope = new EditorGUI.ChangeCheckScope())
            {
                EditorGUILayout.PropertyField(so.FindProperty("enableDebug"));
                EditorGUILayout.PropertyField(so.FindProperty("endpoint"));
                EditorGUILayout.PropertyField(so.FindProperty("maxRetry"));

                if (scope.changed)
                {
                    so.ApplyModifiedProperties();
                    MySDKSettings.Save(settings);
                }
            }

            EditorGUILayout.HelpBox("配置保存在 ProjectSettings/MySDKSettings.asset", MessageType.Info);
        },

        keywords = new System.Collections.Generic.HashSet<string>(new[] { "MySDK", "Debug", "Endpoint" })
    };
}
3.2 第二种
cs 复制代码
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.UIElements;

[FilePath("ProjectSettings/MyCustomPlugin.asset", FilePathAttribute.Location.ProjectFolder)]
public class MyScriptableObject : ScriptableSingleton<MyScriptableObject>
{
    public string testString;
    public int testInt;

    // save 方法是 protected,必须重新封装一下
    public void SaveChange(bool saveAsText)
    {
        Save(saveAsText); // 必须手动 Save,才会写盘
    }
}

public class SettingProviderSample : SettingsProvider
{
    private SerializedObject serializeObj;
    private SerializedProperty serializeFieldInt;
    private SerializedProperty serializeFieldString;

    [SettingsProvider]
    static SettingsProvider CreateMyProjectSettings()
    {
        var provider = new SettingProviderSample("Project/My Plugin", SettingsScope.Project)
        {
            label = "My Plugin", // 左侧列表显示的名字
            keywords = new[] { "A", "Custom", "Ray" }
        };
        return provider;
    }

    private SettingProviderSample(string path, SettingsScope scopes, IEnumerable<string> keywords = null) : base(path,
        scopes, keywords)
    {
    }

    public override void OnActivate(string searchContext, VisualElement rootElement)
    {
        base.OnActivate(searchContext, rootElement);
        serializeObj = new SerializedObject(MyScriptableObject.instance);
        serializeFieldInt = serializeObj.FindProperty("testInt");
        serializeFieldString = serializeObj.FindProperty("testString");

        EditorGUILayout.PropertyField(serializeObj.FindProperty("testString"));
    }

    public override bool HasSearchInterest(string searchContext)
    {
        return base.HasSearchInterest(searchContext);
        return searchContext.Contains("A");
    }

    public override void OnGUI(string searchContext)
    {
        base.OnGUI(searchContext);

        serializeObj.UpdateIfRequiredOrScript();

        EditorGUI.BeginChangeCheck();

        EditorGUILayout.BeginFadeGroup((string.IsNullOrEmpty(searchContext) || !searchContext.Contains("testInt"))
            ? 1
            : 0);
        EditorGUILayout.PropertyField(serializeFieldString);
        EditorGUILayout.EndFadeGroup();
        EditorGUILayout.PropertyField(serializeFieldInt);


        if (EditorGUI.EndChangeCheck())
        {
            serializeObj.ApplyModifiedProperties();
            MyScriptableObject.instance.SaveChange(true);
        }
    }
}

2. SearchContext

HasSearchInterest 的优先级大于 keywords,同时存在时,以 HasSearchInterest 为准。

3. EditorApplication.hierarchyWindowItemOnGUI

cs 复制代码
[InitializeOnLoad]
public class HierarchyIcon
{
    private static Texture2D myIcon;
    static HierarchyIcon()
    {
        myIcon = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Samples/Sprite/icon_1.png");
        EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyGUI;
    }
    
    static void OnHierarchyGUI(int instanceID, Rect selectionRect)
    {
        var go = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
        if (go != null && go.GetComponent<PathTransform>() != null)
        {
            Rect r = new Rect(selectionRect);
            r.x = 120;
            r.size = new Vector2(20, 20);
            GUI.DrawTexture(r, myIcon);
        }
    }
}

4. EditorApplication.projectWindowItemOnGUI

cs 复制代码
[InitializeOnLoad]
public class ProjectIcon
{
    private static Texture2D myIcon;
    static ProjectIcon()
    {
        myIcon = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Samples/Sprite/icon_2.png");
        EditorApplication.projectWindowItemOnGUI += OnHierarchyGUI;
    }
    
    static void OnHierarchyGUI(string guid, Rect selectionRect)
    {
        var go = AssetDatabase.GUIDToAssetPath(guid);
        if (go != null && go.Contains("PathTransform"))
        {
            Rect r = new Rect(selectionRect);
            r.x = 150;
            r.size = new Vector2(20, 20);
            GUI.DrawTexture(r, myIcon);
        }
    }
}

5. PropertyDrawer

6. DecoratorDrawer

7. 自定义搜索框

相关推荐
木白CPP11 分钟前
Linux DMA驱动详解(二)-----DMA的使用者
java·linux·运维
caoerzhong15 分钟前
JeeWMS 开源仓库管理系统 GPL-3.0 合规指南:Java WMS 二次开发前必须弄清的授权边界
java·开发语言·开源·vue
Sam_Deep_Thinking21 分钟前
单一职责原则:JAVA LocalDate的设计取舍
java·后端·程序员·单一职责原则
杨杨杨大侠30 分钟前
Java 不再需要 JVM?一文看懂 GraalVM Native Image、Substrate VM 与 JDK
java·jvm·java ee
Sylvia33.43 分钟前
火星数据体育API|一站式接入足球篮球电竞等18+项目实时数据
java·开发语言·python·websocket·游戏
李少兄1 小时前
解决 MySQL Lock wait timeout exceeded 报错
java
va学弟1 小时前
Java 语言特性:泛型(Generics)
java·泛型
SL_staff2 小时前
插单风险的数据闭环治理:JVS-BI 在制造业实时预警中的技术实践
java·数据分析·数据可视化
白远山2 小时前
健身场馆无人自动化解决方案:从架构到落地实践
java·开发语言·数据库·数据挖掘·需求分析
user_admin_god2 小时前
第 01 篇:OpenAI 兼容 API 初探 —— 用 curl 跑通第一次对话
java·人工智能·spring boot·语言模型·devops