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 能找出多少不同,而取决于它能否长期保存作者的继承意图。