I'm using the EnumNamedArrayAttribute PropertyDrawer detailed in an old Unity Discussions thread here, in bonzairob's response, with a few small additions and it works well for my purposes, with some minor quirks when the length of the array changes. But this brings up the question, can I expand this to remove the Plus and Minus buttons that now appear at the bottom of the array? Or even make the Array Size field inaccessible?
using UnityEngine;
using UnityEditor;
[CustomPropertyDrawer(typeof(EnumNamedArrayAttribute))]
public class DrawerEnumNamedArray : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EnumNamedArrayAttribute enumNames = attribute as EnumNamedArrayAttribute;
string[] variableName = property.propertyPath.Split('.');
SerializedProperty p = property.serializedObject.FindProperty(variableName[0]);
if(p.arraySize != enumNames.names.Length) {
p.arraySize = enumNames.names.Length;
}
int index = System.Convert.ToInt32(property.propertyPath.Substring(property.propertyPath.IndexOf("[")).Replace("[", "").Replace("]", ""));
if (index < enumNames.names.Length) {
label.text = enumNames.names[index];
}
EditorGUI.PropertyField( position, property, label, true );
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
return EditorGUI.GetPropertyHeight(property, label, true);
}
}
I'm afraid I'm rather new to Custom Inspectors, and this problem has proven unusually difficult to Google.
The really tricky thing here is: For Lists and arrays Unity only applies custom drawers on a per-element basis -> with a drawer alone you can not change the list drawing itself!
The only real way to change this is to rather implement a custom drawer/inspector for the type containing this list.
In most cases this is rather undesired as you have to start implementing custom editors for each component using such a list - uncanny.
However, you could instead of the "magic" attribute simply create a custom type and have a custom drawer for that one instead:
public abstract class EnumArray
{
public abstract string[] names { get; }
}
[Serializable]
public class EnumArray<TEnum, TValues> : EnumArray, IEnumerable<TValues>
{
[SerializeField]
private TValues[] values;
public IEnumerator<TValues> GetEnumerator()
{
foreach (var value in values)
{
yield return value;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Length => names.Length;
public TValues this[TEnum index]
{
get => values[k_Indices[index]];
set => values[k_Indices[index]] = value;
}
public TValues this[int index]
{
get => values[index];
set => values[index] = value;
}
private static readonly Dictionary<TEnum, int> k_Indices = ((TEnum[])Enum.GetValues(typeof(TEnum))).Select((x, i) => (x, i)).ToDictionary(o => o.x, o => o.i);
private static readonly string[] k_Names = Enum.GetNames(typeof(TEnum));
public override string[] names => k_Names;
}
and have a custom drawer for that instead
[CustomPropertyDrawer(typeof(EnumArray), true)]
public class EnumArrayDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var height = EditorGUIUtility.singleLineHeight; // header
var values = property.FindPropertyRelative("values");
if (values.isExpanded)
{
for (var i = 0; i < values.arraySize; i++)
{
var element = values.GetArrayElementAtIndex(i);
height += EditorGUI.GetPropertyHeight(element, new GUIContent("DummyLabel"), true);
}
height += EditorGUIUtility.singleLineHeight; // additional buffer for the list border
}
return height;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
using (new EditorGUI.PropertyScope(position, label, property))
{
var variableName = property.propertyPath.Split('.')[^1];
var enumArray = property.serializedObject.targetObject.GetType().GetField(variableName).GetValue(property.serializedObject.targetObject) as EnumArray;
var names = enumArray.names;
var values = property.FindPropertyRelative("values");
values.arraySize = names.Length;
var headerRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
values.isExpanded = EditorGUI.Foldout(headerRect, values.isExpanded, label);
if (values.isExpanded)
{
using (new EditorGUI.IndentLevelScope())
{
// This is what Unity internally uses for the default List drawing
// except we do not show any add/remove buttons and also do not want to reorder items
// also don't show header as we already have our custom foldout header anyway
var list = new ReorderableList(values.serializedObject, values, false, false, false, false)
{
drawElementCallback = (rect, index, active, focused) =>
{
var element = values.GetArrayElementAtIndex(index);
EditorGUI.PropertyField(rect, element, new GUIContent(names[index]), true);
},
elementHeightCallback = (index) =>
{
var element = values.GetArrayElementAtIndex(index);
return EditorGUI.GetPropertyHeight(element, GUIContent.none, true);
}
};
list.DoList(new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight, position.width, position.height - EditorGUIUtility.singleLineHeight));
}
}
}
}
}
So assuming some enum like e.g.
public enum Axis
{
X,
Y,
Z
}
instead of so far using
[EnumArrayNames(typeof(SomeEnum))]
public string[] Example;
you now rather simply use
public EnumArray<SomeEnum, string> Example;
and can as usual simply iterate it and access values
foreach(var item in Example)
{
Debug.Log(item);
}
Example[2] = "SomeValue";
and as little bonus you can also directly access via the enum key
Debug.Log(Example[SomeEnum.ValueB]);
And this is how it would look like:
public class TestComponent : MonoBehaviour
{
public EnumArray<Axis, string> strings;
public EnumArray<Axis, Color> colors;
public EnumArray<Axis, Vector3> vectors;
public EnumArray<Axis, SomeCustomClass> custom;
}
[Serializable]
public class SomeCustomClass
{
public string someString;
public Color someColor;
public Vector3 someVector;
}

Note: Does not work with arrays/lists as value type because in that case the var variableName = property.propertyPath.Split('.')[^1]; fails since property in that case again refers to the elements, not the array itself
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With