Skip to content

Instantly share code, notes, and snippets.

@mattdevv
Forked from a-gruzdev/EnumArray.cs
Last active August 21, 2025 01:15
Show Gist options
  • Save mattdevv/a3275cbd3ada72530d432db79ade6d18 to your computer and use it in GitHub Desktop.
Save mattdevv/a3275cbd3ada72530d432db79ade6d18 to your computer and use it in GitHub Desktop.

Revisions

  1. mattdevv revised this gist Aug 21, 2025. 1 changed file with 81 additions and 44 deletions.
    125 changes: 81 additions & 44 deletions EnumArray.cs
    Original file line number Diff line number Diff line change
    @@ -1,36 +1,39 @@
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    #if UNITY_EDITOR
    using UnityEditor;
    #endif

    [Serializable]
    public class EnumArray<E, T> : ISerializationCallbackReceiver where E : Enum
    public class EnumArray<TEnum, TData> : IEnumerable<TData>, ISerializationCallbackReceiver where TEnum : Enum
    {
    public static readonly int s_Length;

    [SerializeField] private T[] _values;
    [SerializeField] private TData[] _values;
    public int Length => s_Length;

    static EnumArray()
    {
    var names = Enum.GetNames(typeof(E));
    var names = Enum.GetNames(typeof(TEnum));
    s_Length = names.Length;

    #if UNITY_EDITOR
    EnumArrayDrawer.Names[typeof(E)] = names;
    EnumArrayDrawer.Names[typeof(TEnum)] = names;

    if (Enum.GetUnderlyingType(typeof(E)) != typeof(int))
    Debug.LogWarning($"EnumArray should not be used with Enum type {typeof(E)} as it doesn't derive from int");
    if (Enum.GetUnderlyingType(typeof(TEnum)) != typeof(int))
    Debug.LogWarning($"EnumArray should not be used with Enum type {typeof(TEnum)} as it doesn't derive from int");

    var values = Enum.GetValues(typeof(E)) as int[];
    var values = Enum.GetValues(typeof(TEnum)) as int[];
    Array.Sort(values, (i0, i1) => i0 - i1);
    if (values[0] != 0) Debug.LogWarning($"EnumArray should not be used with Enum type {typeof(E)} as it doesn't start from 0");
    if (values[0] != 0) Debug.LogWarning($"EnumArray should not be used with Enum type {typeof(TEnum)} as it doesn't start from 0");
    for (int i = 1; i < values.Length; i++)
    {
    if (values[i] != values[i - 1] + 1)
    {
    Debug.LogWarning($"EnumArray should not be used with Enum type {typeof(E)} as it contains a gap in enums");
    Debug.LogWarning($"EnumArray should not be used with Enum type {typeof(TEnum)} as it contains a gap in enums");
    break;
    }
    }
    @@ -39,21 +42,32 @@ static EnumArray()

    public EnumArray()
    {
    _values = new T[s_Length];
    _values = new TData[s_Length];
    }

    public ref T this[E e]
    public ref TData this[TEnum e]
    {
    get => ref _values[e.GetHashCode()];
    }

    public ref T this[int i]
    public ref TData this[int i]
    {
    get => ref _values[i];
    }

    public IEnumerator<TData> GetEnumerator()
    {
    return ((IEnumerable<TData>) _values).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
    return GetEnumerator();
    }

    public void OnAfterDeserialize()
    {
    // required check in case array was serialized when Enumerator class had a different number of elements
    if (_values.Length != s_Length)
    Array.Resize(ref _values, s_Length);
    }
    @@ -62,14 +76,13 @@ public void OnBeforeSerialize() { }
    }

    #if UNITY_EDITOR
    [CustomPropertyDrawer(typeof(EnumArray<,>))]
    [CustomPropertyDrawer(typeof(EnumArray<,>), true)]
    public class EnumArrayDrawer : PropertyDrawer
    {
    private const float Padding = 4;
    private const float Spacing = 2;
    private bool open = false;

    public static readonly System.Collections.Generic.Dictionary<Type, string[]> Names = new();
    public static readonly Dictionary<Type, string[]> Names = new();

    private Type _type;

    @@ -78,16 +91,16 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent
    if (_type == null)
    _type = fieldInfo.FieldType.GenericTypeArguments[0];

    property.Next(true);
    property.Next(true); // this will be the array

    // header + top/bottom padding
    float height = EditorGUIUtility.singleLineHeight + 2 * Padding;
    if (open)
    float height = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
    if (property.isExpanded)
    {
    int arraySize = property.arraySize;

    // spacing between array elements
    height += arraySize * Spacing;
    height += arraySize * Spacing + 2 * Padding;

    // get each array element height
    for (int i = 0; i < arraySize; i++)
    @@ -99,38 +112,62 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
    if (Event.current.type == EventType.Repaint)
    EditorStyles.helpBox.Draw(position, false, false, false, false);
    //var enumArrayProperty = property.

    position.height = EditorGUIUtility.singleLineHeight;
    position.y += Padding;
    position.x += Padding * 4;
    position.width -= Padding * 8;
    var style = new GUIStyle(EditorStyles.foldout);
    style.fontStyle = FontStyle.Bold;
    open = EditorGUI.Foldout(position, open, label, style);

    if (!open)
    return;

    position.y += EditorGUIUtility.singleLineHeight + Spacing;
    position.x += Padding * 2;
    position.width -= Padding * 4;
    var header = position;
    header.height = EditorGUIUtility.singleLineHeight;
    header.width -= EditorGUIUtility.standardVerticalSpacing * 3;

    bool expanded = EditorGUI.BeginFoldoutHeaderGroup(header, property.isExpanded, label);
    property.isExpanded = expanded;
    if (property.hasMultipleDifferentValues == false)
    {
    // only use header as a property if array has same values
    EditorGUI.BeginProperty(header, label, property);
    EditorGUI.EndProperty();
    }

    property.Next(true);
    var labels = Names[_type];
    property.Next(true); // this will be the array

    for (int i = 0; i < property.arraySize; i++)
    if (expanded)
    {
    var serializedElement = property.GetArrayElementAtIndex(i);
    position.height = EditorGUI.GetPropertyHeight(serializedElement);
    label.text = labels[i];
    if (Event.current.type == EventType.Repaint)
    {
    Rect background = position;

    GUI.Box(position, GUIContent.none);
    EditorGUI.PropertyField(position, serializedElement, label, true);
    background.height -= EditorGUIUtility.singleLineHeight + 2 * EditorGUIUtility.standardVerticalSpacing;
    background.y += EditorGUIUtility.singleLineHeight + 2 * EditorGUIUtility.standardVerticalSpacing;
    EditorStyles.helpBox.Draw(background, false, false, false, false);
    }

    position.y += position.height + Spacing;
    position.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing * 4;

    var labels = Names[_type];

    for (int i = 0; i < property.arraySize; i++)
    {
    Rect propertyRect = position;
    var serializedElement = property.GetArrayElementAtIndex(i);
    propertyRect.height = EditorGUI.GetPropertyHeight(serializedElement);
    label.text = labels[i];

    if (propertyRect.Contains(Event.current.mousePosition))
    {
    Color col = EditorGUIUtility.isProSkin
    ? new Color32(80, 80, 80, 255)
    : new Color32 (210, 210, 210, 255);
    EditorGUI.DrawRect(propertyRect, col);
    }

    propertyRect.x += 6 * EditorGUIUtility.standardVerticalSpacing;
    propertyRect.width -= 9 * EditorGUIUtility.standardVerticalSpacing;
    EditorGUI.PropertyField(propertyRect, serializedElement, label, true);

    position.y += propertyRect.height + Spacing;
    }
    }

    EditorGUI.EndFoldoutHeaderGroup();
    }
    }
    #endif
  2. mattdevv revised this gist Jan 3, 2025. 1 changed file with 6 additions and 11 deletions.
    17 changes: 6 additions & 11 deletions EnumArray.cs
    Original file line number Diff line number Diff line change
    @@ -42,16 +42,14 @@ public EnumArray()
    _values = new T[s_Length];
    }

    public T this[E e]
    public ref T this[E e]
    {
    get => _values[e.GetHashCode()];
    set => _values[e.GetHashCode()] = value;
    get => ref _values[e.GetHashCode()];
    }

    public T this[int i]
    public ref T this[int i]
    {
    get => _values[i];
    set => _values[i] = value;
    get => ref _values[i];
    }

    public void OnAfterDeserialize()
    @@ -97,9 +95,6 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent
    }

    return height;

    // WRONG: assumes all array elements are single line height
    //return (EditorGUIUtility.singleLineHeight + Spacing) * ((open ? property.arraySize : 0) + 1) + Padding * 2;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    @@ -128,10 +123,10 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten
    for (int i = 0; i < property.arraySize; i++)
    {
    var serializedElement = property.GetArrayElementAtIndex(i);

    position.height = EditorGUI.GetPropertyHeight(serializedElement);

    label.text = labels[i];

    GUI.Box(position, GUIContent.none);
    EditorGUI.PropertyField(position, serializedElement, label, true);

    position.y += position.height + Spacing;
  3. mattdevv revised this gist Jan 3, 2025. 1 changed file with 35 additions and 10 deletions.
    45 changes: 35 additions & 10 deletions EnumArray.cs
    Original file line number Diff line number Diff line change
    @@ -81,35 +81,60 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent
    _type = fieldInfo.FieldType.GenericTypeArguments[0];

    property.Next(true);

    // header + top/bottom padding
    float height = EditorGUIUtility.singleLineHeight + 2 * Padding;
    if (open)
    {
    int arraySize = property.arraySize;

    // spacing between array elements
    height += arraySize * Spacing;

    // get each array element height
    for (int i = 0; i < arraySize; i++)
    height += EditorGUI.GetPropertyHeight(property.GetArrayElementAtIndex(i));
    }

    return height;

    return (EditorGUIUtility.singleLineHeight + Spacing) * ((open ? property.arraySize : 0) + 1) + Padding * 2;
    // WRONG: assumes all array elements are single line height
    //return (EditorGUIUtility.singleLineHeight + Spacing) * ((open ? property.arraySize : 0) + 1) + Padding * 2;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
    if (Event.current.type == EventType.Repaint)
    EditorStyles.helpBox.Draw(position, false, false, false, false);


    position.height = EditorGUIUtility.singleLineHeight;
    position.y += Padding;
    position.x += Padding * 4;
    position.width -= Padding * 8;
    open = EditorGUI.Foldout(position, open, label, EditorStyles.foldout);
    position.x -= Padding * 2;
    position.width += Padding * 4;

    var style = new GUIStyle(EditorStyles.foldout);
    style.fontStyle = FontStyle.Bold;
    open = EditorGUI.Foldout(position, open, label, style);
    if (!open)
    return;


    position.y += EditorGUIUtility.singleLineHeight + Spacing;
    position.x += Padding * 2;
    position.width -= Padding * 4;

    property.Next(true);
    var step = EditorGUIUtility.singleLineHeight + Spacing;
    var labels = Names[_type];

    for (int i = 0; i < property.arraySize; i++)
    {
    position.y += step;
    var serializedElement = property.GetArrayElementAtIndex(i);

    position.height = EditorGUI.GetPropertyHeight(serializedElement);

    label.text = labels[i];
    EditorGUI.PropertyField(position, property.GetArrayElementAtIndex(i), label);
    EditorGUI.PropertyField(position, serializedElement, label, true);

    position.y += position.height + Spacing;
    }
    }
    }
  4. mattdevv revised this gist Dec 13, 2024. 1 changed file with 28 additions and 4 deletions.
    32 changes: 28 additions & 4 deletions EnumArray.cs
    Original file line number Diff line number Diff line change
    @@ -16,8 +16,24 @@ static EnumArray()
    {
    var names = Enum.GetNames(typeof(E));
    s_Length = names.Length;

    #if UNITY_EDITOR
    EnumArrayDrawer.Names[typeof(E)] = names;

    if (Enum.GetUnderlyingType(typeof(E)) != typeof(int))
    Debug.LogWarning($"EnumArray should not be used with Enum type {typeof(E)} as it doesn't derive from int");

    var values = Enum.GetValues(typeof(E)) as int[];
    Array.Sort(values, (i0, i1) => i0 - i1);
    if (values[0] != 0) Debug.LogWarning($"EnumArray should not be used with Enum type {typeof(E)} as it doesn't start from 0");
    for (int i = 1; i < values.Length; i++)
    {
    if (values[i] != values[i - 1] + 1)
    {
    Debug.LogWarning($"EnumArray should not be used with Enum type {typeof(E)} as it contains a gap in enums");
    break;
    }
    }
    #endif
    }

    @@ -53,6 +69,7 @@ public class EnumArrayDrawer : PropertyDrawer
    {
    private const float Padding = 4;
    private const float Spacing = 2;
    private bool open = false;

    public static readonly System.Collections.Generic.Dictionary<Type, string[]> Names = new();

    @@ -64,19 +81,26 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent
    _type = fieldInfo.FieldType.GenericTypeArguments[0];

    property.Next(true);
    return (EditorGUIUtility.singleLineHeight + Spacing) * (property.arraySize + 1) + Padding * 2;

    return (EditorGUIUtility.singleLineHeight + Spacing) * ((open ? property.arraySize : 0) + 1) + Padding * 2;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
    if (Event.current.type == EventType.Repaint)
    EditorStyles.helpBox.Draw(position, false, false, false, false);


    position.height = EditorGUIUtility.singleLineHeight;
    position.y += Padding;
    position.x += Padding;
    position.width -= Padding * 2;
    GUI.Label(position, label, EditorStyles.boldLabel);
    position.x += Padding * 4;
    position.width -= Padding * 8;
    open = EditorGUI.Foldout(position, open, label, EditorStyles.foldout);
    position.x -= Padding * 2;
    position.width += Padding * 4;

    if (!open)
    return;

    property.Next(true);
    var step = EditorGUIUtility.singleLineHeight + Spacing;
  5. @a-gruzdev a-gruzdev revised this gist Dec 13, 2024. No changes.
  6. @a-gruzdev a-gruzdev revised this gist Dec 13, 2024. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion EnumArray.cs
    Original file line number Diff line number Diff line change
    @@ -38,7 +38,6 @@ public T this[int i]
    set => _values[i] = value;
    }


    public void OnAfterDeserialize()
    {
    if (_values.Length != s_Length)
  7. @a-gruzdev a-gruzdev created this gist Dec 13, 2024.
    93 changes: 93 additions & 0 deletions EnumArray.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,93 @@
    using System;
    using UnityEngine;
    #if UNITY_EDITOR
    using UnityEditor;
    #endif

    [Serializable]
    public class EnumArray<E, T> : ISerializationCallbackReceiver where E : Enum
    {
    public static readonly int s_Length;

    [SerializeField] private T[] _values;
    public int Length => s_Length;

    static EnumArray()
    {
    var names = Enum.GetNames(typeof(E));
    s_Length = names.Length;
    #if UNITY_EDITOR
    EnumArrayDrawer.Names[typeof(E)] = names;
    #endif
    }

    public EnumArray()
    {
    _values = new T[s_Length];
    }

    public T this[E e]
    {
    get => _values[e.GetHashCode()];
    set => _values[e.GetHashCode()] = value;
    }

    public T this[int i]
    {
    get => _values[i];
    set => _values[i] = value;
    }


    public void OnAfterDeserialize()
    {
    if (_values.Length != s_Length)
    Array.Resize(ref _values, s_Length);
    }

    public void OnBeforeSerialize() { }
    }

    #if UNITY_EDITOR
    [CustomPropertyDrawer(typeof(EnumArray<,>))]
    public class EnumArrayDrawer : PropertyDrawer
    {
    private const float Padding = 4;
    private const float Spacing = 2;

    public static readonly System.Collections.Generic.Dictionary<Type, string[]> Names = new();

    private Type _type;

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
    if (_type == null)
    _type = fieldInfo.FieldType.GenericTypeArguments[0];

    property.Next(true);
    return (EditorGUIUtility.singleLineHeight + Spacing) * (property.arraySize + 1) + Padding * 2;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
    if (Event.current.type == EventType.Repaint)
    EditorStyles.helpBox.Draw(position, false, false, false, false);

    position.height = EditorGUIUtility.singleLineHeight;
    position.y += Padding;
    position.x += Padding;
    position.width -= Padding * 2;
    GUI.Label(position, label, EditorStyles.boldLabel);

    property.Next(true);
    var step = EditorGUIUtility.singleLineHeight + Spacing;
    var labels = Names[_type];
    for (int i = 0; i < property.arraySize; i++)
    {
    position.y += step;
    label.text = labels[i];
    EditorGUI.PropertyField(position, property.GetArrayElementAtIndex(i), label);
    }
    }
    }
    #endif