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 写回目标对象。
基本使用步骤
-
创建 SerializedObject 实例
将目标对象(或对象数组)传入构造函数。如果传入多个对象,SerializedObject将允许你同时编辑它们共享的序列化数据。SerializedObject so = new SerializedObject(targetObject); -
获取 SerializedProperty
使用FindProperty根据字段名查找属性。字段名通常与 Inspector 中显示的属性路径一致(可在 Inspector 中 Shift+右键点击属性标签来复制路径)。SerializedProperty prop = so.FindProperty("myInt");
-
读取或修改值
通过SerializedProperty提供的类型化变量(如intValue、floatValue、stringValue等)进行操作。prop.intValue = 42;
-
应用修改
调用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 的用法都能显著提升工具的健壮性和用户体验。
浙公网安备 33010602011771号