首页 > 解决方案 > 如何在 Inspector 编辑器脚本中绘制列表及其所有项目?

问题描述

主脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DialogueTrigger : MonoBehaviour
{
    public List<Dialogue> dialogue = new List<Dialogue>();

    [HideInInspector]
    public int dialogueNum = 0;

    private bool triggered = false;

    public void TriggerDialogue()
    {
        if (triggered == false)
        {
            if (FindObjectOfType<DialogueManager>() != null)
            {
                FindObjectOfType<DialogueManager>().StartDialogue(dialogue[dialogueNum]);
                dialogueNum += 1;
            }
            triggered = true;
        }
    }

    private void Update()
    {
        if (DialogueManager.dialogueEnded == true)
        {
            if (dialogueNum == dialogue.Count)
            {
                return;
            }
            else
            {
                FindObjectOfType<DialogueManager>().StartDialogue(dialogue[dialogueNum]);
                DialogueManager.dialogueEnded = false;
                dialogueNum += 1;
            }
        }
    }
}

创建项目名称和句子的对话脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class Dialogue
{
    public string name;

    [TextArea(1, 10)]
    public string[] sentences;
}

和编辑器脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(DialogueTrigger))]
public class DialogueTriggerEditor : Editor
{
    private SerializedProperty _dialogues;

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        _dialogues = serializedObject.FindProperty("dialogue");
        serializedObject.Update();

        for (int i = 0; i < _dialogues.arraySize; i++)
        {
            var dialogue = _dialogues.GetArrayElementAtIndex(i);
            EditorGUILayout.PropertyField(dialogue, new GUIContent("Dialogue " + i));
        }
    }
}

但是现在我在 Inspector 中有一个对话变量,我可以设置对话的数量以及每个对话的名称和句子。

但在它下面,它会根据我设置的对话数量创建更多对话。

对话

我希望在 Inspector 中拥有的是一个主要对话:

然后在里面我可以设置对话的数量。例如,如果我设置 5,那么在 Dialogues 下会有: Dialogue 1 Dialogue 2 Dialogue 3 Dialogue 4 Dialogue 5

然后在每个对话下面,例如对话 1,会有它的名称和句子。能够改变每个对话的句子大小。

标签: c#unity3d

解决方案


问题是EditorGUILayout.PropertyField默认情况下不支持嵌套属性。

最简单的解决方法是使用正确的重载PropertyField(SerializedProperty property, GUIContent label, bool includeChildren, params GUILayoutOption[] options);

这需要bool includeChildren

[CustomEditor(typeof(DialogueTrigger))]
public class DialogueTriggerEditor : Editor
{
    private SerializedProperty _dialogues;

    private void OnEnable()
    {
        // do this only once here
        _dialogues = serializedObject.FindProperty("dialogue");
    }

    public override void OnInspectorGUI()
    {
        //base.OnInspectorGUI();

        serializedObject.Update();

        // Ofcourse you also want to change the list size here
        _dialogues.arraySize = EditorGUILayout.IntField("Size", _dialogues.arraySize);

        for (int i = 0; i < _dialogues.arraySize; i++)
        {
            var dialogue = _dialogues.GetArrayElementAtIndex(i);
            EditorGUILayout.PropertyField(dialogue, new GUIContent("Dialogue " + i), true);
        }

        // Note: You also forgot to add this
        serializedObject.ApplyModifiedProperties();
    }
}

在此处输入图像描述


请注意,还有其他更可定制的解决方案。另一个快速的方法可能是手动获取这些嵌套属性并定义它们应该如何绘制:

[CustomEditor(typeof(DialogueTrigger))]
public class DialogueTriggerEditor : Editor
{
    private SerializedProperty _dialogues;

    // store which dialogue is foldout
    private List<bool> dialogueFoldout = new List<bool>();

    private void OnEnable()
    {
        _dialogues = serializedObject.FindProperty("dialogue");

        for (var i = 0; i < _dialogues.arraySize; i++)
        {
            dialogueFoldout.Add(false);
        }
    }

    public override void OnInspectorGUI()
    {
        //base.OnInspectorGUI();

        serializedObject.Update();

        var color = GUI.color;

        EditorGUI.BeginChangeCheck();
        _dialogues.arraySize = EditorGUILayout.IntField("Size", _dialogues.arraySize);
        if (EditorGUI.EndChangeCheck())
        {
            dialogueFoldout.Clear();

            for (var i = 0; i < _dialogues.arraySize; i++)
            {
                dialogueFoldout.Add(false);
            }

            serializedObject.ApplyModifiedProperties();
            return;
        }

        for (var i = 0; i < _dialogues.arraySize; i++)
        {
            var dialogue = _dialogues.GetArrayElementAtIndex(i);

            dialogueFoldout[i] = EditorGUILayout.Foldout(dialogueFoldout[i], "Dialogue " + i);

            // make the next fields look nested below the before one
            EditorGUI.indentLevel++;

            if (dialogueFoldout[i])
            {
                var name = dialogue.FindPropertyRelative("name");
                var sentences = dialogue.FindPropertyRelative("sentences");

                if (string.IsNullOrWhiteSpace(name.stringValue)) GUI.color = Color.yellow;
                EditorGUILayout.PropertyField(name);
                GUI.color = color;

                // if you still want to be able to controll the size
                sentences.arraySize = EditorGUILayout.IntField("Senteces size", sentences.arraySize);

                // make the next fields look nested below the before one
                EditorGUI.indentLevel++;
                for (var s = 0; s < sentences.arraySize; s++)
                {
                    var sentence = sentences.GetArrayElementAtIndex(s);
                    if (string.IsNullOrWhiteSpace(sentence.stringValue)) GUI.color = Color.yellow;
                    EditorGUILayout.PropertyField(sentence, new GUIContent("Sentece " + s));
                    GUI.color = color;
                }
                EditorGUI.indentLevel--;
            }

            EditorGUI.indentLevel--;
        }

        serializedObject.ApplyModifiedProperties();
    }
}

您可以再次向前迈出一步,而是为您的班级使用完整的CustomPropertyDrawer 。Dialogue这样做的巨大优势在于,不仅在这一类中DialogueTrigger,而且在您拥有字段的任何地方都public Dialogue使用自定义抽屉来显示!


或者,如果您真的想要花哨的列表(可重新排序),可以简单地删除任何索引处的元素等,我强烈建议您查看ReorderableList。这是 Unity 在UnityEvent(like onClick) 中使用的一个未记录的功能,而且使用起来有点复杂,但是一旦你得到它,它就真的很强大了!(在我的问题中,我们还解决了如何将其用于嵌套列表,例如您的情况。)


推荐阅读