I am trying to make a custom inspector for my sequence class. The idea is to allow the user to configure various UnityEvents that get called at the start or the end of a sequence.
I want to have a collection of sequences in a ReorderableList so that it is highly configurable inside the inspector.
I am very close to a solution but i am fairly inexperienced with editor scripting. My scripts are almost working correctly. But I still need to solve the best way to dynamically adjust the vertical height of each item, in the DrawListItem method, and the total vertical height in the ElementHeight Method.
I am considering trying to deserialize the unity events so that i can use the GetPersistentEventCount method to get an idea of the required vertical height, but this seems like it is probably overkill. I suspect that there must be a simpler way to retrieve this data.
Currently when i add multiple items to the sequence I am getting what is pictured below, where the event fields overlap each other and the add/remove buttons are beneath the lower Unity Event.
Does anyone know the best way to resolve this?
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
using System;
using UnityEngine.Events;
[CustomEditor(typeof(SequenceManager))]
public class SequenceManagerEditor : Editor
{
SerializedProperty Sequences;
ReorderableList list;
private void OnEnable()
{
Sequences = serializedObject.FindProperty("Sequences");
list = new ReorderableList(serializedObject, Sequences, true, true, true, true);
list.drawElementCallback = DrawListItems;
list.drawHeaderCallback = DrawHeader;
list.elementHeightCallback = ElementHeight;
}
//Draws the elements in the list
void DrawListItems(Rect rect, int index, bool isActive, bool isFocused)
{
SerializedProperty element = list.serializedProperty.GetArrayElementAtIndex(index);
////NAME
EditorGUI.LabelField(new Rect(
rect.x,
rect.y EditorGUIUtility.standardVerticalSpacing,
50,
EditorGUIUtility.singleLineHeight), "Name");
EditorGUI.PropertyField(
new Rect(
rect.x 50,
rect.y EditorGUIUtility.standardVerticalSpacing,
rect.width - 50,
EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("Name"),
GUIContent.none
);
//ON INIT
EditorGUI.LabelField(new Rect(
rect.x,
rect.y EditorGUIUtility.singleLineHeight EditorGUIUtility.standardVerticalSpacing * 3,
50,
EditorGUIUtility.singleLineHeight), "OnInit");
EditorGUI.PropertyField(new Rect(
rect.x 50,
rect.y EditorGUIUtility.singleLineHeight EditorGUIUtility.standardVerticalSpacing * 3,
rect.width - 50,
3 * rect.y 5 * EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("OnInit"),
GUIContent.none);
//ON DONE
EditorGUI.LabelField(new Rect(
rect.x,
rect.y 7 * EditorGUIUtility.singleLineHeight,
50,
EditorGUIUtility.singleLineHeight), "OnDone");
EditorGUI.PropertyField(
new Rect(
rect.x 50,
rect.y 7 * EditorGUIUtility.singleLineHeight,
rect.width - 50,
3 * rect.y 12 * EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("OnDone"),
GUIContent.none);
SerializedProperty indexProperty = element.FindPropertyRelative("index");
indexProperty.intValue = index;
}
private float ElementHeight(int index)
{
return (13 * EditorGUIUtility.singleLineHeight);
}
//Draws the header
void DrawHeader(Rect rect)
{
string name = "Sequences";
EditorGUI.LabelField(rect, name);
}
public override void OnInspectorGUI()
{
//base.OnInspectorGUI();
serializedObject.Update();
this.list.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
}
for the sake of completeness, I've also added the Sequence class and SequenceManager class below.
using UnityEngine;
using UnityEditor;
using System;
using UnityEngine.Events;
[Serializable]
public class Sequence
{
public string Name;
public UnityEvent OnInit;
public UnityEvent OnDone;
private Module _module;
public int index;
private bool active;
//Called By The Sequence Manager At the start of a sequence
internal void Init(Module p_module)
{
Debug.Log($"sequence: {Name} with index: {index} has started");
active = true;
_module = p_module;
if(OnInit.HasNoListners())
{
Done();
}
else
{
OnInit.Invoke();
}
}
//Called Manually to Trigger the End of the Sequence
internal void Done()
{
if (!OnDone.HasNoListners())
{
OnDone.Invoke();
}
active = false;
Debug.Log($"sequence: {Name} with index: {index} is done");
_module.FinishedSequence(index);
}
//Check if active
internal bool GetActive()
{
return active;
}
}
using System;
namespace UnityEngine
{
[Serializable]
public class SequenceManager: MonoBehaviour
{
#region Properties
public Sequence[] Sequences;
#endregion
}
}
CodePudding user response:
It's possible to access public fields of Sequence
by casting the elements of serializedObject.targetObjects
in your editor script. You can also use serializedObject.targetObject
or
If you rather want to change how a Sequence
is drawn in the Inspector in general, you would be better implementing a custom PropertyDrawer
instead and not have to deal with the list at all.
And the drawer dealing with the index
is btw dangerous / not reliable!
Currently your index is only applied if this is actually opened in the Inspector and not in Debug mode. What if later you want to modify this via script?
In my opinion it would be better if your SequenceManager
rather simply passes in the index into the Init
method if it is really needed for anything except the log ;)
Also be very careful: In your Sequence
script you have
using UnityEditor;
be aware that this namespace is only available within the Unity editor itself and completely stripped of during the build.
=> You want to make sure that nothing of this namespace is trying to be used in a built application so you will have to wrap any references to this namespace in according pre-processor tags like e.g.
#if UNITY_EDITOR
using UnityEditor;
#endif
and the same for any code related to this namespace (see also Platform Dependent Compilation)