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
的两个实例,以显示如何将值接管到实际实例
在 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
的两个实例,以显示如何将值接管到实际实例