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下的对象都可以被序列化

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


 

二、数组、list的序列化

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

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

inspector面板值 


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

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

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

定义序列化类

看下面板值 

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

如图:

定义自定义类容器

ipspector面板的显示值 

 


 四、自定义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目录下找到你想要目录,点击右键,会弹出如下菜单:

创建出SkillData.asset资源


五、在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<TKey, TValue> :
    SerializableDictionary,
    ISerializationCallbackReceiver,
    IDictionary<TKey, TValue>
{
    [SerializeField] private List<SerializableKeyValuePair> list = new List<SerializableKeyValuePair>();

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

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

    private Dictionary<TKey, int> KeyPositions => _keyPositions.Value;
    private Lazy<Dictionary<TKey, int>> _keyPositions;

    public SerializableDictionary()
    {
        _keyPositions = new Lazy<Dictionary<TKey, int>>(MakeKeyPositions);
    }

    private Dictionary<TKey, int> MakeKeyPositions()
    {
        var dictionary = new Dictionary<TKey, int>(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<Dictionary<TKey, int>>(MakeKeyPositions);
    }

    #region IDictionary<TKey, TValue>

    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<TKey> Keys => list.Select(tuple => tuple.Key).ToArray();
    public ICollection<TValue> 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 <KeyValuePair<TKey, TValue>>

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

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

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

    public void CopyTo(KeyValuePair<TKey, TValue>[] 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<TKey, TValue>(entry.Key, entry.Value);
        }
    }

    public bool Remove(KeyValuePair<TKey, TValue> kvp) => Remove(kvp.Key);

    #endregion

    #region IEnumerable <KeyValuePair<TKey, TValue>>

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return list.Select(ToKeyValuePair).GetEnumerator();


    }
    static KeyValuePair<TKey, TValue> ToKeyValuePair(SerializableKeyValuePair skvp)
    {
        return new KeyValuePair<TKey, TValue>(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字典的测试

 

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

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

1)下载OdinSerializer 序列化插件

                下载OdinSerializer

 

 

网络有点慢,耐心等待。

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

2)定义序列化类

该类继承OdinSerializer插件中SerializedScriptableObject类

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

 public class TestAssetData : SerializedScriptableObject
 {
    public SerializableDictionary<int, int> data = new SerializableDictionary<int, int>();

 }

 至此,该篇所有内容完成

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity 中,我们可以使用 JsonUtility 类来实现对象的序列化和反序列化。对于 Dictionary 类型的对象,我们可以通过将其转换为一个包含键值对的 List 类型对象,然后对 List 类型对象进行序列化和反序列化。 下面是一个示例代码: ```csharp using System.Collections.Generic; using UnityEngine; [System.Serializable] public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver { [SerializeField] private List<TKey> keys = new List<TKey>(); [SerializeField] private List<TValue> values = new List<TValue>(); // save the dictionary to lists public void OnBeforeSerialize() { keys.Clear(); values.Clear(); foreach (KeyValuePair<TKey, TValue> pair in this) { keys.Add(pair.Key); values.Add(pair.Value); } } // load dictionary from lists public void OnAfterDeserialize() { this.Clear(); for (int i = 0; i < keys.Count; i++) { this.Add(keys[i], values[i]); } } } [System.Serializable] public class MyData { public SerializableDictionary<string, int> myDict = new SerializableDictionary<string, int>(); } public static class JsonHelper { public static string ToJson<T>(T obj) { return JsonUtility.ToJson(obj); } public static T FromJson<T>(string json) { return JsonUtility.FromJson<T>(json); } } public class Example : MonoBehaviour { private MyData data = new MyData(); private void Start() { data.myDict.Add("key1", 1); data.myDict.Add("key2", 2); string json = JsonHelper.ToJson(data); Debug.Log(json); MyData loadedData = JsonHelper.FromJson<MyData>(json); Debug.Log(loadedData.myDict["key1"]); Debug.Log(loadedData.myDict["key2"]); } } ``` 在上面的示例代码中,我们定义了一个 SerializableDictionary 类来实现 Dictionary序列化和反序列化。在 MyData 类中使用了 SerializableDictionary 类型的成员变量 myDict。在 JsonHelper 类中,我们定义了 ToJson 和 FromJson 方法来将对象转换为 Json 字符串和从 Json 字符串中加载对象。在 Example 类中,我们创建了一个 MyData 对象,并向其中添加了两个键值对。我们将 MyData 对象转换为 Json 字符串并输出到控制台,然后从 Json 字符串中加载了一个新的对象,并输出了其中的两个值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值