问题描述
这就是我声明列表的方式:
使用系统;使用 System.Collections;使用 System.Collections.Generic;使用 System.IO;使用 System.Linq;使用 UnityEditor;使用 UnityEngine;使用 UnityEditorInternal;公共类 ConversationTrigger : MonoBehaviour{公开列表<对话>对话 = 新列表<对话>();
这是使用此列表的编辑器脚本:
使用 System.Collections;使用 System.Collections.Generic;使用 UnityEditor;使用 UnityEditorInternal;使用 UnityEngine;[自定义编辑器(typeof(ConversationTrigger))]公共类 ConversationTriggerEditor :编辑器{私有 ReorderableList 对话列表;私有无效 OnEnable(){ConversationTrigger 对话触发器 = (ConversationTrigger)target;ConversationsList = new ReorderableList(serializedObject, serializedObject.FindProperty("conversations"), true, true, true, true);}公共覆盖无效 OnInspectorGUI(){serializedObject.Update();ConversationsList.DoLayoutList();serializedObject.ApplyModifiedProperties();EditorUtility.SetDirty(target);}}
这是使用 ReorderableList 之前 Inspector 的屏幕截图.我可以点击一个对话项目并折叠它,然后点击每个孩子再次折叠它,依此类推:
这是使用 ReorderableList 时的屏幕截图:
现在我只能拖动项目并更改位置,当点击 + 时,它会添加最后一个项目的副本.
我现在想做的事情我至少能想到三件事:
-
在选择现有项目时,例如打开,然后单击+在开口下添加新的空对话项.如果我选择了 Magic,在最后添加一个空的新项目,如果选择了中间 Locked Room 中的一个,在它后面添加空项目.这个想法是在选中的项目之后添加空项目.
删除与所选项目相同的项目时
如何在单击其中一个项目或制作箭头以折叠项目并在我的问题(例如 Opening 和其他问题)中的屏幕截图中显示所有孩子时如何做到这一点?
我花了很长时间,但我喜欢 EditorScripting :D
假设你的数据结构是这样的
public class ConversationTrigger : MonoBehaviour{公开列表<对话>谈话;public void SaveConversations() { }public void LoadConversations() { }}[可序列化]公开课对话{公共字符串名称;公共布尔折叠;公开列表<对话>对话;}[可序列化]公开课对话{公共字符串名称;公共布尔折叠;公共列表<字符串>句子;}
我想出了以下 EditorScript.这个想法是基于我以前关于
添加对话和句子,但仍然可以重新排列和(展开)折叠所有内容
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 :
And this screenshot is when using the 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 :
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.
When removing item/s same the selected one/s
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 ?
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 ReorderableList
s 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 Conversation property</returns>
private float GetConversationHeight(SerializedProperty conversation)
{
var foldout = conversation.FindPropertyRelative("Foldout");
// if not foldout the height is simply 1 line
var height = EditorGUIUtility.singleLineHeight;
// otherwise we sum up every controls and child heights
if (foldout.boolValue)
{
// we need some more lines:
// for the Name field,
// the list header,
// the list buttons and a bit buffer
height += EditorGUIUtility.singleLineHeight * 5;
var dialogues = conversation.FindPropertyRelative("Dialogues");
for (var d = 0; d < dialogues.arraySize; d++)
{
var dialog = dialogues.GetArrayElementAtIndex(d);
height += GetDialogueHeight(dialog);
}
}
return height;
}
/// <summary>
/// Returns the height of given Dialogue property
/// </summary>
/// <param name="dialog"></param>
/// <returns>height of given Dialogue property</returns>
private float GetDialogueHeight(SerializedProperty dialog)
{
var foldout = dialog.FindPropertyRelative("Foldout");
// same game for the dialog if not foldout it is only a single line
var height = EditorGUIUtility.singleLineHeight;
// otherwise sum up controls and child heights
if (foldout.boolValue)
{
// we need some more lines:
// for the Name field,
// the list header,
// the list buttons and a bit buffer
height += EditorGUIUtility.singleLineHeight * 4;
var sentences = dialog.FindPropertyRelative("Sentences");
// the sentences are easier since they always have the same height
// in this example 2 lines so simply do
// at least have space for 1 sentences even if there is none
height += EditorGUIUtility.singleLineHeight * Mathf.Max(1, sentences.arraySize) * 2;
}
return height;
}
#endregion
#endregion Helpers
}
Adding new Conversations, give them names, fold them. If a conversation is selected add the new conversation after it
Adding dialogues and sentences and still being able to rearrange and (un)fold everything
这篇关于如何在检查器中使用带有列表的 ReorderableList 并添加新的空项目和折叠子项?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,WP2