8.2 存档结构与版本迁移
上一节把存档存进了抽象层,这一节解决更难的一半:玩家设备上躺着的旧存档,格式变了怎么办。用 v1→v2→v3 的迁移链演示三类改动——字段新增默认值、字段重命名、类型收紧,配可跑测试断言旧存档能升到最新且不丢数据、迁移幂等、未知版本明确失败,并用 S 层脚本对照迁移前后的体积与耗时。
tag
上一节把存档存进了抽象层,这一节解决更难的一半:玩家设备上躺着的旧存档,格式变了怎么办。用 v1→v2→v3 的迁移链演示三类改动——字段新增默认值、字段重命名、类型收紧,配可跑测试断言旧存档能升到最新且不丢数据、迁移幂等、未知版本明确失败,并用 S 层脚本对照迁移前后的体积与耗时。
小游戏要在浏览器与多个小游戏宿主上跑,存储能力各不相同:有的无上限,有的只有几 MB 且会静默丢写。本节给出一层可断言的 KV 抽象——内存、限额、降级到最小存档三种实现,容量按整数字节记账、超限按优先级淘汰,云存档只按整数版本号对账取新者。
把「落地弹得对不对」变成四个整数参数:重力增量、速度阻尼比、落地弹性比、静止阈值。用固定步长的整数物理内核实现下落与弹跳,再用单文件 HTML 原型配 Playwright 无头,先注入固定帧数步进、再走一遍真实 rAF 下落,断言落点、弹跳次数与静止判定并截图。
动手写 t1/t2 两级里最核心的碰撞内核:AABB 与圆相交(整数平方距离比较,无开方)、按网格做的 3x3 邻域 broad phase,并用可播种 LCG 撒点,对照暴力 O(n^2) 与网格的候选对数随实体数增长,产 CSV 与 SVG。
把「小游戏要不要上物理引擎」变成可核算的分级问题:把玩法类型映射到所需物理能力,再折算成代码行数、实现工时与包体占用,用 S 层脚本逐层汇总五个层级,讲清《合成小岛》为什么只需最轻的一级、多上一级要付多少代价,以及分级为何要按能力增量补齐而非整体升级。
把浏览器与小游戏宿主的输入差异摆成一张矩阵:事件 API 名、多点触控投递方式、取消事件、安全区、坐标原点五个维度逐平台对比,用 S 层脚本统计差异条目并估算适配工时,讲清权重模型与复用系数如何把「每家平台都要改一遍」压成一次性抽象层成本。
把「跟不跟手」从主观感受变成可调参数:用整数跟随模型(跟随系数、死区、最小步、速度上限、松手吸附)实现拖拽手感,参数全为像素整数、无浮点,再用单文件 HTML 原型配 Playwright 无头真跑一次鼠标拖拽,断言落点、吸附与手势并截图存档。
把不同平台的触摸事件统一归一化成 {x,y,phase} 样本序列,再用手势状态机识别点击、长按、拖拽与滑动:给出可跑的 TypeScript 输入内核与 14 项单元测试,讲清时间阈值与位移阈值的判定顺序,以及物理像素到逻辑坐标的整数缩放与原点翻转。
暂停容易,恢复难:切后台十分钟再回来,若把十分钟按固定步长如实补回,一帧要跑三万七千步,设备直接卡死。本节用封顶补偿把离线时长折算成有上限的固定步数,并把整条「暂停 → 切后台 → 恢复」路径放进单文件 HTML 原型,用 Playwright 无头断言加截图验收。
一款小游戏不止一个「世界」:菜单、对局、结算、弹窗各自是一个场景。本节用场景栈 + 生命周期状态机管理它们——进入、暂停、恢复、退出四个动作,按动作而非目标态判定合法迁移,非法迁移一律拒绝且不改动任何状态,并配可跑的 TS 内核与断言测试。
固定步长主循环是《合成小岛》可复现的地基:用累加器把可变帧率折算成整数固定步,输入按 tick 采样、随机按步推进,同一 seed 与输入序列在 16ms / 32ms / 不规则帧率下终局逐位一致;配可跑的 TS 内核、断言测试与跨语言对拍。
小游戏改一张图、加一个关卡,用户要重下多少,取决于热更新的版本模型。本节用 TypeScript 写一个差异计算内核:版本号按整数分段比较,清单按路径与哈希分类,再据此在「差异下载」与「整包更新」之间做决策,让每次更新要下多少变成可复算的数字。
音频常常是包体里仅次于图集的一块,却最容易被「随便导出个 MP3」糊弄过去。本节把音频格式折算成可算的字节:码率、容器开销、片段时长一起进公式,再用一段脚本选出每个片段该用的格式,并给出「什么时长用什么格式」的判据,算清短音效与长片段各自该走哪条路。
图集与纹理是包体里最重的一块:它既是批处理能否发生的前提,也决定了运行时的显存占用。本节用 Pillow 真实生成精灵、跑一遍自写的货架装箱,把打包率、PNG 字节与显存占用都算成确定数字,并给出图集尺寸与纹理格式的取舍判据,让资源预算从手感变成可复算的数字。
把「合批到底省多少」算成可对照的整数模型:一帧耗时拆成固定开销、每批状态切换成本与每精灵顶点成本三项,比较未合批/按纹理合批/图集三种策略的批次数与帧耗时,产 CSV 与纯 Python SVG,再用 B 层扫单批上限看批大小敏感性在哪里趋于平台。
把设计分辨率到各种真实屏幕的映射拆成可算的整数像素:用千分比整数表达 fit/fill 缩放,算出内容区与居中偏移,再与刘海/圆角/指示条的安全区求交,四台设备逐档断言,并给出可切换设备与缩放模式的单文件 HTML 原型与 Playwright 无头验收脚本。
把「小游戏没有 DOM」这件平台约束落成可运行的工程抽象:用一份不依赖任何引擎的最小 2D 绘制内核,把清屏/矩形/文本三类指令发到两个宿主——Node 里的整数像素缓冲与浏览器里的真实 Canvas,逐像素断言两边画出的非背景像素数一致、指纹稳定,并附可打开的单文件 HTML 原型与无头验证脚本。
分包方案定下来之后,还要回答一个问题:这份方案在真实构建里占多少体积、离上限有多近。本节把构建产物清单按包与类型聚合,算出各包占比并画出堆叠图,再用一段 B 层基准把每项体积按可播种的整数抖动跑 400 轮,看「main 越线」出现的概率,让余量从感觉变成数字。
分包不是把文件挪个位置,而是把「玩家什么时候会用到它」翻译成加载顺序。本节给出一份带依赖与体积的资源清单,再用一段 P 层 TypeScript 内核按「依赖闭包 + 体积预算」自动规划 main 与分包,并用四条不变量断言规划结果,让分包线由约束反推而不是拍脑袋。
引擎选型定下来之后,第一件事不是写玩法,而是把工程骨架的模块边界钉死。本节先把《合成小岛》的十个模块写成一份带层级与依赖方向的配置表,再用一段 P 层 TypeScript 内核做拓扑排序、无环校验与「依赖只能向下」的检查,让模块边界不靠口头约定,而靠一条跑得起来的断言守住。