目录
一、普通类型和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>();
}
至此,该篇所有内容完成