- Home /
Reorderable list of UnityEvents
Hi! I'm a bit of a novice with C# but I'm very familiar with many of the Unity tools and concepts. I'd appreciate some help!
I've been following this tutorial, but it doesn't work well when I swap simple variables like floats and strings out for Unity Events.
My goal:
I'm making a scripted event system which could be triggered from anything. It should fire off a list of Unity Events with delays between each one. It should have a custom inspector component so that the list is reorderable.
My problem:
Correctly displaying this list as a Reorderable List in a custom Inspector component.
My setup:
I've created a struct, which uses a UnityEvent (the action) and a float (the delay) for each object in the list.
I then have another script as a container (I think? not too sure of the correct terminology there) which iterates through the list and calls each event in order.
This works exactly how I want it to, but the Inspector tools for this are a bit difficult to work with because it's not reorderable. Here's how it looks by default:
When I add the custom editor script, everything overlaps each other. Here's the same list with the custom inspector:
Here's all the code:
The event struct:
using UnityEngine;
using UnityEngine.Events;
using System;
// An event which can be called as part of a Scripted Sequence
[Serializable]
public struct ScriptedSequenceEvent {
public float delay;
public UnityEvent scriptedEvent;
}
Container for the events. Also processes the list when I call the Scripted Sequence:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// A container for the events
public class ScriptedSequence : MonoBehaviour {
private IEnumerator sequenceCoroutine;
public bool startAutomatically;
public bool onlyTriggerOnce;
private bool started = false;
// The list of events
public List<ScriptedSequenceEvent> events = new List<ScriptedSequenceEvent>();
void Start () {
if (startAutomatically)
StartScriptedSequence ();
}
public void StartScriptedSequence () {
if (!onlyTriggerOnce) {
BeginSequence ();
} else {
if (!started) {
BeginSequence ();
}
}
}
void BeginSequence () {
started = true;
sequenceCoroutine = ProcessSequence ();
StartCoroutine (sequenceCoroutine);
}
private IEnumerator ProcessSequence () {
int i = 0;
// Iterate through each item in the list.
foreach(ScriptedSequenceEvent currentEvent in events) {
// Wait for the delay, if it's > 0.
if (currentEvent.delay > 0f)
yield return new WaitForSeconds (currentEvent.delay);
// Invoke the event.
currentEvent.scriptedEvent.Invoke ();
// Increase the current iteration number so we move on to the next one in the next loop.
i++;
}
}
}
Custom Inspector code:
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
// The custom inspector script which lets use make the list of events reorderable, and lets us draw it how we want.
[CustomEditor(typeof(ScriptedSequence))]
public class ScriptedSequence_Editor : Editor {
private ReorderableList list;
private void OnEnable() {
// Create the list
list = new ReorderableList (serializedObject, serializedObject.FindProperty ("events"), true, true, true, true);
// Draw header label
list.drawHeaderCallback = (Rect rect) => {
EditorGUI.LabelField(rect, "Events");
};
// Draw each element in the list
list.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) => {
var element = list.serializedProperty.GetArrayElementAtIndex(index);
rect.y += 2;
EditorGUI.PropertyField (
new Rect(rect.x, rect.y, rect.width - 30, EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("scriptedEvent"), GUIContent.none);
EditorGUI.PropertyField (
new Rect(rect.x + rect.width - 25, rect.y, 25, EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("delay"), GUIContent.none);
};
}
public override void OnInspectorGUI() {
serializedObject.Update ();
list.DoLayoutList ();
serializedObject.ApplyModifiedProperties ();
}
}
Thanks in advance! :)
Answer by Adam-Mechtley · May 01, 2017 at 05:19 PM
On your ReorderableList instance, you need to assign something to either elementHeight
(when everything has the same height) or elementHeightCallback
(when individual elements may have different heights).
Thanks!
elementHeight prevented the problem when I add more elements to the list, but the elements can change height when I add multiple events to each element, so it seems like I neeed elementHeightCallback.
Any chance you could give me some pointers for how to implement this?
It works like any of other callback functions, and most times if you are using e.g., default PropertyDrawers it is sufficient to do something like this:
list.elementHeightCallback = delegate(int index) {
var element = list.serializedProperty.GetArrayElementAtIndex(index);
var elementHeight = EditorGUI.GetPropertyHeight(element);
// optional, depending on the situation in question and the defaults you like
// you may want to subtract the margin out in the drawElementCallback before drawing
var margin = EditorGUIUtility.standardVerticalSpacing;
return elementHeight + margin;
};
Thank you, that code fixed the element height when I add multiple events to each element. However, I still get this issue when I add more elements to the list:
I guess what I'm looking for is some understanding of how these callbacks work, and how I can add my own and configure them to my needs.
Answer by Vykx · Oct 31, 2018 at 03:29 AM
@CrowbarSka this should work
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
[CustomEditor(typeof(ScriptedSequence))]
public class ScriptedSequence_Editor : Editor
{
private ReorderableList list;
private ScriptedSequence scriptedSequence;
private void OnEnable()
{
scriptedSequence = target as ScriptedSequence;
// Create the list
list = new ReorderableList(serializedObject, serializedObject.FindProperty("events"), true, true, true, true);
}
private void DrawList()
{
// Draw header label
list.drawHeaderCallback = (Rect rect) =>
{
EditorGUI.LabelField(rect, "Events");
};
// Draw each element in the list
list.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) =>
{
var element = list.serializedProperty.GetArrayElementAtIndex(index);
rect.y += 2;
EditorGUI.PropertyField(
new Rect(rect.x, rect.y, rect.width - 30, EditorGUI.GetPropertyHeight(element,true)),
element.FindPropertyRelative("scriptedEvent"), GUIContent.none);
EditorGUI.PropertyField(
new Rect(rect.x + rect.width - 25, rect.y, 25, EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("delay"), GUIContent.none);
};
list.elementHeightCallback = (int index) =>
{
var elementHeight = scriptedSequence.events[index].scriptedEvent.GetPersistentEventCount();
if (elementHeight >= 1)
{
elementHeight--;
}
return (EditorGUIUtility.singleLineHeight * 5) + elementHeight * (EditorGUIUtility.singleLineHeight * 2.7f);
};
}
public override void OnInspectorGUI()
{
serializedObject.Update();
DrawList();
list.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
}