关于Unity中Inspector序列化显示Dictionary的方法
字典作为最重要的数据结构之一,在Unity的inspector中竟然无法序列化显示。通过Google搜索,我找到了几种方法。[*]使用List模拟字典的键值对,在Awake或Start方法中将键值复制到字典中。
原文链接:https://forum.unity.com/threads/adding-public-dictionary-as-parameter.692701/
缺点:每使用一个字典都需要写一段foreach;只能在开始时初始化字典,无法在运行中和字典绑定。
2. 使用两个List分别保存字典的键和值,通过继承ISerializationCallbackReceiver接口定义序列化行为。
原文链接:Serializing Dictionaries | Odin Inspector for Unity
缺点:每使用一个类型的字典都需要新建一个类,不够方便。
3. 使用Odin Serializer
原文链接:Odin Serializer Quick Start | Odin Inspector for Unity
缺点:需要继承SerializedMonoBehaviour代替MonoBehaviour,侵入性的接口。
4. 使用List模拟字典的键值对,内部使用一个字典存储键的索引,在运行时更改字典可将修改反映到List(Inspector)上,并使用ReorderableList和PropertyDrawer自定义绘制方法。
原文链接:SerializableDictionary - Unify Community Wiki
缺点:只能单行显示,无法用于复杂数据结构。
<hr/>方法4应该可以满足大多数人的需求,但没有满足我的需求。
我的需求如下:
[*]Dictionary的键和值可以是复杂的可序列化数据结构
[*]在inspector中拥有类似List的GUI(如添加和删除按钮)
[*]使用时编码简单,接口非侵入性
因此我只要在方法4的基础上改动自定义绘制方法即可。
复杂数据结构含有嵌套,GUI上需要是可伸缩的,而原文使用的ReorderableList的行高是固定的,不同元素的高度不一样的话会有显示问题,所以我只能弃用它。而Unity原生的List可以有动态的行高,那我直接把自定义绘制方法去掉,并用上复杂数据结构试试。
public class Example : MonoBehaviour {
public SerializableDictionary<string, VelocityState> velocityStates;
public SerializableDictionary<string, List<GameObject>> gameObjects;
public SerializableDictionary<string, SerializableDictionary<string, Color>> colors;
}
(其中VelocityState是我自定义的一个结构体)
去掉原文的自定义绘制方法可以直接用于复杂数据结构!
但是有一个问题,会多一层List嵌套。
通过简单的自定义绘制方法可以解决它。解决后效果如图:
大功告成!完结撒花~
感谢方法4原文作者给予我代码上的启发。
<hr/>附录:
SerializableDictionary.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
public class SerializableDictionary { }
public class SerializableDictionary<TKey, TValue> :
SerializableDictionary,
ISerializationCallbackReceiver,
IDictionary<TKey, TValue> {
private List<SerializableKeyValuePair> list = new List<SerializableKeyValuePair>();
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.Key] = i;
}
return dictionary;
}
public void OnBeforeSerialize() { }
public void OnAfterDeserialize() {
_keyPositions = new Lazy<Dictionary<TKey, int>>(MakeKeyPositions);
}
#region IDictionary<TKey, TValue>
public TValue this {
get => list].Value;
set {
var pair = new SerializableKeyValuePair(key, value);
if (KeyPositions.ContainsKey(key)) {
list] = pair;
}
else {
KeyPositions = 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(&#34;An element with the same key already exists in the dictionary.&#34;);
else {
KeyPositions = 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.Key] = i;
return true;
}
else
return false;
}
public bool TryGetValue(TKey key, out TValue value) {
if (KeyPositions.TryGetValue(key, out var index)) {
value = list.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(&#34;arrayIndex&#34;);
for (var i = 0; i < numKeys; i++, arrayIndex++) {
var entry = list;
array = 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
}
public class SerializableDictionaryDrawer : PropertyDrawer {
private SerializedProperty listProperty;
private SerializedProperty getListProperty(SerializedProperty property) =>
listProperty ??= property.FindPropertyRelative(&#34;list&#34;);
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);
}
} unity2019.4.x测试无法使用 我是在2020.3.14上使用的,老版本可能用不了 嗯,我改出了一个2019的版本
页:
[1]