Skip to content

Instantly share code, notes, and snippets.

@FleshMobProductions
Last active February 29, 2024 16:02
Show Gist options
  • Save FleshMobProductions/6d520cf36ea0cf8d4172baf373bef500 to your computer and use it in GitHub Desktop.
Save FleshMobProductions/6d520cf36ea0cf8d4172baf373bef500 to your computer and use it in GitHub Desktop.
Unity PropertyDrawer for selecting animation state names of AnimationControllers in the inspector
// Place file in your main project, outside of an Editor folder
using UnityEngine;
namespace FMPUtils.Types
{
[System.Serializable]
public class AnimationStateNameSelection
{
public Animator animator;
public string animationName;
}
}
// Place file in your project, inside of an "Editor" folder
using FMPUtils.Types;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
namespace FMPUtils.Editor
{
[CustomPropertyDrawer(typeof(AnimationStateNameSelection))]
public class AnimationStateNameSelectionDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUIUtility.singleLineHeight * 2f;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, GUIContent.none, property);
SerializedProperty animatorProp = property.FindPropertyRelative(nameof(AnimationStateNameSelection.animator));
SerializedProperty animationNameProp = property.FindPropertyRelative(nameof(AnimationStateNameSelection.animationName));
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
Rect line1Rect = position; // Assignment copy since this is a struct
line1Rect.height = EditorGUIUtility.singleLineHeight;
Rect line1PrefixRect = line1Rect;
float animatorPrefixLabelWidth = 80f;
line1PrefixRect.width = animatorPrefixLabelWidth;
Rect line1ValueRect = line1Rect;
line1ValueRect.x = line1PrefixRect.x + line1PrefixRect.width;
line1ValueRect.width = line1Rect.width - line1PrefixRect.width;
EditorGUI.LabelField(line1PrefixRect, "Animator");
animatorProp.objectReferenceValue = EditorGUI.ObjectField(line1ValueRect, animatorProp.objectReferenceValue, typeof(Animator), true);
if (animatorProp.objectReferenceValue != null)
{
// Adjust the rect to draw contents one line below:
Rect line2Rect = position;
line2Rect.height = EditorGUIUtility.singleLineHeight;
line2Rect.y = position.y + EditorGUIUtility.singleLineHeight;
List<string> stateNames = new List<string>();
string prevAnimationName = animationNameProp.stringValue;
Animator animator = animatorProp.objectReferenceValue as Animator;
AnimatorController animatorController = animator.runtimeAnimatorController as AnimatorController;
// TODO: since there can be multiple animation layers it would be good to offer an additional dropdown for the layer selection
// before selecting the animation state name.
// States of substate machines seem to be included too but perhaps explore a way to better display/visualize it
AnimatorControllerLayer[] layers = animatorController.layers;
foreach (AnimatorControllerLayer layer in layers)
{
foreach (ChildAnimatorState state in layer.stateMachine.states)
{
string stateName = state.state.name;
if (!stateNames.Contains(stateName))
stateNames.Add(stateName);
}
}
if (stateNames.Count == 0)
{
EditorGUI.LabelField(line2Rect, "No states in AnimationController found");
}
else
{
int selectedIndex = stateNames.IndexOf(prevAnimationName);
if (selectedIndex == -1)
selectedIndex = 0;
selectedIndex = EditorGUI.Popup(line2Rect, "State Name", selectedIndex, stateNames.ToArray());
if (selectedIndex >= 0)
{
animationNameProp.stringValue = stateNames[selectedIndex];
}
else
{
animationNameProp.stringValue = string.Empty;
}
}
}
EditorGUI.EndProperty();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment