Last active
May 19, 2025 07:11
-
-
Save shane-harper/7bd579c3f6ca0a419f056fb9bb49b681 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEngine; | |
#if UNITY_EDITOR | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text.RegularExpressions; | |
using UnityEditor; | |
#endif | |
/// <summary> | |
/// Attribute used to annotate a serialized list field to automatically align its length and elements | |
/// with the values of a specified enum type. Each element in the list corresponds to a value in the enum. | |
/// </summary> | |
[AttributeUsage(AttributeTargets.Field)] | |
public class EnumListAttribute : PropertyAttribute | |
{ | |
public readonly Type EnumType; | |
public EnumListAttribute(Type type) | |
{ | |
EnumType = type; | |
} | |
} | |
#if UNITY_EDITOR | |
/// <summary> | |
/// Custom property drawer for fields annotated with the <see cref="EnumListAttribute"/>. | |
/// Automatically ensures that the serialized list matches the number of values in the specified enum type, | |
/// aligning each list element with its corresponding enum value. | |
/// </summary> | |
[CustomPropertyDrawer(typeof(EnumListAttribute))] | |
internal class EnumListAttributeDrawer : PropertyDrawer | |
{ | |
private List<string> _cachedNames; | |
private Type _cachedType; | |
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | |
{ | |
if (attribute is not EnumListAttribute { EnumType: { IsEnum: true } } enumListAttribute) | |
{ | |
EditorGUI.PropertyField(position, property, label, true); | |
return; | |
} | |
var names = GetEnumNames(enumListAttribute.EnumType); | |
if (!ValidateArraySize(property, names.Count)) | |
{ | |
property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); | |
EditorUtility.SetDirty(property.serializedObject.targetObject); | |
} | |
var index = GetIndexFromPropertyPath(property.propertyPath); | |
var displayLabel = index < names.Count | |
? new GUIContent(ObjectNames.NicifyVariableName(names[index])) | |
: label; | |
position.height = EditorGUIUtility.singleLineHeight; | |
EditorGUI.PropertyField(position, property, displayLabel, true); | |
} | |
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | |
{ | |
return EditorGUI.GetPropertyHeight(property, label, true); | |
} | |
private IReadOnlyList<string> GetEnumNames(Type type) | |
{ | |
if (_cachedNames != null && _cachedType == type) | |
{ | |
return _cachedNames; | |
} | |
_cachedType = type; | |
_cachedNames = Enum.GetValues(type) | |
.Cast<Enum>() | |
.Where(e => Convert.ToInt32(e) >= 0) | |
.Select(e => Enum.GetName(type, e)) | |
.ToList(); | |
return _cachedNames; | |
} | |
private static int GetIndexFromPropertyPath(string path) | |
{ | |
var match = Regex.Match(path, @"\[(\d+)\]"); | |
return match.Success ? int.Parse(match.Groups[1].Value) : 0; | |
} | |
private static bool ValidateArraySize(SerializedProperty property, int requiredSize) | |
{ | |
var parent = GetParentArrayProperty(property); | |
if (parent == null || parent.arraySize == requiredSize) | |
{ | |
return false; | |
} | |
parent.arraySize = requiredSize; | |
return true; | |
} | |
private static SerializedProperty GetParentArrayProperty(SerializedProperty property) | |
{ | |
var path = property.propertyPath; | |
var arrayPath = path[..path.LastIndexOf(".Array", StringComparison.Ordinal)]; | |
return property.serializedObject.FindProperty(arrayPath); | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment