移动到下一个属性。
更新 SerializedProperty,使其指向下一个属性(按序列化顺序)。
这可以用于遍历对象的状态,而无需事先了解其数据结构。它也是迭代数组的有效方法。
如果当前属性是复合类型,例如结构体、数组、字符串或内联 Unity 结构体(如 Vector3),则 enterChildren 参数决定是访问嵌套属性,还是跳过到复合类型后面的属性。
Next 永远不会移动到不同的对象,因此当当前属性类型为 SerializedPropertyType.ObjectReference 时,调用 Next(true) 不会访问该引用对象的属性。访问引用对象的一种方法是使用 SerializedProperty.objectReferenceValue 获取该对象,并为该目标构造一个新的 SerializedObject 实例。
如果 SerializedObject 中没有更多属性,则此方法将返回 false,并且 SerializedProperty 将设置为无效状态,不再引用任何属性。
using System; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEditor;
public class SerializePropertyNextExample : ScriptableObject { public long m_dataFirst = 45; public string m_data2 = "hi";
[Serializable] class NestedInline { public long[] m_data3 = new long[] {1, 2}; public bool m_data4 = true; } [SerializeField] NestedInline m_nested;
[MenuItem("Example/SerializedProperty Next Example")] static void NextExample() { var scriptableObject = ScriptableObject.CreateInstance<SerializePropertyNextExample>();
using (var serializedObject = new SerializedObject(scriptableObject)) { var sb = new StringBuilder();
// Visit the top level (depth 0) properties: m_dataFirst, m_data2 and m_nested VisitAll(serializedObject, false, sb);
// Visit each property, down to the granularity of individual string and array elements VisitAll(serializedObject, true, sb); Debug.Log(sb.ToString()); } }
static void VisitAll(SerializedObject serializedObject, bool visitChildren, StringBuilder report) { report.AppendLine($"Traversal result (visitChildren: {visitChildren})");
// Start at the first field, instead of using GetIterator(), in order to skip past the internal properties var serializedProperty = serializedObject.FindProperty("m_dataFirst"); do { report.AppendLine($"\tFound {serializedProperty.propertyPath} (depth {serializedProperty.depth})"); } while (serializedProperty.Next(visitChildren)); } }
using System; using System.Text; using UnityEngine; using UnityEditor;
public class SerializePropertyArrayEnumerationWithNextExample : ScriptableObject { // Example of enumerating the elements of an array of struct using Next() [Serializable] public struct Data { public int m_Data1; public string m_Data2; }
public Data[] m_Data; public bool m_BeyondData;
[MenuItem("Example/SerializedProperty Array Enumeration Using Next")] static void TestArrayEnumeration() { var scriptableObject = ScriptableObject.CreateInstance<SerializePropertyArrayEnumerationWithNextExample>(); scriptableObject.m_Data = new Data[] { new Data() { m_Data1 = 0, m_Data2 = "zero" }, new Data() { m_Data1 = 1, m_Data2 = "one" }, new Data() { m_Data1 = 2, m_Data2 = "two" } };
var report = new StringBuilder();
using (var serializedObject = new SerializedObject(scriptableObject)) { var arrayProperty = serializedObject.FindProperty("m_Data");
var arrayElement = arrayProperty.GetArrayElementAtIndex(0); var arraySize = arrayProperty.arraySize;
for (int i = 0; i < arraySize; i++) { ReportElement(arrayElement, i, report); arrayElement.Next(false); } } Debug.Log(report.ToString()); }
static void ReportElement(SerializedProperty arrayElement, int index, StringBuilder report) { var data1 = arrayElement.FindPropertyRelative("m_Data1").intValue; var data2 = arrayElement.FindPropertyRelative("m_Data2").stringValue; report.AppendLine($"Visiting Index {index}: {data1}, {data2}"); } }
如果 SerializedProperty 引用的是 ManagedReference(例如,带有 SerializeReference 属性的字段),并且 Next 被调用,enterChildren 设置为 true,则 SerializedProperty 将移动到托管对象中的第一个属性。由于托管引用可以形成循环图,因此盲目调用 Next(true) 可能会导致无限循环。以下示例演示了避免这种情况的技术。
using System; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEditor;
[CreateAssetMenu] public class SerializePropertyNextSerializeReferenceExample : ScriptableObject { [Serializable] public class Node { public int m_Data; public string m_Data2;
[SerializeReference] public Node m_Child1 = null;
[SerializeReference] public Node m_Child2 = null; }
[SerializeReference] public Node m_Front = null;
[MenuItem("Example/SerializedProperty Next Cyclic Graph Example")] static void TestNextOnCyclicGraph() { var scriptableObject = ScriptableObject.CreateInstance<SerializePropertyNextSerializeReferenceExample>();
// Setup a graph of 3 nodes that has several cycles scriptableObject.m_Front = new Node() { m_Data = 1, m_Data2 = "First Node" };
var node2 = new Node() { m_Data = 2, m_Data2 = "Second Node", m_Child1 = scriptableObject.m_Front }; scriptableObject.m_Front.m_Child1 = node2;
var node3 = new Node() { m_Data = 3, m_Data2 = "Third Node" }; scriptableObject.m_Front.m_Child2 = node3; node2.m_Child2 = node3; node3.m_Child1 = scriptableObject.m_Front;
using (var serializedObject = new SerializedObject(scriptableObject)) { var serializedProperty = serializedObject.FindProperty("m_Front");
var sb = new StringBuilder(); sb.AppendLine("Graph traversal result:");
// Track visited Node objects by managed reference id to prevent endless looping var visitedNodes = new HashSet<long>();
bool visitChild; do { // default is false so we don't enumerate each character of each string, visitChild = false;
if (serializedProperty.propertyType == SerializedPropertyType.ManagedReference) { long refId = serializedProperty.managedReferenceId; if (visitedNodes.Add(refId)) visitChild = true; // First time seeing node, so visit it } else if (serializedProperty.propertyType == SerializedPropertyType.String) { sb.AppendLine($"Found {serializedProperty.propertyPath} : {serializedProperty.stringValue}"); } else if (serializedProperty.propertyType == SerializedPropertyType.Integer) { sb.AppendLine($"Found {serializedProperty.propertyPath} : {serializedProperty.intValue}"); } } while (serializedProperty.Next(visitChild));
/*Expected output Graph traversal result: Found m_Front.m_Data : 1 Found m_Front.m_Data2 : First Node Found m_Front.m_Child1.m_Data : 2 Found m_Front.m_Child1.m_Data2 : Second Node Found m_Front.m_Child1.m_Child2.m_Data : 3 Found m_Front.m_Child1.m_Child2.m_Data2 : Third Node */ Debug.Log(sb.ToString()); } } }