Unity3D 在 ReorderableList 中正确显示 UnityEvent

Unity3D Display a UnityEvent Corrrectly in a ReorderableList

我正在尝试为我的序列制作自定义检查器 class。这个想法是允许用户配置在序列开始或结束时调用的各种 UnityEvents。

我想在 ReorderableList 中有一个序列集合,以便它在检查器中高度可配置。

我非常接近解决方案,但我对编辑器脚本编写还缺乏经验。我的脚本几乎可以正常工作。但是我还需要解决动态调整每个item的垂直高度的最佳方法,在DrawListItem方法中,以及ElementHeight方法中的总垂直高度。

我正在考虑尝试反序列化统一事件,以便我可以使用 GetPersistentEventCount 方法来了解所需的垂直高度,但这似乎有点矫枉过正。我怀疑一定有更简单的方法来检索这些数据。

目前,当我向序列中添加多个项目时,我得到了下图所示的内容,事件字段相互重叠,add/remove 按钮位于较低的 Unity 事件下方。

有谁知道解决这个问题的最佳方法吗?

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

为了完整起见,我还在下面添加了序列 class 和序列管理器 class。

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
    }
}

可以通过在编辑器脚本中转换 serializedObject.targetObjects 的元素来访问 Sequence 的 public 字段。如果您不使用 [CanEditMultipleObjects].

,也可以使用 serializedObject.targetObjectEditor.target
if(target is not SequenceManager manager)
{
    //target was not a SequenceManager, the manager variable will be null
    return;
}

并在 ElementHeight() 内:

var sequence = manager.Sequences[index];
var size = sequence.OnInit.GetPersistentEventCount()
         + sequence.OnDone.GetPersistentEventCount();

Edit - 在尝试了一些东西之后,我了解到你可以通过序列化 属性 来获取数组大小。您可以在 ElementHeight():

中使用它
var element = list.serializedProperty.GetArrayElementAtIndex(index);
var size = element.FindPropertyRelative("OnInit.m_PersistentCalls.m_Calls").arraySize
         + element.FindPropertyRelative("OnDone.m_PersistentCalls.m_Calls").arraySize;

而不是硬编码的默认元素高度

X * EditorGUIUtility.singleLineHeight

您应该使用 EditorUtility.GetPropertyHeight 并获取

等属性的实际高度
private float ElementHeight(int index)
{
    var property = list.serializedProperty.GetArrayElementAtIndex(index);
    return EditorUtility.GetPropertyHeight(property);
}

这个 returns 属性 的高度 需要使用它的默认 属性 抽屉 - 使用 [=15 应用的那个=] 你做的 ;)


除此之外:自定义编辑器的确切用途是什么?

自 Unity 2020 以来,列表和数组的默认抽屉无论如何都是 ReorderableList,所以老实说,我真的没有看到您的自定义编辑器添加任何不是默认检查器的东西:)

这是没有自定义编辑器的样子:

如果您更愿意更改 Sequence 在 Inspector 中的一般绘制方式,您最好实施自定义 PropertyDrawer 而不必处理列表。

处理 index 的抽屉很危险/不可靠!

目前只有在检查器中实际打开索引而不是在调试模式下才应用您的索引。如果以后你想通过脚本修改这个怎么办?

在我看来,如果您的 SequenceManager 只是将索引传递给 Init 方法会更好,如果日志之外的任何东西确实需要它的话 ;)


还要非常小心:在你的 Sequence 脚本中你有

using UnityEditor;

请注意,此命名空间仅在 Unity 编辑器本身内可用,并在构建过程中被完全删除。

=> 您要确保此命名空间中的任何内容都没有尝试在构建的应用程序中使用,因此您必须根据 pre-processor 标签将对此命名空间的任何引用包装起来,例如

#if UNITY_EDITOR
using UnityEditor;
#endif

与此命名空间相关的任何代码都相同(另请参阅 Platform Dependent Compilation