1

The main script:

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

The dialogue script that create the items name and sentences:

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

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

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

And the editor script:

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

But now I have one variable of the dialogue in the Inspector there I can set the number of dialogues and each dialogue name and sentences.

But under it it's creating more Dialogues according to the number of dialogues I set.

Dialogues

What I want to have instead in the Inspector is one main Dialogues:

Then inside it I can set the number of dialogues. For example if I set 5 then under Dialogues there will be: Dialogue 1 Dialogue 2 Dialogue 3 Dialogue 4 Dialogue 5

And then inside under each Dialogue for example Dialogue 1 there will be the Name and Sentences of it. With the able to change the sentences size of each dialogue.

Dubi Duboni
  • 813
  • 2
  • 17
  • 33

2 Answers2

3

The problem is that EditorGUILayout.PropertyField by default doesn't support nested properties.

The simplest fix would be to use the correct overload PropertyField(SerializedProperty property, GUIContent label, bool includeChildren, params GUILayoutOption[] options);

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

enter image description here


Note there are other more custumizeable solutions. Another quick one might e.g. be to manually get those nested properties and define how they should be drawn:

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

You can again take a step forward and instead use a complete CustomPropertyDrawer for your Dialogue class. The huge advantage of this would be that not only in this one class DialogueTrigger but everywhere where you ever have a public Dialogue field it would be displayed using the custom drawer instead!


Or if you really want fancy Lists (reorderable), simple to remove elements at any index etc I strongly recommend to have a look at ReorderableList. It is a not documented feature Unity uses e.g. in the UnityEvent (like onClick) and a bit complex to get into it but as soon as you got it once it is really powerful! (In my question here we also solved how to use this for nested Lists like in your case.)

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • I tried now your solution there is a problem. When I change the Inspector the Dialogue size it keep adding more Elements (Element 1 , Element 2...) under the Dialogue. Instead adding new Dialogue 1 , Dialogue 2, Dialogue 3...And also give me exception ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. on the line 35: dialogueFoldout[i] = EditorGUILayout.Foldout(dialogueFoldout[i], "Dialogue " + i); – Dubi Duboni Mar 29 '19 at 11:43
  • You can see in this screenshot in the link what I mean in the Inspector. And if I only move the mouse to the Sentences size box of Dialogue 0 it throw the exception. https://imgur.com/a/B4mzWKp – Dubi Duboni Mar 29 '19 at 11:47
  • I see what is the problem: The size of dialogueFoldout is only 1 while the size of _dialogues.arraySize is 4 so inside the loop for (var i = 0; i < _dialogues.arraySize; i++) it's throwing the exception. Not sure why it's adding only one item to the dialogueFoldout. – Dubi Duboni Mar 29 '19 at 13:14
  • 1
    You mean the second solution, right? The first should make no trouble at all. currently I can't further debug it I'm sorry. For now maybe stick with the first solution. It is surely something about [this thread](https://answers.unity.com/questions/1174593/unable-to-solve-argumentexception-getting-control.html) – derHugo Mar 29 '19 at 14:28
  • I'm using now the first solution. But I don't have the Size property like in your animated gif. And I have two dialogues (Dialogue 0 and Dialogue 1) But I didn't set yet any size so why it's showing two dialogues already ? – Dubi Duboni Mar 29 '19 at 19:14
  • This is working fine. How can I add a top level call it Conversations and when I change the size of it for example set it to 2 there will be two conversations and under each one I will set how many dialogues to add ? – Dubi Duboni Mar 30 '19 at 02:53
1

If you use the SerializeField property on your list of dialogues you will get the root element of "Dialogue" where you can specify the number of elements in the list and each child will be an instance of the dialog class, and if you serialize the field in an editor script the elements will also update if you add to the list in script.

Edit: You will need to update your editor script as well, if you want to add element from the editor script you can grab an instance of the class from the game object and just add elements to the list (so long as the list is public)

Example

Script.cs

[SerializeField]
public List<Dialogue> dialogue = new List<Dialogue>();

Editor.cs

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

    Script script = GameObject.Find("GameObject").GetComponent<Script>();
    script.dialogue.Add(new Dialogue());
    EditorUtility.SetDirty(script);
}

This is what it should look like in the editor

Airwarfare
  • 36
  • 1
  • 6
  • I added the SerializeField above the List but how the editor script should look like now ? I still have it like in the screenshot in my question. – Dubi Duboni Mar 28 '19 at 22:47
  • @DubiDuboni I have edited my question to further clarify, keep in mind OnInspectorGUI() will call multiple times, so the above code will keep adding elements to your list, its just for example purposes. – Airwarfare Mar 28 '19 at 22:55
  • It didn't draw Dialogue 1, Dialogue 2....But keep showing element 1 Element 2.... – Dubi Duboni Mar 28 '19 at 23:11
  • if you want it to say "Dialogue 1" rather than element 1 just update the name variable of the Dialogue class, that will change the name of the child in the inspector – Airwarfare Mar 28 '19 at 23:14