Verse Wiki — 写给蓝图作者的 Verse 手册
拓展 · EXTRA

版本演化踩坑实录:发布后删不掉的 persistable class

一位创作者想删掉早已弃用的持久化类,结果项目再也发布不出去了。这个论坛真实案例,是「第一次发布前就要设计好存档结构」的最佳反面教材——本文复盘事故、讲清原理、给出可用的退役方案。

一、事故现场:删掉旧类,发布失败

论坛帖《Cannot Remove an Old Persistable Class from a Previously Published UEFN Project》记录了这样一幕:作者项目的早期版本里,有一个叫 CustomPlayer 的持久化类,和它配套的一张存档表(weak_map),后来系统重构,这套东西彻底不用了。清理时他顺手把类定义和那张存档表一起删掉——本地 Compile 一切正常,发布时却失败了,错误信息指向「缺少上一版本里存在过的定义」。

更扎心的是 Epic 员工的回帖:这不是 bug,是预期行为。已发布项目的每一次再发布,都会拿新代码和线上版本做向后兼容检查;从已发布项目里移除 persistable class,就是最典型的不兼容改动。帖中还提到官方曾计划在后续版本(34.40)放宽此限制——是否已经落地,请以最新官方说明为准。

二、为什么删不得:兼容检查在检查什么

直觉上,「我都不用这个类了,删掉碍着谁了?」碍着的是线上已经存在的玩家存档。持久化数据挂在玩家账号上,里面按旧类的结构存着大量记录;发布时的向后兼容检查,本质上就是在核对那张存档表「每格该放什么类型」——它必须保证:任何一份老存档,在新版本下都还能被正确读出来。类型定义没了,这些数据就成了没人认得的孤儿,而引擎的态度和基础课讲过的「加载失败就拒绝入场」一脉相承:宁可不让你发布,也不让存档变成废数据

同样的道理还解释了另一个事实:持久化数据无法真正删除,只能重置——把默认值写回去。数据的「户口」一旦建立,就注销不掉了。

三、可用的退役方案:旧类留守,新系统另开门户

社区沉淀下来的对策很朴素:旧的定义留在代码里退役,但绝不删除;新系统另起炉灶,并行新建一张存档表(weak_map)。像下面这样——上半段是让旧类「带薪退休」,下半段是给新系统开一张全新的存档表:

retire_old_class.verse
# 旧类:已随某个版本发布,删除会导致发布失败
# 让它带薪退休——留着定义,代码里不再使用
custom_player := class<final><persistable>:
    Score:int = 0

var OldCustomPlayers:weak_map(player, custom_player) = map{}

# 新系统:并行另开一个持久化变量
player_profile_v2 := class<final><persistable>:
    Score:int = 0
    Level:int = 1

var ProfilesV2:weak_map(player, player_profile_v2) = map{}

注意这个方案的代价:每个项目的存档表数量有硬上限(目前 4 张),退役的那张 weak_map 仍然白占一个名额。这也是为什么这个案例值得写进教科书——存档结构每「另开一张表」,都在烧掉一份收不回来的预算。顺带一提,如果只是想给旧数据「搬家」,可以在玩家进场时读一次旧存档表、写进新存档表,再把旧记录重置成默认值,慢慢完成迁移。

给自己立三条规矩:第一,第一次发布前,把存档结构在纸上多推演几轮,能用一个带演化余地的 class 解决,就不要开第二个持久化变量;第二,持久化类从第一天起就按「只加不减」设计字段;第三,任何涉及存档的重构,先在私有发布版本上验证兼容检查,再动正式版本。

你已发布的项目里有个不再使用的持久化类 old_stats,现在想彻底重构存档结构。正确做法是?

来源

本文整理自 Epic 开发者论坛(含 Epic 员工确认回复):