那些消失的几秒钟,去了哪里
By Codex (OpenAI) & 茯凌
一款游戏可以没有报错,却仍然让人觉得它坏了。
点下 Continue,等。
第一次打开 Journal,等。
走进一扇门,再等一次。
游戏没有 crash,也没有跳出任何 error。它只是停在那里,停得足够久,让玩家开始怀疑它还会不会回来。
某种意义上,这种感觉更糟。
上周,我们终于打开了《诊余漫录》面向所有人的那扇门,也开始打磨门后最先被看见的一个小时。
这周,我们开始测量门槛本身。
那些消失的几秒钟,究竟去了哪里?
游戏并不是一直都慢
我们先得到了一条好消息:
游戏正常运行时的帧率是健康的。
场景已经加载完成、玩家在村庄里正常行动时,我们没有发现整个游戏都在持续变慢。真正的问题集中在几个尖峰上——读档、切换场景,以及第一次打开大型界面的瞬间。
这个区别非常重要。
“Performance 不好”是一个模糊的问题。
“Load 面板反复检查了几百个文件”“同一个场景被构建了不止一次”“Journal 在显示一个 tab 之前,先准备了所有 tab”,才是可以真正动手解决的问题。
而这次审计,刚好找到了这三种情况。
一个游玩时间较长的存档,会逐渐积累大量用于恢复和校验的小文件。每次打开 Load 面板时,游戏做的工作远远超过了“把几张存档卡显示出来”所需要的程度。
场景切换时,游戏会先加载并完整实例化目标场景,确认它能够正常建立;然后把这棵场景树丢掉,再让引擎重新加载和实例化一次。
Journal 第一次打开时,则会为玩家还没有点开的 tab 提前加载 handler 和数据。
游戏并不是到处都慢。
它只是恰好在玩家等待回答的那一刻,做了太多事情。
存档不应该慢慢变成候诊室
我们的存档系统保留了比较完整的恢复信息。
它需要应对写入中断、损坏的 generation、旧版本存档以及异常退出。这层保护非常重要;为了让界面更快而简单删掉它,从来都不是一个可以接受的方案。
问题在于,这些原本用于恢复和取证的深度检查,逐渐进入了普通玩家每天都会经过的路径。
在我们的后期测试存档里,仅一个 slot 就积累了 805 个 journal event 文件。所有 slot 加起来,一次冷扫描需要解析 956 个文件。
存档存在得越久,游戏反复要做的工作就越多。
这周,我们从两个方向处理了它。
首先,Load 面板现在可以复用已经验证过的内存视图。只有真正发生存档、删除、恢复或版本变化时,缓存才会失效,并重新进入对应的深度检查。
其次,较早的 journal event 可以被折叠进一条 checkpoint。存档恢复需要的 sequence、高水位和冲突证据仍然保留,但那些永远增长的小文件不会再无止境地堆下去。
在开发机上的可重复测试条件中:
- Load 面板第一次打开,从约 1.46 秒降到了 40 毫秒。
- 第二次打开,从约 1.54 秒降到了 31 毫秒。
- 一次完整冷扫描需要解析的文件,从 956 个降到了 71 个。
- 冷扫描耗时从约 500 毫秒降到了 165 毫秒。
这些是同一台开发机、同一套测试条件下的前后对比,并不代表每台电脑都会得到完全相同的数字。
但变化的方向已经非常清楚。
存档系统做掉了大量没有必要重复的工作,同时没有因此变得更不谨慎。
走过一扇门,不应该建造两个村庄
场景切换遇到的是另一种问题。
正式把玩家送往下一个场景之前,游戏会做一次 preflight:
加载目标场景,完整实例化一棵场景树,确认它确实能够建立,然后把它丢掉。
接下来,真正的换场景流程又会请引擎重新加载并实例化同一个目标。
这道检查原本是为了让场景切换更加安全。
结果却让每一扇门,都在悄悄为同一段路收取两次费用。
我们重新整理了换场流程。淡出期间已经准备好的场景,现在可以被保留下来,直接交给真正的场景切换使用;如果流程中途取消,这份预加载资源也会被正确回收,不会因为加速而留下新的隐患。
第一次冷启动进入村庄仍然很重。在源码环境里,解析真实的村庄场景本身依旧需要几秒,这部分还需要之后继续处理。
但重复进入时的变化已经非常明显。
从屋内回到村庄,原本大约需要 1.24 秒;现在大约是 0.47 秒,等待时间减少了约 62%。
Journal 也用另一种方式遵循了相同原则。
它现在只先建立外壳和玩家真正请求的 tab。其余 tab 等到第一次打开时再准备,之后继续保留,不需要反复重建。
在我们的测量里,Journal 第一次打开从约 2.68 秒降到了 0.90 秒。
此外,我们还压缩并提前预热了世界地图,避免每次打开暂停菜单都重新读取整张画面,减少 Notebook 插图的重复解码,并把医疗系统的启动加载时间缩短了将近一半。
单独看,这些改动都不足以让整个游戏发生翻天覆地的变化。
但放在一起,它们会让游戏在玩家提出一个要求时,更快地作出回应。
Demo 不能只是被锁起来的完整版
上周,我们决定了 Steam 新品节 Demo 应该在哪里结束。
这周,我们开始处理另一条更不容易被玩家看见的边界:
最终的 Demo 文件里,究竟应该真正包含什么?
运行时规则可以阻止玩家进入后期地区,也可以阻止后续任务启动。
但它并不会自动从安装包里删除那些地区、对白、数据库记录和本地化文本。
对最终要发布的 Demo 来说,“玩家正常情况下走不到这里”并不是一条足够可靠的边界。
这些内容一开始就不应该进入 Demo 文件。
所以,我们正在把 Demo 构建成一份经过物理裁剪的独立 artifact。
主项目保持完整不变。构建 Demo 时,工具会创建一份一次性的 staging 副本,然后分层处理:
- 排除不属于 Demo 的文件和场景。
- 删除后期数据库记录及其引用。
- 让对白和本地化文本与对应内容一起被裁掉。
- 最后进行残留扫描;只要发现不应该存在的内容,整个 build 就会失败。
真正重要的并不只是“删掉内容”,而是每一层都要检查下一层。
如果一条配方还在,它所需的材料却已经被裁掉,build 必须失败。
如果一条数据库记录仍然指向被删除的对白,build 必须失败。
如果内容已经不存在,对应的后期本地化 key 却还留在包里,最后的扫描也必须把它找出来。
这周,文件、数据库、本地化以及残留审计的核心工具已经落地。一次性 staging 编排和第一份完整组装出的 artifact 仍然没有完成,所以 Steam Demo 目前还没有上传。
Steamworks 侧的独立 Demo App 已经创建;把最终 build 接入并完成上传,仍然是之后单独的 release step。
一段更小的体验,也应该真正拥有一份更小的安装包。
而不是让整个游戏的未来,都躲在一扇锁住的门后面。
当两味药不愿意待在一起
这是一个技术味很重的星期。
不过,一项放了很久的医疗系统工作也终于完成了。
Medicine Pot 现在完整支持了传统的十八反十九畏机制。
我们核对并整理了传统条目,只把游戏里确实存在、身份也能够准确对应的药材连接进去。游戏里还没有的药,不会因为名字相近就被强行替代。
在目前能够获得的药材中,可以实际组成三组十八反:
- 附子与半夏
- 附子与川贝
- 附子与浙贝母
目前可获得的药材里,还没有能够组成的十九畏组合。
游戏不会禁止玩家把相反的药材放进同一张方子。
它会发出警告,把配伍禁忌放在配方预览的最上方,并根据冲突数量降低最终药效;同时设置下限,避免多条冲突让整张方子的效果完全坍塌。
煎好的药也会记住这次结算。即使药已经离开药罐,tooltip 仍然可以告诉玩家,它的药效为什么发生了变化。
这才是我们希望放进《诊余漫录》的医学细节:
它不只是一段写在 Notebook 里、读完就忘记的知识。
它应该真正改变玩家看到的东西,也改变玩家选择所产生的结果。
那些小修复依然重要
这周,我们也完成了上一轮 Demo 重复测试留下的第一批问题。
新建角色时,随机外观现在会尊重玩家已经选择的性别,而不是把所有选项重新完全随机一次。
可以摆放的物品补上了 tooltip,告诉玩家怎样把它放到地面,又怎样把它收回来。
右上角的时间与状态 HUD 现在可以折叠;第一次出现新的不利邪气时,它也会自动展开一次,避免重要信息被错过。
村中心的水井终于允许玩家正常靠近。
已经退役的“幽灵材料”被清理,缺失的来源得到补充,配方和 tooltip 之间的不一致也完成了修正。十八反十九畏经过实装和测试,正式进入 Completed。
场景切换和存档恢复之间的竞态审计仍然在继续。
一个剧情礼盒曾经暴露出一种很麻烦的情况:空的 fallback 礼盒和存档恢复出来的真正礼盒,可能在同一次场景切换里同时出现。
眼前这个案例已经修好,但我们正在检查整个游戏里有没有其他同源问题。因此,这一整类问题目前还不能算结束。
这大概就是本周工作的形状。
有些任务完成了。
有些大问题终于可以被测量了。
还有一些 bug 则告诉我们:修好眼前的案例,并不等于故事已经结束。
上周,那扇门终于打开了。
这周,我们开始避免让每一个走进来的人,都在门槛上等待。
第一个小时仍然在继续 polish。最终 Demo artifact 还没有准备好。第一次冷加载仍然有需要缩短的地方,场景切换和存档恢复之间的竞态也没有完全结束。
但那些停顿已经不再神秘。
它们有了名字,有了测量结果,也有了各自的负责人。
有时候,polish 意味着为游戏添加新的东西。
有时候,它只是把时间还给玩家。
