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:

93201-editor4.png

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! :slight_smile:

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).

@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();
	}
}