unity 序列化那些事,支持Dictionary序列化

目录

一、普通类型和UnityEngine空间类型序列化

二、数组、list的序列化

三、自定义类的序列化支持

 四、自定义asset

五、在inspector面板中支持Dictionary序列化

1、在MonoBehaviour中实现Dictionary序列化

 2、自定义property,让其在inpsector能够显示

3、MonoBehaviour脚本中Dictionary字典的测试

 4、asset中脚本对字典Dictionary的支持

1)下载OdinSerializer 序列化插件

2)定义序列化类


        unity中的inspector面板支持list,但是有时候我们需要Dictionary,尤其是我们需要通过asset资源与ScriptableObject脚本一起实现序列化时更是需要如此。如:技能需要通过id来确定访问单个技能数据,那必须满足key和Value的数据结构。

由于unity并不是原生的支持对字典的序列化,这件简述了unity关于序列化与及自定义类的序列化的方法,同时实现在inspector面板中字典序列化问题,以及asset资产中实现序列化 Dictionary的方法。在实际运用中我们可以通过OdinSerializer 的的Serialize插件配合使用来实现我们我想要的效果。

一、普通类型和UnityEngine空间类型序列化

对于普通的MonoBehaviour对象常用public数据类型是支持直接序列化的,如果是namespace UnityEngine下的对象都可以被序列化

unity 序列化那些事,支持Dictionary序列化_第1张图片

在inspector面板下,我们看到如果是基础类型和UnityEngine命名空间下类型,可以直接序列化。

unity 序列化那些事,支持Dictionary序列化_第2张图片


 

二、数组、list的序列化

在实际过程中我们往往需要数组或列表序列化。unity对与能显示指定类型的容器可以序列化。对于Array容器因为其不知道它所定义的数据类型unity并没有提供直接支持,当然字典Dictionary无法直接支持。

定义一个list链表和int[]的原始数值

inspector面板值 

unity 序列化那些事,支持Dictionary序列化_第3张图片


三、自定义类的序列化支持

我们有时并不满足只有基础类型的序列化,如果对自定义类进行序列化呢?这时候需要用到两个

attribute。在定义类的时候,需要Serializable和SerializeField配合使用。在定义类的时候添加Serializable属性,Serializable是作用类在,定义类成员变量时需要SerializeField,,SerializeField是作用字段

定义序列化类

unity 序列化那些事,支持Dictionary序列化_第4张图片

看下面板值 

unity 序列化那些事,支持Dictionary序列化_第5张图片

 我们前面讲过list和[]链表和数组类型的时候,基础类型是直接支持的,通过Serializable对类属性定义,并不需要SerializeField

如图:

定义自定义类容器

ipspector面板的显示值 

 unity 序列化那些事,支持Dictionary序列化_第6张图片


 四、自定义asset

我们想不依赖MonoBehaviour类做序列化,只是想做纯数据的的资源,比如技能数据或这角色数据等。unity通过ScriptableObject该类来我们提供了实现的方法。CreateAssetMenu提供给我们一个创建菜单的属性,menuName是菜单名,fileName是文件名称

using UnityEngine;
using System;

[CreateAssetMenu(menuName = "Config/" + "技能数据", fileName = nameof(SkillData))]
public class SkillData : ScriptableObject
{

    public int id;
    public string name;

    public Effect effect;

}

[Serializable]
public class Effect
{
    public int type;
    public float beginTime;
    public float durationTime;
}

config/技能数据 下创建相对与SkillData类的asset.

操作步骤如下:Assets目录下找到你想要目录,点击右键,会弹出如下菜单:

unity 序列化那些事,支持Dictionary序列化_第7张图片

创建出SkillData.asset资源

unity 序列化那些事,支持Dictionary序列化_第8张图片


五、在inspector面板中支持Dictionary序列化

        总体设计思路是通过list实现对Dictionary的支持,由于Unity的inpsector面板中并不直接支持字典,所以我们还需要自定义Property,通过CustomPropertyDrawer属性来实现

1、在MonoBehaviour中实现Dictionary序列化

定义SerializableDictionary类,并继承IDictionary接口,重写该IDictionary接口函数

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

public class SerializableDictionary { }

[Serializable]
public class SerializableDictionary :
    SerializableDictionary,
    ISerializationCallbackReceiver,
    IDictionary
{
    [SerializeField] private List list = new List();

    [Serializable]
    private struct SerializableKeyValuePair
    {
        public TKey Key;
        public TValue Value;

        public SerializableKeyValuePair(TKey key, TValue value)
        {
            Key = key;
            Value = value;
        }
    }

    private Dictionary KeyPositions => _keyPositions.Value;
    private Lazy> _keyPositions;

    public SerializableDictionary()
    {
        _keyPositions = new Lazy>(MakeKeyPositions);
    }

    private Dictionary MakeKeyPositions()
    {
        var dictionary = new Dictionary(list.Count);
        for (var i = 0; i < list.Count; i++)
        {
            dictionary[list[i].Key] = i;
        }
        return dictionary;
    }

    public void OnBeforeSerialize() { }

    public void OnAfterDeserialize()
    {
        _keyPositions = new Lazy>(MakeKeyPositions);
    }

    #region IDictionary

    public TValue this[TKey key]
    {
        get => list[KeyPositions[key]].Value;
        set
        {
            var pair = new SerializableKeyValuePair(key, value);
            if (KeyPositions.ContainsKey(key))
            {
                list[KeyPositions[key]] = pair;
            }
            else
            {
                KeyPositions[key] = list.Count;
                list.Add(pair);
            }
        }
    }

    public ICollection Keys => list.Select(tuple => tuple.Key).ToArray();
    public ICollection Values => list.Select(tuple => tuple.Value).ToArray();

    public void Add(TKey key, TValue value)
    {
        if (KeyPositions.ContainsKey(key))
            throw new ArgumentException("An element with the same key already exists in the dictionary.");
        else
        {
            KeyPositions[key] = list.Count;
            list.Add(new SerializableKeyValuePair(key, value));
        }
    }

    public bool ContainsKey(TKey key) => KeyPositions.ContainsKey(key);

    public bool Remove(TKey key)
    {
        if (KeyPositions.TryGetValue(key, out var index))
        {
            KeyPositions.Remove(key);

            list.RemoveAt(index);
            for (var i = index; i < list.Count; i++)
                KeyPositions[list[i].Key] = i;

            return true;
        }
        else
            return false;
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        if (KeyPositions.TryGetValue(key, out var index))
        {
            value = list[index].Value;
            return true;
        }
        else
        {
            value = default;
            return false;
        }
    }

    #endregion

    #region ICollection >

    public int Count => list.Count;
    public bool IsReadOnly => false;

    public void Add(KeyValuePair kvp) => Add(kvp.Key, kvp.Value);

    public void Clear() => list.Clear();
    public bool Contains(KeyValuePair kvp) => KeyPositions.ContainsKey(kvp.Key);

    public void CopyTo(KeyValuePair[] array, int arrayIndex)
    {
        var numKeys = list.Count;
        if (array.Length - arrayIndex < numKeys)
            throw new ArgumentException("arrayIndex");
        for (var i = 0; i < numKeys; i++, arrayIndex++)
        {
            var entry = list[i];
            array[arrayIndex] = new KeyValuePair(entry.Key, entry.Value);
        }
    }

    public bool Remove(KeyValuePair kvp) => Remove(kvp.Key);

    #endregion

    #region IEnumerable >

    public IEnumerator> GetEnumerator()
    {
        return list.Select(ToKeyValuePair).GetEnumerator();


    }
    static KeyValuePair ToKeyValuePair(SerializableKeyValuePair skvp)
    {
        return new KeyValuePair(skvp.Key, skvp.Value);
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    #endregion
}

 2、自定义property,让其在inpsector能够显示

在Editor目录下创建文件SerializableDictionaryDrawer,利用CustomPropertyDrawer属性创建

自定义Inpsector的显示内容

代码如下:

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(SerializableDictionary), true)]
public class SerializableDictionaryDrawer : PropertyDrawer
{
    private SerializedProperty listProperty;

    private SerializedProperty getListProperty(SerializedProperty property)
    {
        if (listProperty == null)
            listProperty = property.FindPropertyRelative("list");

        return listProperty;

    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PropertyField(position, getListProperty(property), label, true);
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return EditorGUI.GetPropertyHeight(getListProperty(property), true);
    }
}

3、MonoBehaviour脚本中Dictionary字典的测试

 unity 序列化那些事,支持Dictionary序列化_第9张图片

 4、asset中脚本对字典Dictionary的支持

如果我们想对asset资产文件同样对Dictionary支持,我这里通过OdinSerializer插件来实现,这里分享一个免费OdinSerializer插件。

1)下载OdinSerializer 序列化插件

                下载OdinSerializer

 unity 序列化那些事,支持Dictionary序列化_第10张图片

 

网络有点慢,耐心等待。

注意:命名空间需要自己定义,不然它会使用插件默认的 Sirenix命名空间,会报错。

2)定义序列化类

该类继承OdinSerializer插件中SerializedScriptableObject类

//创建测试Asset数据菜单
[CreateAssetMenu(menuName = "Config/" + "测试Asset数据", fileName = nameof(TestAssetData ))]

 public class TestAssetData : SerializedScriptableObject
 {
    public SerializableDictionary data = new SerializableDictionary();

 }

 至此,该篇所有内容完成

你可能感兴趣的:(unity,工作笔记,unity,游戏引擎)