-
-
Save ByronMayne/70a46e73f3af7fb9fec7437174dd4858 to your computer and use it in GitHub Desktop.
using System.Collections.Generic; | |
using System.Reflection; | |
using UnityEditor; | |
using UnityEditorInternal; | |
using UnityEngine; | |
using UnityEngine.Events; | |
[CustomPropertyDrawer(typeof(UnityEvent))] | |
public class CollapsableEventDrawer : UnityEventDrawer | |
{ | |
private class Styles | |
{ | |
public readonly GUIStyle preButton = "RL FooterButton"; | |
public GUIContent iconToolbarPlus = EditorGUIUtility.IconContent("Toolbar Plus", "|Add to list"); | |
} | |
private const string CALLS_PROPERTY_PATH = "m_PersistentCalls.m_Calls"; | |
private const string GET_STATE_METHOD_NAME = "GetState"; | |
private const string REORDERABLE_LIST_FIELD_NAME = "m_ReorderableList"; | |
private const float BUTTON_WIDTH = 30; | |
private const float BUTTON_SPACING = 30; | |
private static readonly BindingFlags NON_PULIC_INSTANCE_FLAGS = BindingFlags.NonPublic | BindingFlags.Instance; | |
// Points the field inside Sate which has the reorderable list. | |
private static FieldInfo _stateReorderableListFieldInfo; | |
// Points to the GetState method defined in UnityEventDrawer. | |
private static MethodInfo _getStateMethod; | |
// Cached array for using reflection | |
private static object[] _getStateArgs = new object[1]; | |
// A class that contains all the custom styling we need | |
private static Styles _styles; | |
// True if we have persistent calls and false if we don't. | |
private bool _hasPersistentCalls; | |
// Holds all our reorderable lists that belong to our state | |
private IDictionary<State, ReorderableList> _lists; | |
/// <summary> | |
/// Used to initialize our values. | |
/// </summary> | |
public CollapsableEventDrawer() | |
{ | |
_lists = new Dictionary<State, ReorderableList>(); | |
} | |
/// <summary> | |
/// A wrapper around the GetState function that is private in <see cref="UnityEventDrawer"/> | |
/// </summary> | |
private State GetState(SerializedProperty property) | |
{ | |
if (_getStateMethod == null) | |
{ | |
_getStateMethod = typeof(UnityEventDrawer).GetMethod(GET_STATE_METHOD_NAME, NON_PULIC_INSTANCE_FLAGS); | |
} | |
_getStateArgs[0] = property; | |
return (State)_getStateMethod.Invoke(this, _getStateArgs); | |
} | |
/// <summary> | |
/// Tries to get the cached ReorderableList from the state. | |
/// </summary> | |
private ReorderableList GetList(SerializedProperty property) | |
{ | |
State state = GetState(property); | |
if(_lists.ContainsKey(state)) | |
{ | |
return _lists[state]; | |
} | |
ReorderableList list = GetReorderableListFromState(state); | |
list.draggable = true; | |
_lists[state] = list; | |
return list; | |
} | |
/// <summary> | |
/// Returns back the number of calls that are currently in the Unity Event. | |
/// </summary> | |
private bool HasPersistentCalls(SerializedProperty property) | |
{ | |
return property.FindPropertyRelative(CALLS_PROPERTY_PATH).arraySize > 0; | |
} | |
/// <summary> | |
/// Returns to Unity to tell it how much space we should use to draw. If we | |
/// have one element we only reserve 32 units. 16 for the element itself and | |
/// 16 for spacing from the next element. If we any calls we let Unity | |
/// draw the default. | |
/// </summary> | |
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | |
{ | |
_hasPersistentCalls = HasPersistentCalls(property); | |
ReorderableList list = GetList(property); | |
if (!_hasPersistentCalls) | |
{ | |
list.elementHeight = 0; | |
list.displayAdd = false; | |
list.displayRemove = false; | |
return EditorGUIUtility.singleLineHeight * 2f; | |
} | |
list.elementHeight = 43; | |
list.displayAdd = true; | |
list.displayRemove = true; | |
return base.GetPropertyHeight(property, label); | |
} | |
/// <summary> | |
/// Used to draw our element override a bit of the Unity logic in the case | |
/// of having no elements. | |
/// </summary> | |
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | |
{ | |
if(_styles == null) | |
{ | |
_styles = new Styles(); | |
} | |
base.OnGUI(position, property, label); | |
if (!_hasPersistentCalls) | |
{ | |
position.x += position.width - BUTTON_SPACING; | |
position.width = BUTTON_WIDTH; | |
if(GUI.Button(position, _styles.iconToolbarPlus, _styles.preButton)) | |
{ | |
State state = GetState(property); | |
ReorderableList list = GetReorderableListFromState(state); | |
list.onAddCallback(list); | |
state.lastSelectedIndex = 0; | |
} | |
} | |
} | |
/// <summary> | |
/// Gets the internal instance of the <see cref="ReorderableList"/> that exists | |
/// in the state. | |
/// </summary> | |
private static ReorderableList GetReorderableListFromState(State state) | |
{ | |
if (_stateReorderableListFieldInfo == null) | |
{ | |
_stateReorderableListFieldInfo = typeof(State).GetField(REORDERABLE_LIST_FIELD_NAME, NON_PULIC_INSTANCE_FLAGS); | |
} | |
return (ReorderableList)_stateReorderableListFieldInfo.GetValue(state); | |
} | |
} |
Holy crap man, you made my day! This stuff should be so badly built-in within the editor.
Another feature I would love to see, is that a missing reference to a method or object is shown by a warning in the console. Or another way to detect broken event links. Cheers
Wow! It has rearrange function too! Thanks!
@ByronMayne great solution to tidying up UnityEvents in the inspector. I was trying to get it to also work with UnityEvent without needing to specify every custom T0
Do you know if this is possible?
I tried typeof(UnityEvent<>), true
but it didn't work. I'm having to specify every actual object like typeof(UnityEvent<MyType>), true
You have to do it per type unfortunately. Unless that has changed. I have not used Unity for about 7 months so I have not been keeping up to date
Saying that you might be able to break into Unity with reflection and do it but that will take some digging
Yeah I think reflection is the only way I can see of doing it. I'll get something working and post the result back here in case anyone comes across this and needs a solution in the future!
Yes, I know I am late to the party, but this one is still useful, and by now you can use [CustomPropertyDrawer(typeof(UnityEventBase))] to have it also work for events with parameters.
I wrote this 4 years ago I am surprised it's still useful that is... unexpected. hahah
Right now this is just the drawer for any
UnityEvent
type with no argument. If you want it to draw on other event types you have to add it to the attribute above.