SerializedProperty 的值,以 System.Object 的形式封装。
此属性表示 SerializedProperty 的值,作为一个包装底层类型的 System.Object。
此属性使编写不需要 SerializedProperty 精确类型的代码来获取或设置其值变得更容易。例如,此属性可以使用相同的语法访问任何数值类型、字符串、内置类型(如 Vector3)和托管引用对象。此属性可以消除使用 switch-case 语句或基于 .NET 反射的缓慢替代方案来确定 SerializedProperty 类型的需要。
将值类型包装为基于堆的 System.Object 需要一个称为“装箱”的转换,这会增加性能开销。在性能至关重要的并且您提前知道类型的情况下,请使用相应的特定于类型的访问器,例如 intValue、stringValue 或 managedReferenceValue,而不是此属性。这消除了此属性所需的装箱性能开销。
当您的应用程序设置此属性时,Unity 会尝试将提供的 System.Object 拆箱并转换为 SerializedProperty 的属性类型。如果失败,Unity 会抛出 InvalidCastException 错误。
boxedValue 对类型为 SerializedPropertyType.Generic 的属性有一些限制。
其他资源:propertyType。
using UnityEditor; using UnityEngine; using System.Collections.Generic;
// To try this example save it as a script called BoxedValueStructExample.cs, // then create an asset file from the Project Window context menu, then inspect it
[System.Serializable] public struct Element { public int m_IntData; public Color m_ColorData; public Rect m_Rect;
public void Change() { ++m_IntData; m_ColorData = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f), 1); m_Rect = new Rect(Random.Range(0, 100), Random.Range(0, 100), Random.Range(0, 100), Random.Range(0, 100)); } };
[CreateAssetMenu] public class BoxedValueStructExample : ScriptableObject { public Element m_NewItem = new Element(); public List<Element> m_ItemList = new List<Element>(); }
[CustomEditor(typeof(BoxedValueStructExample)), CanEditMultipleObjects] public class BoxedValueStructExampleEditor : Editor { SerializedProperty m_NewItemProp; SerializedProperty m_ListProp;
public void OnEnable() { m_NewItemProp = serializedObject.FindProperty("m_NewItem"); m_ListProp = serializedObject.FindProperty("m_ItemList"); }
public override void OnInspectorGUI() { EditorGUILayout.PropertyField(m_NewItemProp);
GUILayout.Space(30);
if (GUILayout.Button("Add New Item to List")) { // Read full Element struct Element newItem = (Element)m_NewItemProp.boxedValue;
// Append a new item to list and set it to the same values as m_NewItem m_ListProp.arraySize++; m_ListProp.GetArrayElementAtIndex(m_ListProp.arraySize - 1).boxedValue = newItem;
// Update NewItem to some new values newItem.Change(); m_NewItemProp.boxedValue = newItem;
// Because boxedValue is used, the code above does not need to deal with fields inside the struct, // and it would not need to change as fields are added and removed to Element }
GUILayout.Space(30); EditorGUILayout.PropertyField(m_ListProp);
serializedObject.ApplyModifiedProperties(); } }
using System.Text; using UnityEditor; using UnityEngine;
public class BoxedValueExample { [MenuItem("Example/Log Property Values")] static void MenuCallback() { var log = new StringBuilder(); var obj = Selection.activeGameObject; { log.AppendLine($"Contents of {obj.name}"); LogProperties(obj, log); }
foreach (var comp in obj.GetComponents<Component>()) { log.AppendLine(); log.AppendLine($"Component {comp.GetType().ToString()}"); LogProperties(comp, log); }
Debug.Log(log.ToString()); }
static void LogProperties(UnityEngine.Object obj, StringBuilder log) { using (var so = new SerializedObject(obj)) { var iterator = so.GetIterator(); iterator.Next(true); // Move past root property
// Printing top level propertise only do { log.Append(iterator.name); log.Append(" type: "); log.Append(iterator.propertyType); LogValue(iterator, log); log.AppendLine(); } while (iterator.Next(false)); } }
static void LogValue(SerializedProperty serializedProperty, StringBuilder log) { // Don't attempt to print these types as strings if (serializedProperty.propertyType == SerializedPropertyType.Generic || serializedProperty.propertyType == SerializedPropertyType.ObjectReference || serializedProperty.propertyType == SerializedPropertyType.ManagedReference) { return; }
log.Append($" value: {serializedProperty.boxedValue}"); }
[MenuItem("Example/Log Property Values", true)] static bool MenuValidation() { return Selection.activeGameObject != null; } }
这是一个递归版本的示例,包含更多信息和格式,但仍然依赖于 boxedValue。
using System.Collections.Generic; using System.Text; using UnityEditor; using UnityEngine;
public class RecursivePropertyLogExample { [MenuItem("Example/Log Property Values (Recursive)")] static void MenuCallback() { var obj = Selection.activeGameObject; { var log = new StringBuilder(); log.AppendLine($"Contents of {obj.name}"); LogProperties(obj, log);
// Log separately to avoid reaching the individual message size limit Debug.Log(log.ToString()); }
foreach (var comp in obj.GetComponents<Component>()) { var log = new StringBuilder(); log.AppendLine($"Component {comp.GetType().ToString()} of {obj.name}"); LogProperties(comp, log); Debug.Log(log.ToString()); } }
static void LogProperties(UnityEngine.Object obj, StringBuilder log) { using (var so = new SerializedObject(obj)) { var iterator = so.GetIterator(); iterator.Next(true); // Move past root property
// Prevent endless loops if SerializeReference instances form cyclical graphs var visitedManagedReferences = new HashSet<long>();
bool visitChild; do { visitChild = false;
if (iterator.propertyType == SerializedPropertyType.Generic) { visitChild = true; } else if (iterator.propertyType == SerializedPropertyType.ManagedReference) { long refId = iterator.managedReferenceId; if (visitedManagedReferences.Add(refId)) visitChild = true; // First time seeing node, so visit it }
for (int i = 0; i < iterator.depth; i++) log.Append(" ");
if (iterator.name == "data") { // If this is an array element then it is more informative to use the name exposed by // propertyPath, e.g. "data[7]" instead of "data". var stringPos = iterator.propertyPath.LastIndexOf('.'); if (stringPos > 0) log.Append(iterator.propertyPath.Substring(stringPos + 1)); else log.Append(iterator.name); } else log.Append(iterator.name);
LogType(iterator, log); LogValue(iterator, log); log.AppendLine();
// Skip past the "Array" child inside each property of type array if (iterator.isArray) iterator.Next(true); } while (iterator.Next(visitChild)); } }
static void LogType(SerializedProperty serializedProperty, StringBuilder log) { log.Append(" type: "); if (serializedProperty.propertyType == SerializedPropertyType.Integer || serializedProperty.propertyType == SerializedPropertyType.Float) log.Append(serializedProperty.numericType); else if (serializedProperty.propertyType == SerializedPropertyType.Generic && serializedProperty.isArray) log.Append("Array"); else log.Append(serializedProperty.propertyType); }
static void LogValue(SerializedProperty serializedProperty, StringBuilder log) { // use boxedValue to get and print the value as a string // There are a few special cases to improve the quality of the output
if (serializedProperty.propertyType == SerializedPropertyType.Generic || serializedProperty.propertyType == SerializedPropertyType.AnimationCurve || serializedProperty.propertyType == SerializedPropertyType.Gradient || serializedProperty.propertyType == SerializedPropertyType.LayerMask) { return; }
if (serializedProperty.propertyType == SerializedPropertyType.ObjectReference) { if (ReferenceEquals(serializedProperty.objectReferenceValue, null)) log.Append($" value: null"); else log.Append($" instanceID: {serializedProperty.objectReferenceValue.GetInstanceID()}"); } else if (serializedProperty.propertyType == SerializedPropertyType.ManagedReference) { if (ReferenceEquals(serializedProperty.managedReferenceValue, null)) log.Append($" value: null"); else log.Append($" refId: {serializedProperty.managedReferenceId} ({serializedProperty.managedReferenceFullTypename})"); } else { log.Append($" value: {serializedProperty.boxedValue.ToString()}"); } }
[MenuItem("Example/Log Property Values (Recursive)", true)] static bool ValidateMenuItem() { return Selection.activeGameObject != null; } }