Skip to content

Instantly share code, notes, and snippets.

@mattdevv
Forked from a-gruzdev/EnumArray.cs
Last active January 3, 2025 04:35
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.
Fixed length array indexable by enum. Includes custom PropertyDrawer
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;
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
}
public EnumArray()
{
_values = new T[s_Length];
}
public ref T this[E e]
{
get => ref _values[e.GetHashCode()];
}
public ref T this[int i]
{
get => ref _values[i];
}
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;
private bool open = false;
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);
// 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;
}
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;
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 labels = Names[_type];
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;
}
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment