Unity serializes those things and supports Dictionary serialization

Table of contents

1. Serialization of ordinary types and UnityEngine spatial types

2. Serialization of arrays and lists

3. Serialization support for custom classes

 4. Custom asset

5. Support Dictionary serialization in the inspector panel

1. Implement Dictionary serialization in MonoBehaviour

 2. Customize the property so that it can be displayed in inpsector

3. Test of Dictionary dictionary in MonoBehaviour script

 4. The script in the asset supports Dictionary

1) Download the OdinSerializer serialization plugin

2) Define the serialization class


        The inspector panel in unity supports list, but sometimes we need Dictionary, especially when we need to implement serialization through asset resources and ScriptableObject scripts. For example, if a skill needs to use an id to determine access to a single skill data, it must satisfy the data structure of key and value.

Since unity does not natively support the serialization of dictionaries, this article briefly introduces the methods of unity serialization and serialization of custom classes, and at the same time realizes the serialization of dictionaries in the inspector panel and the implementation of sequences in asset assets Dictionary method. In practice, we can use the Serialize plug-in of OdinSerializer to achieve the effect we want.

1. Serialization of ordinary types and UnityEngine spatial types

For ordinary MonoBehaviour objects, the commonly used public data types support direct serialization. If it is an object under namespace UnityEngine, it can be serialized

Under the inspector panel, we see that if it is a basic type and a type under the UnityEngine namespace, it can be directly serialized.


 

2. Serialization of arrays and lists

In the actual process, we often need array or list serialization. Unity can serialize containers that can display specified types. Unity does not provide direct support for the Array container because it does not know the data type it defines. Of course, Dictionary cannot directly support it.

Define a list<int> linked list and the original value of int[]

inspector panel value 


3. Serialization support for custom classes

Sometimes we are not satisfied with the serialization of only basic types. What if we serialize custom classes? At this time, two

attribute. When defining a class, Serializable and SerializeField need to be used together. Add the Serializable attribute when defining a class. Serializable is the function class. SerializeField is required when defining class member variables. SerializeField is the function field.

Define the serialization class

see panel value 

 When we talked about list and [] linked list and array types earlier, the basic type is directly supported, and the definition of class attributes through Serializable does not require SerializeField

As shown in the picture:

Define a custom class container

The display value of the ipspector panel 

 


 4. Custom asset

We don't want to rely on the MonoBehaviour class for serialization, but just want to be pure data resources, such as skill data or role data. Unity provides us with an implementation method through the ScriptableObject class. CreateAssetMenu provides us with a property to create a menu, menuName is the menu name, fileName is the file name

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

Create an asset relative to the SkillData class under config /skill data .

The operation steps are as follows: Find the directory you want in the Assets directory, right-click, and the following menu will pop up:

Create the SkillData.asset resource


5. Support Dictionary serialization in the inspector panel

        The overall design idea is to realize the support for Dictionary through the list. Since the inpsector panel of Unity does not directly support the dictionary, we also need to customize the Property, which is realized through the CustomPropertyDrawer property.

1. Implement Dictionary serialization in MonoBehaviour

Define the SerializableDictionary class, inherit the IDictionary interface, and rewrite the IDictionary interface function

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. Customize the property so that it can be displayed in inpsector

Create the file SerializableDictionaryDrawer in the Editor directory and use the CustomPropertyDrawer property to create it

Customize the display content of Inpsector

code show as below:

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. Test of Dictionary dictionary in MonoBehaviour script

 

 4. The script in the asset supports Dictionary

If we want to also support Dictionary for asset asset files, I will implement it through the OdinSerializer plug-in here, and share a free OdinSerializer plug-in here.

1) Download the OdinSerializer serialization plugin

                DownloadOdinSerializer

 

 

The network is a bit slow, please be patient.

Note: The namespace needs to be defined by yourself, otherwise it will use the default Sirenix namespace of the plug-in, and an error will be reported.

2) Define the serialization class

This class inherits the SerializedScriptableObject class in the OdinSerializer plugin

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

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

 }

 At this point, all the content of this article is completed

Guess you like

Origin blog.csdn.net/lejian/article/details/130058297