2024-02-25 Unity 编辑器开发之编辑器拓展7 —— Inspector 窗口拓展

1 SerializedObject 和 SerializedProperty

​ 在 Unity 中,可以完全自定义某一个脚本在 Inspector 窗口的相关显示。

​ SerializedObject 和 SerializedProperty 主要用于在 Unity 编辑器中操作和修改序列化对象的属性,通常在自定义编辑器中使用,以创建更灵活、可定制的属性面板。

​ 只需记住简单的规则:

2 自定义显示步骤

  1. 单独为某一个脚本实现一个自定义脚本,并且脚本需要继承 Editor。

    一般该脚本命名为:自定义脚本名 + Editor。

image-20240225145626645
  1. 在该脚本前加上特性。
    命名空间:UnityEditor
    特性名:CustomEditor(想要自定义脚本类名的 Type)
using UnityEditor;
using UnityEngine;

// 通过该特性,可以为 Lesson22 脚本自定义 Inspector 窗口中的显示
[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    ...
}
  1. 声明对应 SerializedProperty 序列化属性对象,主要通过它和自定义脚本中的成员进行关联。

    可以利用继承 Editor 后的成员 serializedObject 中的 FindProperty("成员变量名") 方法关联对应成员。

    一般在 OnEnable 函数中初始化。当选中对象后并在 Inspector 窗口进行显示时,OnEnable 函数会被执行;同理,选择其他对象使 Inspector 窗口取消显示时,OnDisable 函数会被执行。

using UnityEditor;
using UnityEngine;

// 通过该特性,可以为 Lesson22 脚本自定义 Inspector 窗口中的显示
[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty atk;
    private SerializedProperty def;
    private SerializedProperty obj;

    private void OnEnable() {
        // 关联序列化属性
        atk = serializedObject.FindProperty("atk");
        def = serializedObject.FindProperty("def");
        obj = serializedObject.FindProperty("obj");
    }
}
  1. 重写 OnInspectorGUI 函数。

    该函数控制 Inspector 窗口中显示的内容,只需在其中重写内容便可自定义窗口。

    注意:其中的逻辑需要包裹在这两句代码之间:

    serializedObject.Update(); // 更新序列化对象的表示形式 ... serializedObject.ApplyModifiedProperties(); // 应用属性修改

using UnityEditor;
using UnityEngine;

// 通过该特性,可以为 Lesson22 脚本自定义 Inspector 窗口中的显示
[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty atk;
    private SerializedProperty def;
    private SerializedProperty obj;
    
    private bool foldOut;

    private void OnEnable() {
        // 关联序列化属性
        atk = serializedObject.FindProperty("atk");
        def = serializedObject.FindProperty("def");
        obj = serializedObject.FindProperty("obj");
    }
    
    // 该函数控制了 Inspector 窗口中显示的内容
    // 只需要在其中重写内容便可以自定义窗口
    public override void OnInspectorGUI() {
        // base.OnInspectorGUI(); 不要调用父类的方法,而是去自定义
        
        serializedObject.Update(); // 更新序列化对象的表示形式
        
        // 自定义Inspector窗口的内容
        foldOut = EditorGUILayout.BeginFoldoutHeaderGroup(foldOut, "基础属性");
        if (foldOut) {
            if (GUILayout.Button("测试自定义 Inspector 窗口")) {
                Debug.Log(target.name); // 获取 Lesson22 脚本对象
            }
            EditorGUILayout.IntSlider(atk, 0, 100, "攻击力");
            def.floatValue = EditorGUILayout.FloatField("防御力", def.floatValue);
            EditorGUILayout.ObjectField(obj, new GUIContent("敌对对象"));
        }
        EditorGUILayout.EndFoldoutHeaderGroup();
        
        serializedObject.ApplyModifiedProperties(); // 应用属性修改
    }
}
image-20240225150537536

3 数组、List 自定义显示

3.1 基础方式

EditorGUILayout.PropertyField(SerializedProperty对象, 标题);

​ 该 API 会按照属性类型默认处理控件绘制的逻辑。

using UnityEditor;
using UnityEngine;

// 通过该特性,可以为 Lesson22 脚本自定义 Inspector 窗口中的显示
[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty strs;
    private SerializedProperty ints;
    private SerializedProperty gameObjects;
    private SerializedProperty listObjs;

    private void OnEnable() {
        // 默认得到的数组和 List 容量为空
        strs = serializedObject.FindProperty("strs");
        ints = serializedObject.FindProperty("ints");
        gameObjects = serializedObject.FindProperty("gameObjects");
        listObjs = serializedObject.FindProperty("listObjs");
    }
    
    public override void OnInspectorGUI() {
        serializedObject.Update();
        
        EditorGUILayout.PropertyField(strs, new GUIContent("字符串数组"));
        EditorGUILayout.PropertyField(ints, new GUIContent("整形数组"));
        EditorGUILayout.PropertyField(gameObjects, new GUIContent("游戏对象数组"));
        EditorGUILayout.PropertyField(listObjs, new GUIContent("游戏对象List"));

        serializedObject.ApplyModifiedProperties();
    }
}
image-20240225152856784

3.2 自定义方式

​ 利用 SerializedProperty 中数组相关的 API 来完成自定义。

API说明
arraySize获取数组或 List 容量。
InsertArrayElementAtIndex(索引)为数组在指定索引插入默认元素(容量会变化)。
DeleteArrayElementAtIndex(索引)为数组在指定索引删除元素(容量会变化)。
GetArrayElementAtIndex(索引)获取数组中指定索引位置的 SerializedProperty 对象。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

// 通过该特性,可以为 Lesson22 脚本自定义 Inspector 窗口中的显示
[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty strs;
    private SerializedProperty ints;
    private SerializedProperty gameObjects;
    private SerializedProperty listObjs;

    private int count;

    private void OnEnable() {
        // 默认得到的数组和 List 容量为空
        strs = serializedObject.FindProperty("strs");
        ints = serializedObject.FindProperty("ints");
        gameObjects = serializedObject.FindProperty("gameObjects");
        listObjs = serializedObject.FindProperty("listObjs");

        // 初始化当前容量,否则每次开始都是 0
        count = listObjs.arraySize;
    }

    public override void OnInspectorGUI() {
        serializedObject.Update();

        // 容量设置
        count = EditorGUILayout.IntField("List容量", count);
        
        // 移除尾部内容,从后往前移除
        for (int i = listObjs.arraySize - 1; i >= count; i--)
            listObjs.DeleteArrayElementAtIndex(i);
        
        // 根据容量绘制需要设置的每一个索引位置的对象
        for (int i = 0; i < count; i++) {
            // 如果数组或 List 容量不够,通过插入的形式扩容
            if (listObjs.arraySize <= i)
                listObjs.InsertArrayElementAtIndex(i);
        
            SerializedProperty indexPro = listObjs.GetArrayElementAtIndex(i);
            EditorGUILayout.ObjectField(indexPro, new GUIContent($"索引{i}"));
        }

        serializedObject.ApplyModifiedProperties();
    }
}
image-20240225153514603

4 自定义属性自定义显示

4.1 基础方式

EditorGUILayout.PropertyField(SerializedProperty对象, 标题);

​ 需要为自定义类添加 Serializable 特性。

using System;
using UnityEngine;

[Serializable]
public class MyCustomPro
{
    public int   i;
    public float f;
}

public class Lesson22 : MonoBehaviour
{
    public MyCustomPro myCustom;
}
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty myCustom;

    private void OnEnable() {
        myCustom = serializedObject.FindProperty("myCustom");
    }

    public override void OnInspectorGUI() {
        serializedObject.Update();
        
        EditorGUILayout.PropertyField(myCustom, new GUIContent("我的自定义属性"));

        serializedObject.ApplyModifiedProperties();
    }
}
image-20240225154307517

4.2 自定义方式

​ 使用如下方法寻找自定义属性的成员:

  1. SerializedProperty.FindPropertyRelative(属性)
  2. SerializedObject.FindProperty(属性.子属性)
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty myCustomI;
    private SerializedProperty myCustomF;

    private void OnEnable() {
        // myCustomI = myCustom.FindPropertyRelative("i");
        // myCustomF = myCustom.FindPropertyRelative("f");

        myCustomI = serializedObject.FindProperty("myCustom.i");
        myCustomF = serializedObject.FindProperty("myCustom.f");
    }

    public override void OnInspectorGUI() {
        serializedObject.Update();
        
        myCustomI.intValue = EditorGUILayout.IntField("自定义属性中的I", myCustomI.intValue);
        myCustomF.floatValue = EditorGUILayout.FloatField("自定义属性中的F", myCustomF.floatValue);

        serializedObject.ApplyModifiedProperties();
    }
}
image-20240225154447107

5 字典自定义显示

5.1 SerizlizeField

​ SerizlizeField 特性让私有字段可以被序列化(能够在 Unity 的 Inspector 窗口被看到)。

5.2 ISerializationCallbackReceiver

​ 该接口是 Unity 用于序列化和反序列化时执行自定义逻辑的接口,实现该接口的类能够在对象被序列化到磁盘或从磁盘反序列化时执行一些额外代码。

​ 接口中函数:

  • OnBeforeSerialize: 在对象被序列化之前调用。
  • OnAfterDeserialize: 在对象从磁盘反序列化后调用。

​ 由于需要用两个 List 存储 Dictionary 的键值对,所以需要在

  • OnBeforeSerialize 序列化之前:将 Dictionary 里的数据存入 List 中进行序列化。
  • OnAfterDeserialize 反序列化之后:将 List 中反序列化出来的数据存储到 Dictionary 中。

5.3 代码示例

using System.Collections.Generic;
using UnityEngine;

public class Lesson22 : MonoBehaviour, ISerializationCallbackReceiver
{
    public Dictionary<int, string> myDic = new Dictionary<int, string>() { { 1, "123" }, { 2, "234" } };

    [SerializeField]
    private List<int> keys = new List<int>();

    [SerializeField]
    private List<string> values = new List<string>();

    public void OnAfterDeserialize() {
        myDic.Clear();
        for (int i = 0; i < keys.Count; i++) {
            if (!myDic.ContainsKey(keys[i]))
                myDic.Add(keys[i], values[i]);
            else
                Debug.LogWarning("字典Dictionary容器中不允许有相同的键");
        }
    }

    public void OnBeforeSerialize() {
        keys.Clear();
        values.Clear();
        foreach (var item in myDic) {
            keys.Add(item.Key);
            values.Add(item.Value);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Lesson22))]
public class Lesson22Editor : Editor
{
    private SerializedProperty keys;
    private SerializedProperty values;

    private int dicCount;

    private void OnEnable() {
        keys = serializedObject.FindProperty("keys");
        values = serializedObject.FindProperty("values");

        dicCount = keys.arraySize;
    }

    public override void OnInspectorGUI() {
        serializedObject.Update();

        dicCount = EditorGUILayout.IntField("字典容量", dicCount);
        // 容量变少时,把多的删了
        for (int i = keys.arraySize - 1; i >= dicCount; i--) {
            keys.DeleteArrayElementAtIndex(i);
            values.DeleteArrayElementAtIndex(i);
        }

        for (int i = 0; i < dicCount; i++) {
            // 如果容量不够,扩容
            if (keys.arraySize <= i) {
                keys.InsertArrayElementAtIndex(i);
                values.InsertArrayElementAtIndex(i);
            }
            // 自定义键值对的修改
            SerializedProperty indexKey   = keys.GetArrayElementAtIndex(i);
            SerializedProperty indexValue = values.GetArrayElementAtIndex(i);
            EditorGUILayout.BeginHorizontal();
            indexKey.intValue = EditorGUILayout.IntField("字典的键", indexKey.intValue);
            indexValue.stringValue = EditorGUILayout.TextField("字典的值", indexValue.stringValue);
            EditorGUILayout.EndHorizontal();
        }

        serializedObject.ApplyModifiedProperties();
    }
}
image-20240225180102531
  • 17
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔗理苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值