SerializableTupleCollection
From Unify Community Wiki
(Difference between revisions)
Steinbitglis (Talk | contribs) (→C# - SerializableTupleCollection_Manipulator.cs) |
Steinbitglis (Talk | contribs) (Added 'Editor' as part of the file name) |
||
Line 194: | Line 194: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | == C# - SerializableTupleCollectionDrawer.cs == | + | == C# - Editor/SerializableTupleCollectionDrawer.cs == |
<syntaxhighlight lang="csharp">using UnityEngine; | <syntaxhighlight lang="csharp">using UnityEngine; |
Revision as of 07:27, 7 March 2021
Author: Fredrik Ludvigsen (Steinbitglis)
Contents |
Description
This script lets you use serializable dictionaries in Unity with no boilerplate per new type.
Usage looks like this.
And you get an editor that looks like this.
All files as a zip file
File:SerializableTupleCollection.zip
Usage
Copy and paste all files into Assets. See Example.cs for usage.
C# - Example.cs
using UnityEngine; public class Example : MonoBehaviour { public SerializableTupleCollection<string, string> names; public SerializableTupleCollection<string, GameObject> gameObjects; public SerializableTupleCollection<string, Color> colors; }
C# - SerializableTupleCollection.cs
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; [Serializable] public struct SerializableTuple<TKey, TValue> { public TKey Item1; public TValue Item2; public SerializableTuple (TKey item1, TValue item2) { Item1 = item1; Item2 = item2; } } public class SerializableTupleCollection {} [Serializable] public partial class SerializableTupleCollection<TKey, TValue> : SerializableTupleCollection, IEnumerable<SerializableTuple<TKey, TValue>> { [SerializeField] private List<SerializableTuple<TKey, TValue>> list = new List<SerializableTuple<TKey, TValue>>(); public IEnumerator<SerializableTuple<TKey, TValue>> GetEnumerator () { return list.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); // As disconnected Dictionary (for quick lookups) public Dictionary<TKey, TValue> ToDictionary() => list.ToDictionary(element => element.Item1, element => element.Item2); public static SerializableTupleCollection<TKey, TValue> FromDictionary(IDictionary<TKey, TValue> dictionary) { var result = new SerializableTupleCollection<TKey, TValue>(); result.list = dictionary.Select( kvp => new SerializableTuple<TKey, TValue>( kvp.Key, kvp.Value ) ).ToList(); return result; } }
C# - SerializableTupleCollection_Manipulator.cs
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; public partial class SerializableTupleCollection<TKey, TValue> { public IDictionary<TKey, TValue> Manipulator { get => manipulator ?? (manipulator = new CollectionManipulator(this)); } private IDictionary<TKey, TValue> manipulator; private class CollectionManipulator : IDictionary<TKey, TValue> { SerializableTupleCollection<TKey, TValue> collection; public CollectionManipulator (SerializableTupleCollection<TKey, TValue> collection) { this.collection = collection; } #region IDictionary<TKey, TValue> public TValue this[TKey key] { get { var index = GetIndex(key); if (index == -1) throw new KeyNotFoundException($"Key not found: {key}"); return collection.list[index].Item2; } set { var index = GetIndex(key); if (index != -1) { var p = collection.list[index]; p.Item2 = value; collection.list[index] = p; } else collection.list.Add( new SerializableTuple<TKey, TValue>( key, value ) ); } } public ICollection<TKey> Keys => collection.list.Select( tuple => tuple.Item1 ).ToArray(); public ICollection<TValue> Values => collection.list.Select( tuple => tuple.Item2 ).ToArray(); public void Add(TKey key, TValue value) { if (GetIndex(key) != -1) throw new ArgumentException("An element with the same key already exists in the dictionary."); collection.list.Add( new SerializableTuple<TKey, TValue> ( key, value ) ); } public bool ContainsKey(TKey key) => GetIndex(key) != -1; public bool Remove (TKey key) { var index = GetIndex(key); var removed = index != -1; if (removed) collection.list.RemoveAt(index); return removed; } public bool TryGetValue (TKey key, out TValue value) { var index = GetIndex(key); var gotValue = index != -1; value = gotValue ? collection.list[index].Item2 : default; return gotValue; } #endregion #region ICollection <KeyValuePair<TKey, TValue>> public int Count => collection.list.Count; public bool IsReadOnly => false; public void Add (KeyValuePair<TKey, TValue> item) => Add( item.Key, item.Value ); public void Clear () => collection.list.Clear(); public bool Contains (KeyValuePair<TKey, TValue> item) => GetIndex(item.Key) != -1; public void CopyTo (KeyValuePair<TKey, TValue>[] array, int arrayIndex) { var numKeys = collection.list.Count; if (array.Length - arrayIndex < numKeys) throw new ArgumentException(); for (int i = 0; i < numKeys; i++, arrayIndex++) { var element = collection.list[i]; array[arrayIndex] = new KeyValuePair<TKey, TValue>( element.Item1, element.Item2 ); } } public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(item.Key); #endregion #region IEnumerable <KeyValuePair<TKey, TValue>> public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator () { return collection.list.Select(ToKeyValuePair).GetEnumerator(); KeyValuePair<TKey, TValue> ToKeyValuePair (SerializableTuple<TKey, TValue> sTuple) { return new KeyValuePair<TKey, TValue>( sTuple.Item1, sTuple.Item2 ); } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion private static EqualityComparer<TKey> equalityComparer = EqualityComparer<TKey>.Default; private int GetIndex (TKey key) { var list = collection.list; var numElements = list.Count; for (int i = 0; i < numElements; i++) if (equalityComparer.Equals(list[i].Item1, key)) return i; return -1; } } }
C# - Editor/SerializableTupleCollectionDrawer.cs
using UnityEngine; using UnityEditor; using UnityEditorInternal; [CustomPropertyDrawer(typeof(SerializableTupleCollection), true)] public class SerializableTupleCollectionDrawer : PropertyDrawer { private ReorderableList list; public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) { if (list == null) { var listProp = property.FindPropertyRelative("list"); list = new ReorderableList(property.serializedObject, listProp, true, false, true, true); list.drawElementCallback = DrawListItems; } var firstLine = position; firstLine.height = EditorGUIUtility.singleLineHeight; EditorGUI.PropertyField(firstLine, property, label); if (property.isExpanded) { position.y += firstLine.height; var prevLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = 80; list.DoList(position); EditorGUIUtility.labelWidth = prevLabelWidth; } } void DrawListItems(Rect rect, int index, bool isActive, bool isFocused) { SerializedProperty element = list.serializedProperty.GetArrayElementAtIndex(index); // The element in the list var keyProp = element.FindPropertyRelative("Item1"); var contents = new GUIContent[] {GUIContent.none, new GUIContent (" => ")}; EditorGUI.MultiPropertyField(rect, contents, keyProp, EditorGUI.BeginProperty(rect, new GUIContent($"Element {index}"), element)); EditorGUI.EndProperty(); } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { if (property.isExpanded) { var listProp = property.FindPropertyRelative("list"); if (listProp.arraySize < 2) return EditorGUIUtility.singleLineHeight + 42f; else return EditorGUIUtility.singleLineHeight + 21f * (listProp.arraySize + 1); } else return EditorGUIUtility.singleLineHeight; } }