第16章 - 踩坑记录与常见问题
16.1 踩坑记录总表
CHAGS 的开发过程中积累了大量「现象 → 原因 → 处置」的记录。它们分散在各模块的代码注释与项目文档里,这里按类别集中。每一条都对应代码里的一处修复或一条 lint/测试规则。
A. Vue × Phaser 集成
| # | 现象 | 原因 | 处置 |
|---|---|---|---|
| A1 | 持有 Phaser 实例后卡顿甚至抛错 | ref 会深度代理整个 Phaser 对象图(场景、纹理、WebGL 上下文) |
一律 shallowRef / markRaw;写进 code review 清单 |
| A2 | 路由切走后后台仍跑着 canvas | 未 destroy 或容器被 <KeepAlive> / <Transition> 保留 |
onBeforeUnmount 里 await unmount()(内部 game.destroy(true));不放 KeepAlive |
| A3 | 开发热更新后越玩越卡、有多个画面 | HMR 热替换叠加出多个 canvas | 编辑 Phaser 场景时整页刷新;宿主侧 disposed 标记防迟到挂载 |
B. Phaser 4 引擎行为
| # | 现象 | 原因 | 处置 |
|---|---|---|---|
| B1 | 点在 A 上却抓到了 80 像素外的 B | Container 的 setInteractive(hitArea) 局部坐标约定与直觉不符(按钮那样「点正中心」的场景察觉不到,拖拽一上来就暴露) |
自己写 drag.ts:按下时抓住半径内最近的目标,既确定又对小朋友更友好 |
| B2 | CI 与慢手机上「按住 3.4 秒到不了 45%」 | Phaser 低帧率时钳制 delta,按帧间隔累加的长按进度失真 | 长按改按墙钟计时(记起点,now - holdStartedAt);补慢设备用例 |
| B3 | 长按模式(夸父)永远不提示 | 卡住提示只写在连点分支里;孩子只会点不会按,点一下只涨 30,要 60 下才满 | 提示逻辑移出模式分支,两种模式都生效;长按「按住 420ms 才算会了」才收小手,卡住时把小手找回来 |
| B4 | 射出的箭「穿过」了目标没命中 | 箭一帧能飞很远,按点判定会漏 | 按「这一帧扫过的线段」判定(点到线段距离),并取沿弹道最先碰到的目标 |
C. 内容与数据
| # | 现象 | 原因 | 处置 |
|---|---|---|---|
| C1 | 某种数值配比下永远点不满 / 凑不出 | 内容数值组合错误 | schema 层 refine:tap 强制整除、substitution 用有界完全背包(canReach)校验「容差内可通关」 |
| C2 | 同一个洞补两次算两个 | 完成判定按「归位次数」计数 | 改为按槽位去重计数(filledCount),回归用例覆盖 |
| C3 | 把里程碑的暂停删掉、把三条并成一条,测试全绿 | 里程碑不可观测 | 发 beat:enter:milestone:<id> 事件;e2e 断言「到河边时尚未通关」「三条里程碑按序各出现一次」 |
| C4 | 新增第 8 个典故全绿,用户点进去 404 | 典故 id 抄在四个地方(内容 JSON、索引、注册表、e2e),抄漏无测试发现 | registry 一致性单测(三方对账);e2e 的 id 列表改为枚举内容目录 |
D. 双语与版面
| # | 现象 | 原因 | 处置 |
|---|---|---|---|
| D1 | 英文版典故卡糊成一团:标题出框、星星压寓意、出处压原文 | 卡片用固定纵坐标排版;中文五个字 vs 英文三十个字母 | 改先量后排:统一按卡片内宽换行、量高排布、卡片高度由内容决定、超限整体等比缩小;按钮位置固定不动 |
| D2 | 英文长句时,故事页小手压住最后一行正文 | 小手固定在 H-150 |
小手按正文实测高度定位,放不下挪到右下角;正文起点 985 → 950 |
| D3 | 提示气泡的句号被挤成孤零零的第三行 | 换行宽度 W-260 太窄 |
放宽到 W-180;分幕正文宽度 560 → 596(44px 汉字一行 13 个字) |
| D4 | 英文译文把 unit 写成 baskets 却能通过所有检查,玩家打开才报错 |
game.stages[].levelScript 在内容 schema 里是 unknown,只查内容 schema 不够 |
content:check 在合并译文后重新过一遍模板 schema;另加 CJK 泄漏扫描抓漏翻字段 |
E. 无障碍与可读性
一次全仓审查集中处理了可读性问题(修复前 → 修复后):
| # | 位置 | 修复前对比度 | 处置 |
|---|---|---|---|
| E1 | 曹冲读数(整局最高潮的数字)压水面 | 1.48:1,几乎看不见 | 垫卡片底 → 12.5:1 |
| E2 | 结算答案数字压天空 | 2.92:1 | 换深红 #b03a31 → 4.05:1 |
| E3 | 界面主按钮(亮红配纸色) | 3.42:1 | 换 #b03a31(5.44:1)等深色变体(青 5.61、金 5.42) |
| E4 | 顶栏导航按钮只有 36px | 触控目标偏小 | py-3 + min-h-12(48px) |
| E5 | 典故卡原文与出处只有 13.8 CSS px | 手机上 12px | 提到 28 设计 px(约 16 CSS px) |
| E6 | 360px 窄屏上标题折成三四行 | 未处理溢出 | min-w-0 + truncate,副标题窄屏隐藏 |
| E7 | 故事页连点第二下跳掉一整幕 | 第二下落在下一幕刚建好的点击区上 | 350ms 输入锁 |
F. 工具链与工程
| # | 现象 | 原因 | 处置 |
|---|---|---|---|
| F1 | vue-tsc 报 ERR_PACKAGE_PATH_NOT_EXPORTED: './lib/tsc' |
TypeScript 7 移除了该子路径导出,而 vue-tsc 依赖它 | 锁 TS 5.9.3(唯一满足三方 peer 的组合);解法记录进方案文档 |
| F2 | 安卓打包报 Unsupported class file major version 69 |
Gradle 8.14 不支持 JDK 25 | build-apk.mjs 自动挑 JDK 17–24;支持 CHAGS_JDK 覆盖 |
| F3 | 玩家屏幕上是 zod 报错原文 [{"origin":"string","code":"too_big"...}] |
关卡数据校验失败直接透传 | 界面上改「一句人话 + 回典故馆按钮」;技术详情进控制台与 window.__chags.lastError |
| F4 | 玩法被改坏了要等发版才发现 | release workflow 只在打 tag 时跑、且不含端到端;体积预算写了「用 size-limit 守卫」却没有东西在守;失败时上传的 artifact 路径根本不存在 | 新增 ci.yml(check / e2e / audit 三班岗);新增 check-size.mjs 接进 verify;artifact 改传 test-results |
| F5 | Electron 本地加载被 CORS 拒绝 | file:// 下 <script type="module" crossorigin> 不可用 |
改用 Tauri(默认自定义协议) |
| F6 | 安卓版全程没有旁白 | Android WebView 未实现 Web Speech API | 走原生 TTS(Capacitor 插件桥接),Web 端保留 Web Speech |
| F7 | CI 上 ./gradlew 报 exit 126 |
gradlew 在 Windows 上提交,git 没保留可执行位 | git update-index --chmod=+x 修正 + workflow 里再 chmod +x 兜底 |
16.2 常见问题(FAQ)
Q1:怎么把游戏给孩子玩?
三种方式:手机浏览器打开部署好的网址(最方便);下载 Windows 便携 exe 或安卓 apk(第 14 章,离线可用);本地开发用 pnpm dev。注意浏览器版目前还是在线加载静态资源(PWA 离线缓存属阶段 1)。
Q2:进度会丢吗?存在哪里?
存在设备的 IndexedDB(本地),无账号、不同步。刷新、重开都在;换设备或清浏览器数据会丢。想跨设备要等云存档(换 ProgressStore 实现即可,第 10 章)。
Q3:支持哪些语言?怎么加新语言?
当前中文 + 英文。加 zh-TW 等新语言的完整步骤见第 11 章 11.8:注册 locale、补界面文案表、补 12 份内容翻译、跑 content:check 与 audit:i18n。游戏代码零改动。
Q4:想换美术 / 配音怎么做?
美术:改 games/<id>/src/art.ts 里的绘制函数(换成贴图调用即可),模板与测试不动;分幕插图走 story[].illustration 字段。配音:替换 AudioBus.speak 的实现(按 locale 取音频文件),接口不变。
Q5:复习测验在哪?为什么通关后没有答题?
quiz 模板与 21 道题(每典故 3 道含迁移题)都已就绪,但故意没有挂在「每局结束」——那与「少写字、多动手」冲突。正确入口是「到期复习」:典故卡到复习时间后从图鉴进入小测验。接线路径见第 10 章 10.7。
Q6:能上微信小游戏吗?
架构为它留了门(PlatformServices 是唯一出口),但项目文档的结论很克制:微信小游戏不是「换个适配器就能上」 —— 还需处理 WebGL 上下文差异、包体限制、wx 全局注入与资源加载器替换。决策规则:如果小游戏是硬性首发要求,应先用 Phaser 3.90 + weapp-adapter 做 2–3 天 PoC 验证,或改用 Cocos Creator;如果首发是 H5 / 微信内网页,当前方案最优,小游戏作为后续可选分支。
Q7:可以用它做自己的典故合集吗?
可以,MIT 许可。第 16.3 节给了一份 fork 指南。
Q8:为什么没有排行榜、账号、联机、内购?
主动不做(Anti-scope,第 15 章 15.8):它们抬高成本与合规风险,对「让孩子记住典故」几乎无贡献。每一项都留了触发条件,需要时按既定出口扩展。
Q9:测试现在有多少?
写作本文时:单测 23 个文件、156 项;端到端 41 项(12 部典故的真实操作 + autoplay + 双语 + 慢设备 + 截图)另加双语版面巡检 34 张截图。跑法见第 13 章。
Q10:儿童隐私怎么保证?
无广告 SDK、无社交、无外链诱导、不采集个人信息;遥测默认只做本地缓冲且 flush() 为空实现;进度只存本机。这些不是配置项,而是代码里没有相反的路径(第 9 章)。
Q11:为什么游戏里几乎没有文字,家长会不会觉得没教东西?
设计原话:孩子是「做」出这个办法,而不是被告知(第 15 章三个案例)。教学负载分布在交互、画面、音效、语音里;关卡数据里还有 keyMoment、misconceptions、quiz 三层「记住」的证据链。
Q12:pnpm verify 全绿就可以提交了吗?
改了玩法或交互还要跑 pnpm e2e;改了文案、版面或语言要跑 pnpm audit:i18n。原则:新用例要能失败 —— 写完先故意破坏被测行为,确认变红。
16.3 二次开发指南:做一个自己的合集
假设你不想做典故游戏,而是想做「世界寓言」「科学小实验」之类的合集。可以复用的部分比想象的多:
保留(几乎不用动):
- 工程骨架:pnpm workspace 三层结构、契约、模板机制、平台层、进度层、工具链(verify / content:check / size / e2e / CI);
- 三个铁律与对应的 lint 规则、测试范式。
替换(按顺序做):
- 品牌层:
apps/site/index.html的标题与描述、messages.ts的app.title/app.subtitle、tauri.conf.json的productName/ 窗口标题、capacitor.config.ts的appId/appName、图标(scripts/make-icons.py); - 内容层:清空
packages/content/src/allusions/与i18n/en/,按第 12 章的流程逐份添加;kind字段的语义(history/myth/legend)按你的题材调整(如 science/story); - 模板层:保留
story/place/tap/sort等通用骨架;为你的题材新增模板(第 7 章 7.14 清单); - 游戏层:删掉不需要的
games/*目录,同步清registry.ts、apps/site/package.json与 e2e 引用 ——registry.test.ts会立刻告诉你哪里漏了; - 术语层:界面文案里「典故馆 / 典故卡」等词按题材替换(都在
messages.ts一处)。
三个不要动的纪律(它们是这套工程的价值所在):
pnpm verify必须是「完成」的定义,不要为了赶进度绕过;content:check的内容约束(关键抉择、迁移题、出处)不要删 —— 想改规则可以,改成你的题材真正需要的约束,而不是取消约束;- 一致性测试(registry)、可观测事件(
window.__chags)、autoplay 直达入口,一个都别省。
16.4 路线图与结语
项目自己的路线图(阶段划分):
| 阶段 | 内容 | 状态 |
|---|---|---|
| 阶段 0 · 技术验证 | 骨架、schema、三模板、曹冲称象、pnpm verify、冒烟 |
✅ 已完成 |
| 阶段 1 · MVP | 十二部典故、双语、打包双平台、CI、体积守卫 | ✅ 已完成(本教程描述的状态) |
| 阶段 1 遗留 | PWA 离线、家长锁、休息提醒、复习中心接线 | ⏳ 待做 |
| 阶段 2 · 扩充 | 模板扩到 8+、典故扩到 20–30、低端机降级 | 规划 |
| 阶段 3 · 平台化 | 云存档、家长报告、(可选)微信小游戏 PoC | 规划 |
回看整个项目,它最值得学习的地方不是用了 Phaser 或 Vue,而是把三件事做透了:把教学要求写进数据结构(keyMoment / refine 规则 / transfer 题)、把工程纪律写进工具链(verify / content:check / 体积守卫 / CI 三班岗)、把没做完的事如实记录(复习接线、PWA、家长锁)。教程到这里结束,祝你也做出让孩子记住点什么的作品。
本教程基于 znlgis/CHAGS 仓库本地版本写成,代码示例均经精简,接口与行为以源码为准。