Home > OS >  Unity3D Display a UnityEvent Corrrectly in a ReorderableList
Unity3D Display a UnityEvent Corrrectly in a ReorderableList

Time:03-02

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?

enter image description here

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 enter image description here

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)

  • Related