我曾经在一个名为的东西上提供了一些帮助,SerializableDictionary
这对您的用例非常有帮助。
那时我创建了一个修改版本并将其命名为EnumDictionary
. 这个只是序列化一个字典,并确保给定的每个值只enum
存在一次。(您只需将其放在项目中的任何位置)
using System;
using System.Collections;
using System.Runtime.Serialization;
using System.Reflection;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public abstract class EnumDictionaryBase
{
public abstract class Storage
{
}
protected sealed class Dictionary<TKey, TValue> : System.Collections.Generic.Dictionary<TKey, TValue>
{
public Dictionary()
{
}
public Dictionary(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
[Serializable]
public abstract class EnumDictionary<TKey, TValue> : EnumDictionaryBase, IDictionary<TKey, TValue>, IDictionary, ISerializationCallbackReceiver, IDeserializationCallback, ISerializable where TKey : Enum
{
private readonly Dictionary<TKey, TValue> _internalDictionary;
[SerializeField] private List<TKey> keys;
[SerializeField] private List<TValue> values;
protected EnumDictionary()
{
_internalDictionary = new Dictionary<TKey, TValue>();
foreach (var value in (TKey[])Enum.GetValues(typeof(TKey)))
{
_internalDictionary.Add(value, default);
}
}
private TValue GetValue(IReadOnlyList<TValue> storage, int i)
{
return storage[i];
}
public void OnAfterDeserialize()
{
if (keys == null || values == null || keys.Count != values.Count) return;
_internalDictionary.Clear();
var n = keys.Count;
for (var i = 0; i < n; ++i)
{
_internalDictionary[keys[i]] = GetValue(values, i);
}
keys = null;
values = null;
}
public void OnBeforeSerialize()
{
keys = new List<TKey>();
values = new List<TValue>();
foreach (var key in (TKey[])Enum.GetValues(typeof(TKey)))
{
keys.Add(key);
values.Add(_internalDictionary.ContainsKey(key) ? _internalDictionary[key] : default);
}
}
#region IDictionary<TKey, TValue>
public ICollection<TKey> Keys => ((IDictionary<TKey, TValue>)_internalDictionary).Keys;
public ICollection<TValue> Values => ((IDictionary<TKey, TValue>)_internalDictionary).Values;
public int Count => _internalDictionary.Count;
public bool IsReadOnly => ((IDictionary<TKey, TValue>)_internalDictionary).IsReadOnly;
public TValue this[TKey key]
{
get => _internalDictionary[key];
set => _internalDictionary[key] = value;
}
public void Add(TKey key, TValue value)
{
_internalDictionary.Add(key, value);
}
public bool ContainsKey(TKey key)
{
return _internalDictionary.ContainsKey(key);
}
public bool Remove(TKey key)
{
return _internalDictionary.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value)
{
return _internalDictionary.TryGetValue(key, out value);
}
public void Add(KeyValuePair<TKey, TValue> item)
{
_internalDictionary.Add(item.Key, item.Value);
}
public void Clear()
{
_internalDictionary.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return ((IDictionary<TKey, TValue>)_internalDictionary).Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
((IDictionary<TKey, TValue>)_internalDictionary).CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return ((IDictionary<TKey, TValue>)_internalDictionary).Remove(item);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return ((IDictionary<TKey, TValue>)_internalDictionary).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IDictionary<TKey, TValue>)_internalDictionary).GetEnumerator();
}
#endregion
#region IDictionary
public bool IsFixedSize => ((IDictionary)_internalDictionary).IsFixedSize;
ICollection IDictionary.Keys => ((IDictionary)_internalDictionary).Keys;
ICollection IDictionary.Values => ((IDictionary)_internalDictionary).Values;
public bool IsSynchronized => ((IDictionary)_internalDictionary).IsSynchronized;
public object SyncRoot => ((IDictionary)_internalDictionary).SyncRoot;
public object this[object key]
{
get => ((IDictionary)_internalDictionary)[key];
set => ((IDictionary)_internalDictionary)[key] = value;
}
public void Add(object key, object value)
{
((IDictionary)_internalDictionary).Add(key, value);
}
public bool Contains(object key)
{
return ((IDictionary)_internalDictionary).Contains(key);
}
IDictionaryEnumerator IDictionary.GetEnumerator()
{
return ((IDictionary)_internalDictionary).GetEnumerator();
}
public void Remove(object key)
{
((IDictionary)_internalDictionary).Remove(key);
}
public void CopyTo(Array array, int index)
{
((IDictionary)_internalDictionary).CopyTo(array, index);
}
#endregion
#region IDeserializationCallback
public void OnDeserialization(object sender)
{
_internalDictionary.OnDeserialization(sender);
}
#endregion
#region ISerializable
protected EnumDictionary(SerializationInfo info, StreamingContext context)
{
_internalDictionary = new Dictionary<TKey, TValue>(info, context);
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
_internalDictionary.GetObjectData(info, context);
}
#endregion
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(EnumDictionaryBase), true)]
public class EnumDictionaryDrawer : PropertyDrawer
{
private const string KEYS_FIELD_NAME = "keys";
private const string VALUES_FIELD_NAME = "values";
private const float INDENT_WIDTH = 15f;
private static readonly GUIContent STempContent = new GUIContent();
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
label = EditorGUI.BeginProperty(position, label, property);
var keyArrayProperty = property.FindPropertyRelative(KEYS_FIELD_NAME);
var valueArrayProperty = property.FindPropertyRelative(VALUES_FIELD_NAME);
var labelPosition = position;
labelPosition.height = EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(labelPosition, property, label, false);
if (property.isExpanded)
{
EditorGUI.indentLevel++;
var linePosition = position;
linePosition.y += EditorGUIUtility.singleLineHeight;
foreach (var entry in EnumerateEntries(keyArrayProperty, valueArrayProperty))
{
var keyProperty = entry.KeyProperty;
var valueProperty = entry.ValueProperty;
var i = entry.Index;
var lineHeight = DrawKeyValueLine(keyProperty, valueProperty, linePosition, i);
linePosition.y += lineHeight;
}
EditorGUI.indentLevel--;
}
foreach (var entry1 in EnumerateEntries(keyArrayProperty, valueArrayProperty))
{
var keyProperty1 = entry1.KeyProperty;
var i = entry1.Index;
var keyProperty1Value = GetPropertyValue(keyProperty1);
if (keyProperty1Value == null)
{
var valueProperty1 = entry1.ValueProperty;
SaveProperty(keyProperty1, valueProperty1, i, -1);
DeleteArrayElementAtIndex(keyArrayProperty, i);
if (valueArrayProperty != null) DeleteArrayElementAtIndex(valueArrayProperty, i);
break;
}
foreach (var entry2 in EnumerateEntries(keyArrayProperty, valueArrayProperty, i + 1))
{
var keyProperty2 = entry2.KeyProperty;
var j = entry2.Index;
var keyProperty2Value = GetPropertyValue(keyProperty2);
if (ComparePropertyValues(keyProperty1Value, keyProperty2Value))
{
var valueProperty2 = entry2.ValueProperty;
SaveProperty(keyProperty2, valueProperty2, j, i);
DeleteArrayElementAtIndex(keyArrayProperty, j);
if (valueArrayProperty != null) DeleteArrayElementAtIndex(valueArrayProperty, j);
goto breakLoops;
}
}
}
breakLoops:
EditorGUI.EndProperty();
}
private static float DrawKeyValueLine(SerializedProperty keyProperty, SerializedProperty valueProperty, Rect linePosition, int index)
{
var keyCanBeExpanded = CanPropertyBeExpanded(keyProperty);
if (valueProperty != null)
{
var valueCanBeExpanded = CanPropertyBeExpanded(valueProperty);
if (!keyCanBeExpanded && valueCanBeExpanded)
{
return DrawKeyValueLineExpand(index, keyProperty, valueProperty, linePosition);
}
var keyLabel = keyCanBeExpanded ? ("Key " + index) : "";
var valueLabel = valueCanBeExpanded ? ("Value " + index) : "";
return DrawKeyValueLineSimple(index, keyProperty, valueProperty, keyLabel, valueLabel, linePosition);
}
return DrawKeyLine(index, keyProperty, linePosition, null);
}
private static float DrawKeyValueLineSimple(int keyIndex, SerializedProperty keyProperty, SerializedProperty valueProperty, string keyLabel, string valueLabel, Rect linePosition)
{
var labelWidth = EditorGUIUtility.labelWidth;
var labelWidthRelative = labelWidth / linePosition.width;
var keyPropertyHeight = EditorGUI.GetPropertyHeight(keyProperty);
var keyPosition = linePosition;
keyPosition.height = keyPropertyHeight;
keyPosition.width = labelWidth - INDENT_WIDTH;
EditorGUIUtility.labelWidth = keyPosition.width * labelWidthRelative;
EditorGUI.LabelField(keyPosition, keyProperty.enumDisplayNames[keyIndex], EditorStyles.layerMaskField);
var valuePropertyHeight = EditorGUI.GetPropertyHeight(valueProperty);
var valuePosition = linePosition;
valuePosition.height = valuePropertyHeight;
valuePosition.xMin += labelWidth;
EditorGUIUtility.labelWidth = valuePosition.width * labelWidthRelative;
EditorGUI.indentLevel--;
EditorGUI.PropertyField(valuePosition, valueProperty, TempContent(valueLabel), true);
EditorGUI.indentLevel++;
EditorGUIUtility.labelWidth = labelWidth;
return Mathf.Max(keyPropertyHeight, valuePropertyHeight);
}
private static float DrawKeyValueLineExpand(int keyIndex, SerializedProperty keyProperty, SerializedProperty valueProperty, Rect linePosition)
{
var labelWidth = EditorGUIUtility.labelWidth;
var keyPropertyHeight = EditorGUI.GetPropertyHeight(keyProperty);
var keyPosition = linePosition;
keyPosition.height = keyPropertyHeight;
keyPosition.width = labelWidth - INDENT_WIDTH;
EditorGUI.LabelField(keyPosition, keyProperty.enumDisplayNames[keyIndex]);
var valuePropertyHeight = EditorGUI.GetPropertyHeight(valueProperty);
var valuePosition = linePosition;
valuePosition.height = valuePropertyHeight;
EditorGUI.PropertyField(valuePosition, valueProperty, GUIContent.none, true);
EditorGUIUtility.labelWidth = labelWidth;
return Mathf.Max(keyPropertyHeight, valuePropertyHeight);
}
private static float DrawKeyLine(int keyIndex, SerializedProperty keyProperty, Rect linePosition, string keyLabel)
{
var keyPropertyHeight = EditorGUI.GetPropertyHeight(keyProperty);
var keyPosition = linePosition;
keyPosition.height = keyPropertyHeight;
keyPosition.width = linePosition.width;
var keyLabelContent = keyLabel != null ? TempContent(keyLabel) : GUIContent.none;
EditorGUI.LabelField(keyPosition, keyProperty.enumDisplayNames[keyIndex]);
return keyPropertyHeight;
}
private static bool CanPropertyBeExpanded(SerializedProperty property)
{
switch (property.propertyType)
{
case SerializedPropertyType.Generic:
case SerializedPropertyType.Vector4:
case SerializedPropertyType.Quaternion:
return true;
default:
return false;
}
}
private static void SaveProperty(SerializedProperty keyProperty, SerializedProperty valueProperty, int index, int otherIndex)
{
var keyPropertyHeight = EditorGUI.GetPropertyHeight(keyProperty);
var valuePropertyHeight = valueProperty != null ? EditorGUI.GetPropertyHeight(valueProperty) : 0f;
var lineHeight = Mathf.Max(keyPropertyHeight, valuePropertyHeight);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var propertyHeight = EditorGUIUtility.singleLineHeight;
if (property.isExpanded)
{
var keysProperty = property.FindPropertyRelative(KEYS_FIELD_NAME);
var valuesProperty = property.FindPropertyRelative(VALUES_FIELD_NAME);
foreach (var entry in EnumerateEntries(keysProperty, valuesProperty))
{
var keyProperty = entry.KeyProperty;
var valueProperty = entry.ValueProperty;
var keyPropertyHeight = EditorGUI.GetPropertyHeight(keyProperty);
var valuePropertyHeight = valueProperty != null ? EditorGUI.GetPropertyHeight(valueProperty) : 0f;
var lineHeight = Mathf.Max(keyPropertyHeight, valuePropertyHeight);
propertyHeight += lineHeight;
}
}
return propertyHeight;
}
private static readonly Dictionary<SerializedPropertyType, PropertyInfo> SSerializedPropertyValueAccessorsDict;
static EnumDictionaryDrawer()
{
var serializedPropertyValueAccessorsNameDict = new Dictionary<SerializedPropertyType, string>
{
{SerializedPropertyType.Integer, "intValue"},
{SerializedPropertyType.Boolean, "boolValue"},
{SerializedPropertyType.Float, "floatValue"},
{SerializedPropertyType.String, "stringValue"},
{SerializedPropertyType.Color, "colorValue"},
{SerializedPropertyType.ObjectReference, "objectReferenceValue"},
{SerializedPropertyType.LayerMask, "intValue"},
{SerializedPropertyType.Enum, "intValue"},
{SerializedPropertyType.Vector2, "vector2Value"},
{SerializedPropertyType.Vector3, "vector3Value"},
{SerializedPropertyType.Vector4, "vector4Value"},
{SerializedPropertyType.Rect, "rectValue"},
{SerializedPropertyType.ArraySize, "intValue"},
{SerializedPropertyType.Character, "intValue"},
{SerializedPropertyType.AnimationCurve, "animationCurveValue"},
{SerializedPropertyType.Bounds, "boundsValue"},
{SerializedPropertyType.Quaternion, "quaternionValue"}
};
var serializedPropertyType = typeof(SerializedProperty);
SSerializedPropertyValueAccessorsDict = new Dictionary<SerializedPropertyType, PropertyInfo>();
var flags = BindingFlags.Instance | BindingFlags.Public;
foreach (var kvp in serializedPropertyValueAccessorsNameDict)
{
var propertyInfo = serializedPropertyType.GetProperty(kvp.Value, flags);
SSerializedPropertyValueAccessorsDict.Add(kvp.Key, propertyInfo);
}
}
private static GUIContent IconContent(string name, string tooltip)
{
var builtinIcon = EditorGUIUtility.IconContent(name);
return new GUIContent(builtinIcon.image, tooltip);
}
private static GUIContent TempContent(string text)
{
STempContent.text = text;
return STempContent;
}
private static void DeleteArrayElementAtIndex(SerializedProperty arrayProperty, int index)
{
var property = arrayProperty.GetArrayElementAtIndex(index);
if (property.propertyType == SerializedPropertyType.ObjectReference)
{
property.objectReferenceValue = null;
}
arrayProperty.DeleteArrayElementAtIndex(index);
}
public static object GetPropertyValue(SerializedProperty p)
{
if (SSerializedPropertyValueAccessorsDict.TryGetValue(p.propertyType, out var propertyInfo))
{
return propertyInfo.GetValue(p, null);
}
return p.isArray ? GetPropertyValueArray(p) : GetPropertyValueGeneric(p);
}
private static void SetPropertyValue(SerializedProperty p, object v)
{
if (SSerializedPropertyValueAccessorsDict.TryGetValue(p.propertyType, out var propertyInfo))
{
propertyInfo.SetValue(p, v, null);
}
else
{
if (p.isArray)
SetPropertyValueArray(p, v);
else
SetPropertyValueGeneric(p, v);
}
}
private static object GetPropertyValueArray(SerializedProperty property)
{
var array = new object[property.arraySize];
for (var i = 0; i < property.arraySize; i++)
{
var item = property.GetArrayElementAtIndex(i);
array[i] = GetPropertyValue(item);
}
return array;
}
private static object GetPropertyValueGeneric(SerializedProperty property)
{
var dict = new Dictionary<string, object>();
var iterator = property.Copy();
if (!iterator.Next(true)) return dict;
var end = property.GetEndProperty();
do
{
var name = iterator.name;
var value = GetPropertyValue(iterator);
dict.Add(name, value);
} while (iterator.Next(false) && iterator.propertyPath != end.propertyPath);
return dict;
}
private static void SetPropertyValueArray(SerializedProperty property, object v)
{
var array = (object[])v;
property.arraySize = array.Length;
for (var i = 0; i < property.arraySize; i++)
{
var item = property.GetArrayElementAtIndex(i);
SetPropertyValue(item, array[i]);
}
}
private static void SetPropertyValueGeneric(SerializedProperty property, object v)
{
var dict = (Dictionary<string, object>)v;
var iterator = property.Copy();
if (iterator.Next(true))
{
var end = property.GetEndProperty();
do
{
var name = iterator.name;
SetPropertyValue(iterator, dict[name]);
} while (iterator.Next(false) && iterator.propertyPath != end.propertyPath);
}
}
private static bool ComparePropertyValues(object value1, object value2)
{
if (value1 is Dictionary<string, object> dict1 && value2 is Dictionary<string, object> dict2)
{
return CompareDictionaries(dict1, dict2);
}
return Equals(value1, value2);
}
private static bool CompareDictionaries(Dictionary<string, object> dict1, Dictionary<string, object> dict2)
{
if (dict1.Count != dict2.Count) return false;
foreach (var kvp1 in dict1)
{
var key1 = kvp1.Key;
var value1 = kvp1.Value;
if (!dict2.TryGetValue(key1, out var value2)) return false;
if (!ComparePropertyValues(value1, value2)) return false;
}
return true;
}
private struct EnumerationEntry
{
public readonly SerializedProperty KeyProperty;
public readonly SerializedProperty ValueProperty;
public readonly int Index;
public EnumerationEntry(SerializedProperty keyProperty, SerializedProperty valueProperty, int index)
{
KeyProperty = keyProperty;
ValueProperty = valueProperty;
Index = index;
}
}
private static IEnumerable<EnumerationEntry> EnumerateEntries(SerializedProperty keyArrayProperty, SerializedProperty valueArrayProperty, int startIndex = 0)
{
if (keyArrayProperty.arraySize > startIndex)
{
var index = startIndex;
var keyProperty = keyArrayProperty.GetArrayElementAtIndex(startIndex);
var valueProperty = valueArrayProperty?.GetArrayElementAtIndex(startIndex);
var endProperty = keyArrayProperty.GetEndProperty();
do
{
yield return new EnumerationEntry(keyProperty, valueProperty, index);
index++;
} while (keyProperty.Next(false) && (valueProperty?.Next(false) ?? true) && !SerializedProperty.EqualContents(keyProperty, endProperty));
}
}
}
[CustomPropertyDrawer(typeof(EnumDictionaryBase.Storage), true)]
public class SerializableDictionaryStoragePropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
property.Next(true);
EditorGUI.PropertyField(position, property, label, true);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
property.Next(true);
return EditorGUI.GetPropertyHeight(property);
}
}
#endif
然后,您可以根据枚举值配置这些项目中的每一个。
所以呢?-> 很好地使用那个 EnumDictionary 我会像这样改变你的代码:
public enum ItemDataType
{
Consumables,
Weapons,
Armors,
....
}
[Serializable]
public class ItemDataArray
{
public ItemData[] Elements;
}
[Serializable]
public class ItemDataDictionary : EnumDictionary<ItemDataType, ItemDataArray>{ }
接着
public class ItemDB : ScriptableObject
{
public ItemDataDictionary items;
}
然后与通常的字典一样(实际上在内部将EnumDictionary
存储的值反序列化为 normal Dictionary
)您可以通过例如使用来访问每个项目
var weapons = itemDBAsset.items.Elements[ItemDataType.Weapons];
或使用
foreach(var kvp in itemDBAsset.items)
{
var enumKey = kvp.Key;
var itemDatas = kvp.Value.Elements;
...
}
所以你会做
private void ProcessItemDB()
{
foreach (var kvp in itemDBAsset.items)
{
var itemDatas = kvp.Value.Elements;
foreach (var itemData in itemDatas)
{
itemDB.Add(itemData.icon.name, itemData);
}
}
}