Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
414 views
in Technique[技术] by (71.8m points)

c# - How can I use ReorderableList with a List in the Inspector and adding new empty items and collapse childs?

This is how I declared the List :

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEditorInternal;

public class ConversationTrigger : MonoBehaviour
{
    public List<Conversation> conversations = new List<Conversation>();

This is the editor script using this List :

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

[CustomEditor(typeof(ConversationTrigger))]
public class ConversationTriggerEditor : Editor
{
    private ReorderableList ConversationsList;

    private void OnEnable()
    {
        ConversationTrigger conversationtrigger = (ConversationTrigger)target;
        ConversationsList = new ReorderableList(serializedObject, serializedObject.FindProperty("conversations"), true, true, true, true); 
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        ConversationsList.DoLayoutList();
        serializedObject.ApplyModifiedProperties();
        EditorUtility.SetDirty(target);
    }
}

This is a screenshot of the Inspector before using the ReorderableList. I could click on a Conversation item and collapse it and then on each child to collapse it again and so on :

Before using ReorderableList

And this screenshot is when using the ReorderableList :

When using ReorderableList

Now I can only drag the items and change places and when clicking on + it will add a duplication of the last item.

What I want to do for now I can think of at least 3 things :

  1. When selecting existing item for example Opening and then if clicking on the + add a new empty Conversation item under the Opening. If I selected the Magic add empty new one to the end and if selected the one in the middle Locked Room add empty item after it. The idea is to add empty item after the selected item.

  2. When removing item/s same the selected one/s

  3. How can I make that when clicking double click on one of the items or making a arrow that will collapse the items and show all the childs like in my screenshot in my question like Opening and others ?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It took me quite a while but I love EditorScripting :D

Assuming your data structure looks like this

public class ConversationTrigger : MonoBehaviour
{
    public List<Conversation> conversations;

    public void SaveConversations() { }

    public void LoadConversations() { }
}

[Serializable]
public class Conversation
{
    public string Name;
    public bool Foldout;
    public List<Dialogue> Dialogues;
}

[Serializable]
public class Dialogue
{
    public string Name;
    public bool Foldout;
    public List<string> Sentences;
}

I came up with the following EditorScript. The idea is based on a former question of mine on How to select elements in nested reordeablelist.

It got quite compley as usual for EditorScript. I tried to comment a lot but if anything is unclear just ask me in the comments.

It is a huge pity that the ReorderableList is not a documented features since it is so extremly powerful and useful ...

There are multiple things you have to override:

  • drawHeaderCallback
  • drawElementCallback
  • elementHeightCallback
  • onAddCallback

and in order to be able to ineract with them nested store the different ReorderableLists in dictionaries:

[CustomEditor(typeof(ConversationTrigger))]
public class ConversationTriggerEditor : Editor
{
    private ConversationTrigger _conversationTrigger;

    [SerializeField] private ReorderableList conversationsList;

    private SerializedProperty _conversations;

    private int _currentlySelectedConversationIndex = -1;

    private readonly Dictionary<string, ReorderableList> _dialoguesListDict = new Dictionary<string, ReorderableList>();
    private readonly Dictionary<string, ReorderableList> _sentencesListDict = new Dictionary<string, ReorderableList>();

    private void OnEnable()
    {
        _conversationTrigger = (ConversationTrigger)target;
        _conversations = serializedObject.FindProperty("conversations");

        conversationsList = new ReorderableList(serializedObject, _conversations)
        {
            displayAdd = true,
            displayRemove = true,
            draggable = true,

            drawHeaderCallback = DrawConversationsHeader,

            drawElementCallback = DrawConversationsElement,

            onAddCallback = (list) =>
            {
                SerializedProperty addedElement;
                // if something is selected add after that element otherwise on the end
                if (_currentlySelectedConversationIndex >= 0)
                {
                    list.serializedProperty.InsertArrayElementAtIndex(_currentlySelectedConversationIndex + 1);
                    addedElement = list.serializedProperty.GetArrayElementAtIndex(_currentlySelectedConversationIndex + 1);
                }
                else
                {
                    list.serializedProperty.arraySize++;
                    addedElement = list.serializedProperty.GetArrayElementAtIndex(list.serializedProperty.arraySize - 1);
                }

                var name = addedElement.FindPropertyRelative("Name");
                var foldout = addedElement.FindPropertyRelative("Foldout");
                var dialogues = addedElement.FindPropertyRelative("Dialogues");

                name.stringValue = "";
                foldout.boolValue = true;
                dialogues.arraySize = 0;
            },

            elementHeightCallback = (index) =>
            {
                return GetConversationHeight(_conversations.GetArrayElementAtIndex(index));
            }
        };
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        // if there are no elements reset _currentlySelectedConversationIndex
        if (conversationsList.serializedProperty.arraySize - 1 < _currentlySelectedConversationIndex) _currentlySelectedConversationIndex = -1;

        conversationsList.DoLayoutList();

        if (GUILayout.Button("Save Conversations"))
        {
            _conversationTrigger.SaveConversations();
        }

        if (GUILayout.Button("Load Conversations"))
        {
            Undo.RecordObject(_conversationTrigger, "Loaded conversations from JSON");
            _conversationTrigger.LoadConversations();
        }

        serializedObject.ApplyModifiedProperties();
    }

    #region Drawers

    #region List Headers

    private void DrawConversationsHeader(Rect rect)
    {
        EditorGUI.LabelField(rect, "Conversations");
    }

    private void DrawDialoguesHeader(Rect rect)
    {
        EditorGUI.LabelField(rect, "Dialogues");
    }

    private void DrawSentencesHeader(Rect rect)
    {
        EditorGUI.LabelField(rect, "Sentences");
    }

    #endregion List Headers

    #region Elements

    private void DrawConversationsElement(Rect rect, int index, bool isActive, bool isFocused)
    {
        if (isActive) _currentlySelectedConversationIndex = index;

        var conversation = _conversations.GetArrayElementAtIndex(index);

        var position = new Rect(rect);

        var name = conversation.FindPropertyRelative("Name");
        var foldout = conversation.FindPropertyRelative("Foldout");
        var dialogues = conversation.FindPropertyRelative("Dialogues");
        string dialoguesListKey = conversation.propertyPath;

        EditorGUI.indentLevel++;
        {
            // make the label be a foldout
            foldout.boolValue = EditorGUI.Foldout(new Rect(position.x, position.y, 10, EditorGUIUtility.singleLineHeight), foldout.boolValue, foldout.boolValue ? "" : name.stringValue);

            if (foldout.boolValue)
            {
                // draw the name field
                name.stringValue = EditorGUI.TextField(new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight), name.stringValue);
                position.y += EditorGUIUtility.singleLineHeight;

                if (!_dialoguesListDict.ContainsKey(dialoguesListKey))
                {
                    // create reorderabl list and store it in dict
                    var dialoguesList = new ReorderableList(conversation.serializedObject, dialogues)
                    {
                        displayAdd = true,
                        displayRemove = true,
                        draggable = true,

                        drawHeaderCallback = DrawDialoguesHeader,

                        drawElementCallback = (convRect, convIndex, convActive, convFocused) => { DrawDialoguesElement(_dialoguesListDict[dialoguesListKey], convRect, convIndex, convActive, convFocused); },

                        elementHeightCallback = (dialogIndex) =>
                        {
                            return GetDialogueHeight(_dialoguesListDict[dialoguesListKey].serializedProperty.GetArrayElementAtIndex(dialogIndex));
                        },

                        onAddCallback = (list) =>
                        {
                            list.serializedProperty.arraySize++;
                            var addedElement = list.serializedProperty.GetArrayElementAtIndex(list.serializedProperty.arraySize - 1);

                            var newDialoguesName = addedElement.FindPropertyRelative("Name");
                            var newDialoguesFoldout = addedElement.FindPropertyRelative("Foldout");
                            var sentences = addedElement.FindPropertyRelative("Sentences");

                            newDialoguesName.stringValue = "";
                            newDialoguesFoldout.boolValue = true;
                            sentences.arraySize = 0;
                        }
                    };
                    _dialoguesListDict[dialoguesListKey] = dialoguesList;
                }

                _dialoguesListDict[dialoguesListKey].DoList(new Rect(position.x, position.y, position.width, position.height - EditorGUIUtility.singleLineHeight));
            }

        }
        EditorGUI.indentLevel--;
    }

    private void DrawDialoguesElement(ReorderableList list, Rect rect, int index, bool isActive, bool isFocused)
    {
        if (list == null) return;

        var dialog = list.serializedProperty.GetArrayElementAtIndex(index);

        var position = new Rect(rect);

        var foldout = dialog.FindPropertyRelative("Foldout");
        var name = dialog.FindPropertyRelative("Name");

        {
            // make the label be a foldout
            foldout.boolValue = EditorGUI.Foldout(new Rect(position.x, position.y, 10, EditorGUIUtility.singleLineHeight), foldout.boolValue, foldout.boolValue ? "" : name.stringValue);

            var sentencesListKey = dialog.propertyPath;
            var sentences = dialog.FindPropertyRelative("Sentences");

            if (foldout.boolValue)
            {
                // draw the name field
                name.stringValue = EditorGUI.TextField(new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight), name.stringValue);
                position.y += EditorGUIUtility.singleLineHeight;

                if (!_sentencesListDict.ContainsKey(sentencesListKey))
                {
                    // create reorderabl list and store it in dict
                    var sentencesList = new ReorderableList(sentences.serializedObject, sentences)
                    {
                        displayAdd = true,
                        displayRemove = true,
                        draggable = true,

                        // header for the dialog list
                        drawHeaderCallback = DrawSentencesHeader,

                        // how a sentence is displayed
                        drawElementCallback = (sentenceRect, sentenceIndex, sentenceIsActive, sentenceIsFocused) =>
                        {
                            var sentence = sentences.GetArrayElementAtIndex(sentenceIndex);

                            // draw simple textArea for sentence
                            sentence.stringValue = EditorGUI.TextArea(sentenceRect, sentence.stringValue);
                        },

                        // Sentences have simply a fixed height of 2 lines
                        elementHeight = EditorGUIUtility.singleLineHeight * 2,

                        // when a sentence is added
                        onAddCallback = (sentList) =>
                        {
                            sentList.serializedProperty.arraySize++;
                            var addedElement = sentList.serializedProperty.GetArrayElementAtIndex(sentList.serializedProperty.arraySize - 1);      

                            addedElement.stringValue = "";
                        }
                    };

                    // store the created ReorderableList
                    _sentencesListDict[sentencesListKey] = sentencesList;
                }

                // Draw the list
                _sentencesListDict[sentencesListKey].DoList(new Rect(position.x, position.y, position.width, position.height - EditorGUIUtility.singleLineHeight));
            }
        }
    }

    #endregion Elements

    #endregion Drawers


    #region Helpers

    #region HeightGetter

    /// <summary>
    /// Returns the height of given Conversation property
    /// </summary>
    /// <param name="conversation"></param>
    /// <returns>height of given Convers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...