Unity3D 编辑器:渲染侧边栏中的嵌套元素

Unity3D editor: Rendering nested elements in sidebar

在 Unity 中,我的一个 MonoBehaviours 有一个指向另一个对象(ScriptableObject)的字段。如果我双击该字段,我可以看到该对象的字段。如何将这些字段呈现到顶级 MonoBehaviour 的 属性 抽屉中?

图片形式

我有什么

(双击元素)

我想要的

我有自己的 [CustomEditor] 组件,但我无法让它正常工作;像这样的东西:

SerializedProperty activityStack = serializedObject.FindProperty("activityStack");
EditorGUILayout.PropertyField(activityStack.GetArrayElementAtIndex(0));

只呈现“元素 0(空闲 Activity)”位,而不是引用的实际内容。

因为 ScriptableObject 的默认值 PropertyField 就是您得到的:一个 UnityEngine.Object 参考字段,例如游戏对象和组件以及其他资产 ;)


当然你可以实现你想要实现的,但这有点复杂,不太利于维护,我不推荐它.


我不知道你的 ScriptableObject 所以这里有一个例子

public class ExampleSO : ScriptableObject
{
    public int SomeInt;
    [SerializeField] private string _someString;
}

和您的 MonoBehaviour 例如

public class Example : MonoBehaviour
{
    public List<ExampleSO> _SOList;
}

然后编辑器可能看起来像

using UnityEditor;
using UnityEngine;

// This is the namespace for the ReorderableList
using UnityEditorInternal;

[CustomEditor(typeof(Example))]
public class ExampleEditor : Editor
{
    SerializedProperty _SOList;

    Example _example;
    MonoScript _script;

    ReorderableList _list;

    private void OnEnable()
    {
        // Link up the serializedProperty
        _SOList = serializedObject.FindProperty("_SOList");

        // get the casted target instance (only needed for drawing the script field)
        _example = (Example) target;

        // get the according script instance (only needed for drawing the script field)
        _script = MonoScript.FromMonoBehaviour(_example);

        // Set up the ReorderableList
        _list = new ReorderableList(serializedObject, _SOList, true, true, true, true)
        {
            // What shall be displayed as header for the list?
            drawHeaderCallback = (Rect rect) => EditorGUI.LabelField(rect, _SOList.displayName),

            // How is each element displayed?
            drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
            {
                // Get the element in the list (SerializedProperty)
                var element = _SOList.GetArrayElementAtIndex(index);

                // and draw the default object reference field
                EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), element, new GUIContent("Reference"));

                // Check if an asset is referenced - if not we are done here
                if (!element.objectReferenceValue) return;

                // Otherwise get the SerializedObject for this asset
                var elementSerializedObject = new SerializedObject(element.objectReferenceValue);

                // and all the properties (SerializedProperty) of it you want to display
                var someInt = elementSerializedObject.FindProperty("SomeInt");
                var someString = elementSerializedObject.FindProperty("_someString");

                // Similar to the OnInspectorGUI first load the current values into this serializedobject
                elementSerializedObject.Update();
                {
                    // Adding some indentation just to show that the following fields are actually belonging to the referenced asset
                    EditorGUI.indentLevel++;
                    {
                        rect = EditorGUI.IndentedRect(rect);

                        // shift down the rect by one line
                        rect.y += EditorGUIUtility.singleLineHeight;

                        // Draw the field for the Int 
                        EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), someInt);

                        // Shift down the rect another line
                        rect.y += EditorGUIUtility.singleLineHeight;
  
                        // Draw the string field
                        EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), someString);
                    }
                    EditorGUI.indentLevel--;
                }
                // Write back the changed values and trigger the checks for logging dirty states and Undo/Redo
                elementSerializedObject.ApplyModifiedProperties();
            },

            // How much vertical space should be reserved for each element?
            elementHeightCallback = (int index) =>
            {
                // Get the elements serialized property
                var element = _SOList.GetArrayElementAtIndex(index);

                // by default we have only the asset reference -> single line
                var lines = 1;

                // if the asset is referenced adds space for the additional fields
                if (element.objectReferenceValue) lines += 2; // or how many lines you'll need

                return lines * EditorGUIUtility.singleLineHeight;
            }
        };
    }

    public override void OnInspectorGUI()
    {
        // draw th script field
        DrawScriptField();

        // Load the current values into the serializedObject
        serializedObject.Update();
        {
            // let the ReorderableList do its magic
            _list.DoLayoutList();
        }
        // Write back the changed values into the actual instance
        serializedObject.ApplyModifiedProperties();
    }

    // Just draws the usual script field at the top of the Inspector
    private void DrawScriptField()
    {
        EditorGUI.BeginDisabledGroup(true);
        {
            EditorGUILayout.ObjectField("Script", _script, typeof(Example), false);
        }
        EditorGUI.EndDisabledGroup();

        EditorGUILayout.Space();
    }
}

这会产生以下 Inspector。如您所见,我打开了 MonoBehaviour 的 Isnpectors 和 ExampleSO 的两个实例,以显示如何将值接管到实际实例