0

该脚本有效,但相当乏味。因为我必须为 itemDB 中的每个 ItemData[] 变量手动编写一个 for 循环。

问题:有没有更简单的方法来获取 ItemDB ScriptableObject 中的所有变量?

public class InventoryManager : MonoBehaviour
{
    public Dictionary<string, ItemData> itemDB = new Dictionary<string, ItemData>();
    public  ItemDB itemDBAsset;

    // (NOTE: this is the annoying part as its not scalable)
    private void ProcessItemDB()
    {    
    for (int i = 0; i < itemDBAsset.consumables.Length; i++)
        itemDB.Add(itemDBAsset.consumables[i].icon.name, itemDBAsset.consumables[i]);
    for (int i = 0; i < itemDBAsset.weapons.Length; i++)
        itemDB.Add(itemDBAsset.weapons[i].icon.name, itemDBAsset.weapons[i]);
    for (int i = 0; i < itemDBAsset.armors.Length; i++)
        itemDB.Add(itemDBAsset.armors[i].icon.name, itemDBAsset.armors[i]);
    for (int i = 0; i < itemDBAsset.accessories.Length; i++)
        itemDB.Add(itemDBAsset.accessories[i].icon.name, itemDBAsset.accessories[i]);
    for (int i = 0; i < itemDBAsset.materials.Length; i++)
        itemDB.Add(itemDBAsset.materials[i].icon.name, itemDBAsset.materials[i]);
    for (int i = 0; i < itemDBAsset.materials.Length; i++)
        itemDB.Add(itemDBAsset.craftingScrolls;[i].icon.name, itemDBAsset.craftingScrolls;[i]);
    for (int i = 0; i < itemDBAsset.foods;.Length; i++)
        itemDB.Add(itemDBAsset.foods[i].icon.name, itemDBAsset.foods[i]);
}

.

public class ItemDB : ScriptableObject
{
    public ItemData[] consumables;
    public ItemData[] weapons;
    public ItemData[] armors;
    public ItemData[] accessories;
    public ItemData[] materials;
    public ItemData[] craftingScrolls;
    public ItemData[] foods;
}

[System.Serializable]
public class ItemData
{
    public string name;
    public Sprite icon; 
    public string description;
}
4

1 回答 1

0

我曾经在一个名为的东西上提供了一些帮助,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);
        }
    }
}
于 2021-06-10T06:58:12.787 回答