Prefab覆盖:没有基线版本的 Diff 只是一次猜测

自动关联目录:Prefab覆盖:没有基线版本的 Diff 只是一次猜测

核心判断:Prefab 实例的 Override 不是“当前值与模板值不同”,而是“实例相对某个明确模板 Revision 的结构化补丁”。没有基线身份,Apply、Reset 和模板升级都无法确定语义。

一、业务问题:值相同也可能不是同一种状态

模板把颜色从白色改成红色。实例当前也是红色,可能是继承后的结果,也可能是作者此前显式覆盖成红色。两者显示一致,但下次模板改成蓝色时行为不同。只比较最终值会丢失“继承还是覆盖”的意图。

结构变化更复杂:模板新增子节点、删除节点或调整层级后,实例中的路径可能不再对应原节点。若 Diff 只用数组下标或运行时对象号定位,模板升级会把补丁打到错误对象。

二、实际实现:已经建立单一比较器与相对路径

工程把 Prefab 差异判断集中到一个比较器,C++ 侧只搬运“是否存在基线、子树是否被覆盖”的结果,避免多个语言层各自定义相等规则。场景拓扑还记录相对最近 Prefab 根的路径,并对不应参与模板结构的中间节点做跳过处理。

这个方向值得保留:Override 规则只有一个权威实现;编辑会话与主场景拥有独立树和撤销栈;保存为模板、覆盖模板、查询子树差异不会各写一套递归比较。

三、缺口:路径与比较器还需要 Revision 契约

仅有 path -> changed fields 仍不足以应对模板演进。补丁必须声明自己的基线摘要,并区分属性、结构和删除墓碑:

{
  "prefabId": "logical-id",
  "baseRevision": "content-digest",
  "ops": [
    {"op":"set", "node":"stable-child-id", "field":"Color", "value":"red"},
    {"op":"remove", "node":"stable-child-id-2"}
  ]
}

加载时若 baseRevision 与当前模板一致,可直接应用;不一致则做三方合并:旧模板是 Base,新模板是 Theirs,实例补丁是 Ours。节点重命名或移动依赖稳定子节点身份,不依赖位置路径。无法自动合并的结构冲突必须显式报告,不能静默丢 Override。

四、Apply 与 Reset 的准确含义

  • Reset field:删除该字段补丁,重新继承模板,不是把当前模板值复制到实例。
  • Apply to prefab:把选定补丁合并为模板的新 Revision,再重新计算所有实例补丁。
  • Detach prefab:物化完整值并删除模板关系,之后不再继承。
  • Upgrade prefab:三方合并并产生冲突清单,不是简单替换模板引用。

验证要覆盖“显式覆盖值恰好等于模板”、模板节点移动、删除后实例仍有补丁、嵌套 Prefab、撤销 Apply,以及旧 Revision 无法取得时的失败策略。Prefab 系统的质量,不取决于 Diff 能找出多少不同,而取决于它能否长期保存作者的继承意图。