一、什么是 SerializedObject 与 SerializedProperty

1.1 基本概念

在编辑器扩展开发里,SerializedObject 和 SerializedProperty 是非常实用的工具。SerializedObject 就像是一个容器,它可以把一个对象序列化,让我们方便地在编辑器里对这个对象的属性进行操作。而 SerializedProperty 呢,它代表的是 SerializedObject 里的一个具体属性,通过它我们能直接访问和修改这个属性的值。

1.2 简单示例

下面我们用 Unity 开发来举个例子,Unity 是一款很流行的游戏开发引擎,在它的编辑器扩展中 these tools 非常常用。

using UnityEditor;
using UnityEngine;

// 定义一个简单的类
public class MyClass : MonoBehaviour
{
    public int myInt = 10; // 定义一个整型属性
    public string myString = "Hello"; // 定义一个字符串属性
}

// 自定义编辑器脚本
[CustomEditor(typeof(MyClass))]
public class MyClassEditor : Editor
{
    private SerializedObject serializedObj;
    private SerializedProperty myIntProp;
    private SerializedProperty myStringProp;

    private void OnEnable()
    {
        // 获取当前编辑对象的 SerializedObject
        serializedObj = new SerializedObject(target); 
        // 获取 myInt 属性对应的 SerializedProperty
        myIntProp = serializedObj.FindProperty("myInt"); 
        // 获取 myString 属性对应的 SerializedProperty
        myStringProp = serializedObj.FindProperty("myString"); 
    }

    public override void OnInspectorGUI()
    {
        // 更新 SerializedObject 中的数据
        serializedObj.Update(); 
        // 绘制 myInt 属性的编辑字段
        EditorGUILayout.PropertyField(myIntProp); 
        // 绘制 myString 属性的编辑字段
        EditorGUILayout.PropertyField(myStringProp); 
        // 将修改保存回原对象
        serializedObj.ApplyModifiedProperties(); 
    }
}

在这个例子中,我们创建了一个 MyClass 类,包含两个公共属性 myIntmyString。然后创建了一个自定义编辑器脚本 MyClassEditor,在 OnEnable 方法里,我们通过 target 获取了当前编辑对象的 SerializedObject,再通过 FindProperty 方法找到了 myIntmyString 对应的 SerializedProperty。在 OnInspectorGUI 方法中,我们先调用 serializedObj.Update() 更新数据,然后使用 EditorGUILayout.PropertyField 绘制属性编辑字段,最后调用 serializedObj.ApplyModifiedProperties() 将修改保存回原对象。

二、SerializedObject 的应用场景

2.1 自定义 Inspector 面板

在 Unity 中,默认的 Inspector 面板展示形式可能不能满足我们的需求,这时候就可以使用 SerializedObject 来自定义 Inspector 面板。比如我们有一个复杂的类,包含很多属性,我们可以按照自己的逻辑来分组展示这些属性。

using UnityEditor;
using UnityEngine;

// 定义一个复杂的类
public class ComplexClass : MonoBehaviour
{
    public int value1;
    public float value2;
    public string value3;
    public bool value4;
}

// 自定义编辑器脚本
[CustomEditor(typeof(ComplexClass))]
public class ComplexClassEditor : Editor
{
    private SerializedObject serializedObj;
    private SerializedProperty value1Prop;
    private SerializedProperty value2Prop;
    private SerializedProperty value3Prop;
    private SerializedProperty value4Prop;

    private void OnEnable()
    {
        serializedObj = new SerializedObject(target);
        value1Prop = serializedObj.FindProperty("value1");
        value2Prop = serializedObj.FindProperty("value2");
        value3Prop = serializedObj.FindProperty("value3");
        value4Prop = serializedObj.FindProperty("value4");
    }

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

        // 分组展示属性
        EditorGUILayout.LabelField("数值属性", EditorStyles.boldLabel);
        EditorGUILayout.PropertyField(value1Prop);
        EditorGUILayout.PropertyField(value2Prop);

        EditorGUILayout.LabelField("文本和布尔属性", EditorStyles.boldLabel);
        EditorGUILayout.PropertyField(value3Prop);
        EditorGUILayout.PropertyField(value4Prop);

        serializedObj.ApplyModifiedProperties();
    }
}

在这个例子中,我们把属性分成了两类,分别展示,这样就能让 Inspector 面板更加清晰。

2.2 数据验证和处理

在编辑器开发中,我们可能需要对输入的数据进行验证。使用 SerializedObject 可以方便地获取和修改属性值,并进行验证。

using UnityEditor;
using UnityEngine;

public class DataValidationClass : MonoBehaviour
{
    public int positiveInt;
}

[CustomEditor(typeof(DataValidationClass))]
public class DataValidationClassEditor : Editor
{
    private SerializedObject serializedObj;
    private SerializedProperty positiveIntProp;

    private void OnEnable()
    {
        serializedObj = new SerializedObject(target);
        positiveIntProp = serializedObj.FindProperty("positiveInt");
    }

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

        EditorGUILayout.PropertyField(positiveIntProp);

        // 数据验证
        if (positiveIntProp.intValue < 0)
        {
            EditorGUILayout.HelpBox("输入的数值必须为正数!", MessageType.Error);
            positiveIntProp.intValue = 0;
        }

        serializedObj.ApplyModifiedProperties();
    }
}

这里我们对 positiveInt 属性进行验证,如果输入的值小于 0,就显示错误提示,并将其值设为 0。

三、SerializedProperty 的应用场景

3.1 遍历数组和列表

当我们需要处理数组或列表类型的属性时,SerializedProperty 能帮助我们方便地遍历其中的元素。

using UnityEditor;
using UnityEngine;

public class ArrayClass : MonoBehaviour
{
    public int[] myArray;
}

[CustomEditor(typeof(ArrayClass))]
public class ArrayClassEditor : Editor
{
    private SerializedObject serializedObj;
    private SerializedProperty arrayProp;

    private void OnEnable()
    {
        serializedObj = new SerializedObject(target);
        arrayProp = serializedObj.FindProperty("myArray");
    }

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

        EditorGUILayout.PropertyField(arrayProp);

        // 遍历数组元素
        if (arrayProp.isArray)
        {
            for (int i = 0; i < arrayProp.arraySize; i++)
            {
                SerializedProperty element = arrayProp.GetArrayElementAtIndex(i);
                EditorGUILayout.IntField($"元素 {i}", element.intValue);
            }
        }

        serializedObj.ApplyModifiedProperties();
    }
}

在这个例子中,我们通过 isArray 判断属性是否为数组,然后使用 GetArrayElementAtIndex 方法获取数组中的每个元素,并进行展示。

3.2 嵌套属性操作

当属性是嵌套结构时,SerializedProperty 可以让我们方便地访问和修改嵌套属性的值。

using UnityEditor;
using UnityEngine;

// 嵌套类
public class InnerClass
{
    public int nestedValue;
}

public class NestedClass : MonoBehaviour
{
    public InnerClass inner;
}

[CustomEditor(typeof(NestedClass))]
public class NestedClassEditor : Editor
{
    private SerializedObject serializedObj;
    private SerializedProperty innerProp;
    private SerializedProperty nestedValueProp;

    private void OnEnable()
    {
        serializedObj = new SerializedObject(target);
        innerProp = serializedObj.FindProperty("inner");
        nestedValueProp = innerProp.FindPropertyRelative("nestedValue");
    }

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

        EditorGUILayout.PropertyField(innerProp);
        EditorGUILayout.PropertyField(nestedValueProp);

        serializedObj.ApplyModifiedProperties();
    }
}

在这个例子中,我们通过 FindPropertyRelative 方法找到了嵌套属性 nestedValue,并展示给用户编辑。

四、技术优缺点

4.1 优点

  • 方便数据操作:SerializedObject 和 SerializedProperty 提供了统一的接口来访问和修改对象的属性,不需要手动去获取和设置属性,减少了代码的复杂性。就像在上面的例子中,我们可以轻松地获取和修改属性值,而不需要直接操作对象的字段。
  • 自动处理序列化:它们会自动处理对象的序列化和反序列化过程,这意味着我们不用担心数据的保存和加载问题。比如在 Unity 编辑器中,当我们修改属性后,使用 ApplyModifiedProperties 方法就能将修改保存,下次打开时数据仍然存在。
  • 支持嵌套属性:对于复杂的对象结构,它们可以方便地访问和修改嵌套属性,如上面的嵌套类示例,使用 FindPropertyRelative 方法就能轻松访问嵌套属性。

4.2 缺点

  • 性能开销:由于涉及到序列化和反序列化操作,会有一定的性能开销。特别是在处理大量属性或者频繁调用 UpdateApplyModifiedProperties 方法时,性能可能会受到影响。
  • 学习成本:对于初学者来说,理解 SerializedObject 和 SerializedProperty 的工作原理和使用方法可能需要花费一些时间。比如,要正确使用 FindPropertyFindPropertyRelative 方法,需要清楚属性的名称和嵌套结构。

五、注意事项

5.1 及时更新和应用修改

在使用 SerializedObject 和 SerializedProperty 时,一定要记得在开始修改属性之前调用 serializedObj.Update() 方法来更新数据,修改完成后调用 serializedObj.ApplyModifiedProperties() 方法将修改保存回原对象。如果不这样做,修改可能不会生效。

5.2 正确处理数组和列表

在处理数组和列表属性时,要注意使用正确的方法,如 isArray 判断属性是否为数组,GetArrayElementAtIndex 获取数组元素,arraySize 获取数组的大小等。同时,对数组大小的修改需要使用 serializedObj.ApplyModifiedProperties() 方法使修改生效。

5.3 避免频繁操作

由于存在性能开销,尽量避免在每一帧都调用 UpdateApplyModifiedProperties 方法。可以在需要更新时再调用,比如用户进行了实际的修改操作。

六、文章总结

在编辑器扩展开发中,SerializedObject 和 SerializedProperty 是非常强大的工具。SerializedObject 就像一个数据容器,它能帮助我们方便地管理对象的属性,而 SerializedProperty 则让我们可以直接操作这些属性。它们在自定义 Inspector 面板、数据验证、处理数组和嵌套属性等方面都有很好的应用。不过,我们也要注意它们的性能开销和学习成本,在使用时遵循一些注意事项,如及时更新和应用修改、正确处理数组和列表、避免频繁操作等。通过合理运用 SerializedObject 和 SerializedProperty,我们可以开发出更加高效、灵活的编辑器扩展。