26.Inspector窗口拓展字典

26.Inspector窗口拓展-字典


26.1 知识点

知识回顾 SerizlizeField特性

在字段前添加可以让私有字段被序列化(能够在Unity的Inspector窗口被看到)。

如何在Inspector窗口编辑字典成员

Unity默认是不支持Dictionary在Inspector窗口被显示的,我们只有利用两个List(或数组)成员来间接设置Dictionary。

ISerializationCallbackReceiver接口

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

  • 接口中函数:

    • OnBeforeSerialize: 在对象被序列化之前调用
    • OnAfterDeserialize: 在对象从磁盘反序列化后调用
  • 由于我们需要用两个List存储Dictionary的具体值,相当于字典中的真正内容是存储在两个List中的,所以我们需要在OnBeforeSerialize序列化之前将Dictionary里的数据存入List中进行序列化,在OnAfterDeserialize反序列化之后将List中反序列化出来的数据存储到Dictionary中。

  • 在自定义脚本中继承ISerializationCallbackReceiver接口,声明字典和两个序列化的List(存储键值对),在序列化前和反序列化后分别存储读取字典的键值对。

public class TestInspectorMono : 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);
        }
    }
}

利用两个List在Inspector窗口中自定义Dictionary显示

由于我们在Inspector窗口中显示的信息的数据来源是List,因此我们只需要利用List在Inspector窗口中自定义显示即可。

编辑器脚本中声明属性得到两个List,声明字典长度初始化赋值,写字典缩减扩容逻辑。

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();
}

运行结果

在自定义脚本游戏运行开始时打印字典值

能正常打印说明数据存储到字典成功

private void Start()
{
    foreach (var item in myDic)
    {
        print($"Dic:{item.Key} - {item.Value}");
    }
}

总结

由于Unity中不支持在Inspector窗口直接使用Dictionary,因此我们可以利用两个List(或数组)来间接的表达Dictionary成员。


26.2 知识点代码

Lesson26_Inspector窗口拓展_字典

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson26_Inspector窗口拓展_字典 : MonoBehaviour
{
    void Start()
    {
        #region 知识回顾 SerizlizeField特性

        //让私有字段可以被序列化(能够在Unity的Inspector窗口被看到)

        #endregion

        #region 知识点一 如何在Inspector窗口编辑字典成员

        //Unity默认是不支持Dictionary在Inspector窗口被显示的
        //我们只有利用两个List(或数组)成员来间接设置Dictionary

        #endregion

        #region 知识点二 ISerializationCallbackReceiver接口

        //该接口是Unity提供的用于序列化和反序列化时执行自定义逻辑的接口
        //实现该接口的类能够在对象被序列化到磁盘或从磁盘反序列化时执行一些额外代码
        //接口中函数:
        //OnBeforeSerialize: 在对象被序列化之前调用
        //OnAfterDeserialize: 在对象从磁盘反序列化后调用

        //由于我们需要用两个List存储Dictionary的具体值
        //相当于字典中的真正内容是存储在两个List中的
        //所以我们需要在
        //OnBeforeSerialize序列化之前:将Dictionary里的数据存入List中进行序列化
        //OnAfterDeserialize反序列化之后:将List中反序列化出来的数据存储到Dictionary中

        #endregion

        #region 知识点三 利用两个List在Inspector窗口中自定义Dictionary显示

        //由于我们在Inspector窗口中显示的信息的数据来源是List
        //因此我们只需要利用List在Inspector窗口中自定义显示即可

        
        
        #endregion

        #region 总结

        //由于Unity中不支持在Inspector窗口直接使用Dictionary
        //因此我们可以利用两个List(或数组)来间接的表达Dictionary成员

        #endregion
    }
}

TestInspectorMono

using System;
using System.Collections.Generic;
using UnityEngine;

public class TestInspectorMono : MonoBehaviour, ISerializationCallbackReceiver
{
    #region Lesson23_Inspector窗口拓展_基础知识

    //攻击力
    public int atk;

    //防御力
    public float def;

    //敌对目标对象依附的Gameobject
    public GameObject obj;

    #endregion

    #region Lesson24_Inspector窗口拓展_数组List

    public string[] strs;
    public int[] ints;
    public GameObject[] gameObjects;

    public List<GameObject> listObjs;

    #endregion

    #region Lesson25_Inspector窗口拓展_自定义数据

    public TestCustomClass myCustom;

    #endregion

    #region Lesson26_Inspector窗口拓展_字典

    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);
        }
    }


    private void Start()
    {
        foreach (var item in myDic)
        {
            print($"Dic:{item.Key} - {item.Value}");
        }
    }

    #endregion
}

TestInspectorMonoEditor

using UnityEditor;
using UnityEngine;

//通过这个特性,我们就可以为TestInspectorMono脚本自定义Inspector窗口中的显示了
[CustomEditor(typeof(TestInspectorMono))]
public class TestInspectorMonoEditor : Editor
{
    #region Lesson23_Inspector窗口拓展_基础知识

    private SerializedProperty atk;
    private SerializedProperty def;
    private SerializedProperty obj;

    private bool foldOut;

    #endregion

    #region Lesson24_Inspector窗口拓展_数组和List

    private SerializedProperty strs;
    private SerializedProperty ints;
    private SerializedProperty gameObjects;

    private SerializedProperty listObjs;

    private int count;

    private bool arrayAndListFoldOut;

    #endregion

    #region Lesson25_Inspector窗口拓展_自定义数据

    private SerializedProperty myCustom;

    private SerializedProperty myCustomI;
    private SerializedProperty myCustomF;

    #endregion

    #region Lesson26_Inspector窗口拓展_字典

    private SerializedProperty keys;
    private SerializedProperty values;

    private int dicCount;

    #endregion

    private void OnEnable()
    {
        #region Lesson23_Inspector窗口拓展_基础知识

        //这样就得到与测试脚本对应的字段
        atk = serializedObject.FindProperty("atk");
        def = serializedObject.FindProperty("def");
        obj = serializedObject.FindProperty("obj");

        #endregion

        #region Lesson24_Inspector窗口拓展_数组和List

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

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

        #endregion

        #region Lesson25_Inspector窗口拓展_自定义数据

        myCustom = serializedObject.FindProperty("myCustom");

        //以下两种方式二选其一
        //myCustomI = myCustom.FindPropertyRelative("i");
        //myCustomF = myCustom.FindPropertyRelative("f");
        myCustomI = serializedObject.FindProperty("myCustom.i");
        myCustomF = serializedObject.FindProperty("myCustom.f");

        #endregion

        #region Lesson26_Inspector窗口拓展_字典

        keys = serializedObject.FindProperty("keys");
        values = serializedObject.FindProperty("values");

        dicCount = keys.arraySize;

        #endregion
    }


    public override void OnInspectorGUI()
    {
        //base.OnInspectorGUI();//注释掉父类调用后,Inspector窗口默认显示的atk def obj会消失

        serializedObject.Update();

        #region Lesson23_Inspector窗口拓展_基础知识

        foldOut = EditorGUILayout.BeginFoldoutHeaderGroup(foldOut, "基础属性");
        if (foldOut)
        {
            GUILayout.Button("测试自定义Inspector窗口");
            EditorGUILayout.IntSlider(atk, 0, 100, "攻击力");
            def.floatValue = EditorGUILayout.FloatField("防御力", def.floatValue);
            EditorGUILayout.ObjectField(obj, new GUIContent("敌对对象"));

            if (GUILayout.Button("打印当前target对象"))
            {
                Debug.Log("组件类型" + target.GetType());
                Debug.Log("组件依附的游戏对象名" + target.name);
            }
        }

        EditorGUILayout.EndFoldoutHeaderGroup();

        #endregion

        #region Lesson24_Inspector窗口拓展_数组和List

        EditorGUILayout.PropertyField(strs, new GUIContent("字符串数组"));
        EditorGUILayout.PropertyField(ints, new GUIContent("整形数组"));
        EditorGUILayout.PropertyField(gameObjects, new GUIContent("游戏对象数组"));
        // EditorGUILayout.PropertyField(listObjs, new GUIContent("游戏对象List"));


        arrayAndListFoldOut = EditorGUILayout.BeginFoldoutHeaderGroup(arrayAndListFoldOut, "数组和List属性");
        if (arrayAndListFoldOut)
        {
            //容量设置
            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}"));
            }
        }

        EditorGUILayout.EndFoldoutHeaderGroup();

        #endregion

        #region Lesson25_Inspector窗口拓展_自定义数据

        EditorGUILayout.PropertyField(myCustom, new GUIContent("我的自定义属性"));

        myCustomI.intValue = EditorGUILayout.IntField("自定义属性中的I", myCustomI.intValue);
        myCustomF.floatValue = EditorGUILayout.FloatField("自定义属性中的F", myCustomF.floatValue);

        #endregion

        #region Lesson26_Inspector窗口拓展_字典

        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();

        #endregion
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏