Unity SerializedObject 详解

SerializedObject 是 Unity 编辑器脚本中的核心类,它提供了一种通用的方式来安全地读取和修改一个或多个 Unity 对象的序列化字段。与直接通过反射或组件 API 修改对象不同,SerializedObject 能够自动处理撤销(Undo)、预制件(Prefab)覆盖标记、多对象批量编辑以及脏标记等复杂场景,是构建健壮编辑器工具的基础。

为什么需要 SerializedObject?

假设你希望编写一个菜单命令,将当前选中的所有游戏物体的本地坐标归零。如果直接修改 Transform 组件:

[MenuItem("Edit/Reset Selected Objects Position (No Undo)")]
static void ResetPosition()
{
    foreach (var go in Selection.gameObjects)
        go.transform.localPosition = Vector3.zero;
}

 

这段代码虽然能够生效,但存在严重缺陷:操作无法撤销(Ctrl+Z 无效),场景不会标记为已修改,也不会正确处理预制件实例的属性覆盖。如果使用 SerializedObject,这些问题都会自动解决:

[MenuItem("Edit/Reset Selected Objects Position")]
static void ResetPosition()
{
    var transforms = Selection.gameObjects.Select(go => go.transform).ToArray();
    var so = new SerializedObject(transforms);
    so.FindProperty("m_LocalPosition").vector3Value = Vector3.zero;
    so.ApplyModifiedProperties();
}

 

此时,该操作不仅支持撤销,还会正确地将修改记录为场景更改,并在 Inspector 中为预制件覆盖的属性显示蓝色高亮。

核心概念与工作流程

SerializedObject 本身并不直接存储数据,它相当于在目标对象上打开的一个“数据流”。你可以通过它获取 SerializedProperty 实例来访问具体的字段,所有修改最终需要通过 ApplyModifiedProperties 写回目标对象。

基本使用步骤

  1. ‌创建 SerializedObject 实例‌
    将目标对象(或对象数组)传入构造函数。如果传入多个对象,SerializedObject 将允许你同时编辑它们共享的序列化数据。

    SerializedObject so = new SerializedObject(targetObject);
     
  2. ‌获取 SerializedProperty‌
    使用 FindProperty 根据字段名查找属性。字段名通常与 Inspector 中显示的属性路径一致(可在 Inspector 中 Shift+右键点击属性标签来复制路径)。

    SerializedProperty prop = so.FindProperty("myInt");
     
  3. ‌读取或修改值‌
    通过 SerializedProperty 提供的类型化变量(如 intValuefloatValuestringValue 等)进行操作。

    prop.intValue = 42;

     
  4. ‌应用修改‌
    调用 ApplyModifiedProperties() 将所有挂起的修改写回原始对象,并自动处理 Undo 注册和预制件覆盖标记。

    so.ApplyModifiedProperties();
     

同步与更新

如果在多个帧中保持对同一个 SerializedObject 实例的引用,必须注意数据同步。因为目标对象可能在其他地方(例如另一个 SerializedObject 流或用户手动操作)被修改,此时需要先调用 Update() 来刷新内部状态,确保后续读取的是最新数据。

so.Update();          // 从目标对象重新拉取序列化数据
// ... 读取或修改属性 ...
so.ApplyModifiedProperties();
 

两个独立的 SerializedObject 流即使指向相同的目标对象,它们之间也互不干扰,因此跨帧使用时必须手动同步。

与 SerializedProperty 和 Editor 的协作

SerializedObject 最常见的应用场景是在自定义 Editor 类中。Editor 基类已经为你准备好了 serializedObject 属性,它指向当前检查的一个或多个目标对象。在 OnInspectorGUI 中,标准的做法是:

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

    // 绘制属性,Unity 会自动处理嵌套、数组展开等
    EditorGUILayout.PropertyField(serializedObject.FindProperty("myField"));

    serializedObject.ApplyModifiedProperties();
}
 

这种方式不仅代码简洁,还能确保 Inspector 的行为与原生组件完全一致(包括撤销、预制件覆盖样式等)。需要注意的是,serializedObject 只能在 OnInspectorGUI 等 Inspector 回调中使用;在 OnSceneGUI 或 OnPreviewGUI 中应直接使用 target 属性。

多对象编辑

当你在 Inspector 中同时选中多个游戏物体时,Editor 的 serializedObject 会自动包含所有选中的目标。此时 SerializedProperty 的 hasMultipleDifferentValues 属性会告诉你这些对象在该字段上是否具有不同的值,从而可以在 UI 中显示“—”混合值指示。ApplyModifiedProperties 则会将修改统一应用到所有目标对象上。

常用成员一览

表格
成员说明
SerializedObject(obj) 构造函数,为单个对象创建数据流
SerializedObject(objs) 构造函数,为多个对象创建数据流(支持批量编辑)
Update() 从目标对象重新加载序列化数据
ApplyModifiedProperties() 将修改写回目标对象,并处理 Undo 和预制件覆盖
ApplyModifiedPropertiesWithoutUndo() 同上,但不注册 Undo 操作
FindProperty(path) 根据属性路径查找 SerializedProperty
SetIsDifferentCacheDirty() 强制刷新“属性是否不同”的缓存(多对象编辑时有用)
targetObject / targetObjects 获取当前绑定的目标对象

注意事项

  • ‌必须调用 ApplyModifiedProperties‌:通过 SerializedProperty 设置的值只是暂存在数据流中,只有调用该方法后才会真正应用到目标对象。
  • ‌不要跨帧使用而不更新‌:如果持有 SerializedObject 引用超过一帧,务必在读取前调用 Update(),否则可能读到过期数据。
  • ‌避免在非 Inspector 回调中使用 serializedObject‌:在 OnSceneGUI 等场景中,应直接操作 target,或自行创建新的 SerializedObject 实例。
  • ‌属性路径的获取‌:在 Inspector 中 Shift+右键点击属性标签可以复制其序列化路径,方便在代码中使用。
  • ‌性能考虑‌:频繁创建和销毁 SerializedObject 会产生一定开销,但通常不是瓶颈;对于大量对象,可以考虑复用实例并适时 Update()

总结

SerializedObject 是 Unity 编辑器扩展中不可或缺的基石。它通过序列化系统提供了一层抽象,让开发者能够以统一、安全的方式编辑对象属性,而无需关心底层的 Undo 注册、预制件覆盖同步、多对象差异处理等繁琐细节。无论是编写简单的菜单工具,还是构建复杂的自定义 Inspector,掌握 SerializedObject 和 SerializedProperty 的用法都能显著提升工具的健壮性和用户体验。

posted on 2026-05-15 16:54  -冷夜-  阅读(30)  评论(0)    收藏  举报

导航