第15章 - 适龄设计规范与实现
15.1 交互规范清单:规范与代码的对照
产品定位(6–12 岁、核心指标是「记住」)推导出一套交互规范。下表把每条规范与实现方式、当前状态对齐 —— 已实现的给出落点,未实现的明确标注为待做:
| 规范 | 实现方式 | 状态 |
|---|---|---|
| 可点击区域足够大 | 游戏内按钮 ≥ 88 逻辑像素(BaseScene.addButton 默认 520×136);DOM 顶栏按钮 min-h-14(56 px,2026-10-18 从 48 px 提到 56 px);大厅整张卡可点 |
✅ |
| 按钮不依赖悬停 | 按下瞬间下沉 + 音效;无 hover 语义;命中区按左上原点给 (0,0,w,h),外圈再放 12 px |
✅ |
| 无失败惩罚、无倒计时、无限重试 | 放错弹回原位;后羿射掉最后一个太阳,天黑后太阳自己回来;诱惑有时限、不会等不到头 | ✅ |
| 故事必须听完才轮到动手 | 旁白没念完不出现「继续」小手;story:ready 事件驱动;无语音设备按「读一遍」时长兜底 |
✅ |
| 关键文本配拼音 | 大厅卡片与图鉴卡片都带 pinyin 字段 |
✅ |
| 不靠颜色单独传达信息 | 对/错同时有图标与音效;女娲配对靠形状不靠颜色(色弱友好) | ✅ |
尊重 prefers-reduced-motion |
ctx.reducedMotion 跳过场景淡入等动效;DOM 层补了媒体查询 |
✅ |
| 声音随时可关、且记得住 | 游戏馆右上角 / 游戏内右上角 / 家长页三处同一状态;localStorage 记忆;setMuted 同时停掉正在念的旁白 |
✅ |
| 随时能退出、误触不中断 | 游戏内左上角返回键 + 确认层(「现在回去吗?/ 再玩一会」) | ✅ |
| 连续游玩 20 分钟提示休息 | stores/playtime.ts 只统计玩游戏的时间;大厅一天提醒一次,点「知道啦」收起;家长页可回看 |
✅ |
| 家长能找到、孩子点不到 | 长按标题 3 秒进家长页;清档要再长按 3 秒 | ✅ |
| 二周目可以跳过故事 | 首次必须听完;completedBefore 为真时才出现「跳过故事」 |
✅ |
| 通关后有「再来一次」 | 典故卡两个按钮;requestExit('replay') 原地重开,不回大厅 |
✅ |
| 星星可解释 | 卡面一行:有失误 →「这一局有 n 次小失误…」;零失误 →「一次都没点错,真稳!」 | ✅ |
| 无广告、无外链、无分享诱导、不采集个人信息 | 遥测只本地缓冲、flush() 空实现;无任何第三方 SDK;调试开关只对开发与自动化开放 |
✅ |
| PIN 型家长锁 | 目前只有「长按进入 / 长按清档」,没有密码锁 | ⏳ 待做 |
| 失败反馈统一 | 17 个记失误的模板里,wrong 音效 6 个、抖屏 9 个、红色提示 10 个 —— 口径不统一 | ⏳ 待做 |
这份清单的态度值得说明:项目把「没做的」明确列为「没做」,而不是含糊带过。2026-10-18 的「适龄体验与安全」那一轮,把上一版里的两个「⏳ 规划中」真的做了出来 —— 休息提醒与家长入口从文档承诺变成了代码行为。
15.2 记忆工程的代码落点(回顾与串联)
第 1 章讲过五层记忆机制,这里把每一层的具体实现位置补全,作为一张可坐标化的地图:
| 层 | 机制 | 实现位置 |
|---|---|---|
| L1 情境代入 | 分幕绘本 + 旁白;玩家扮演典故中的角色 | templates/story.ts;defineGame 强制每局以故事开场,且必须听完 |
| L2 关键抉择 | 关键动作由交互承载;keyMoment 提供揭晓语与复习题 |
各游戏场景(曹冲的「秤被压垮」、后羿的「射九留一」);reward.ts 播 keyMoment.reveal |
| L3 多通道编码 | 看(画面)+ 听(TTS 念读)+ 做(拖拽 / 连点)+ 说(归位念白)+ 选(复习题) | AudioBus.speak、orb.speak 归位念白、ReviewView 的三道题 |
| L4 收集与结构 | 典故卡 + 图鉴;按分组与年代落位 | reward.ts 的 card:unlocked;AlbumView |
| L5 间隔重复 + 迁移 | 1/3/7/21/60 天调度;transfer 题 |
progress/review.ts(纯调度)+ apps/site/src/review/quiz.ts(纯状态机);quiz 数据「每条 3 题含 1 道迁移」由 schema 强制 |
这张表是整套架构的「为什么」:keyMoment、misconceptions、quiz.kind、card:unlocked、ReviewScheduler 这些看起来零散的设计,全部服务于同一件事。
15.3 「少写字,多动手」的三个案例
五十三部游戏加起来,屏幕上几乎没有教学文字:开场的两句话,加上刚进玩法(或卡住时)出现的一句短提示(故事页点早了,也只会回一句「先听完这个故事」)。其余全靠图形、动画、音效与语音。三个最能说明问题的案例:
曹冲称象:从「点按钮」到「动手做」
第一版被判定「太死板、文字太多、说教」 —— 复盘出的问题是把「关键抉择」做成了文字选择题,孩子始终在读,没有在做。重做后的改动对照:
| 维度 | 改前 | 改后 |
|---|---|---|
| 故事 | 4 幕 +「下一幕」按钮 | 2 幕,每屏一句话,必须听完 |
| 关键抉择 | 三选一的文字题 | 不做题:让玩家自己试出来 |
| 玩法 | 点石头按钮看数字涨 | 拖拽大象与石头,看水痕 |
| 结算 | 文字测验 3 题 | 石头过秤,数字累加,与大象并列成等式 |
| 屏幕文字 | 每屏 1–2 句 + 题目 | 几乎没有,靠图形 / 动画 / 音效 |
「直接称 → 秤垮了」这第一步是设计的关键:问题是被玩家自己撞见的,不是被告知的。难度上调之后这一点更明显:大象 3300、容差 80,六块大石只有 3000,光搬大的凑不出来 —— 孩子必须自己发现「大小搭配」这个办法。
后羿射日:零惩罚的教学
「不能全射下来」这个分寸感,如果写成文字提示就是说教;做成机制则是:射到只剩一个 → 大地回春;贪心把最后一个也射了 → 天黑,一句「要留一个太阳」,太阳自己回来。孩子自己撞一次就懂了 —— 全程没有失败画面、没有扣分。
烽火戏诸侯:教训落在点火的人身上
信任是「点」出来的,不是「讲」出来的:点一次烽火,信任少一格;第二次来的兵马少了一半,走的时候有人在摇头;第三次「他们连城门都没到」;第四次点火是真的 —— 一个人也没来。全篇没有一句说教文案,孩子是自己把信任点完的。出处还注明「后世学者多存疑」,教训落在信用上,不落在褒姒身上。
15.4 两条硬规矩之一:故事必须听完
2026-10-15 加了一条全站规矩:每一幕旁白没有念完之前,屏幕上不会出现那只「点一下继续」的小手 —— 点哪都不翻页。
理由很直接:故事是这个产品的正片。跳过它,后面的动手环节就成了没有来由的小游戏 —— 孩子记住了操作,却没记住典故。难度的上调也是同一个目的:让「听过的故事」必须被真的用上。
实现上有三个技术点:
- 契约升级:
AudioBus.speak()返回SpeechHandle,其done是Promise<SpeechOutcome>('ended'或'unavailable')。念不出来也必须 resolve,否则静音、没装语音包、无头浏览器会永远卡在「正在听」(第 4 章); - 兜底时长:没有语音的设备按「读一遍」的时长等 ——
'unavailable'时用旁白字数估一个时长,否则「听完才能继续」在那些设备上等于没有; - 可断言:念完发一个
story:ready事件,无头验证与端到端测试靠它确定性地翻页;只有?autoplay=1与?story=instant这两个调试开关能跳过等待。
还在念的时候,屏幕下方是一个小喇叭 + 一条进度条;点早了只回一句「先听完这个故事」。二周目(completedBefore 为真)才出现「跳过故事」 —— 既不打断首次的沉浸,也不让愿意再玩的孩子每次都等 24–48 秒。
顺带修掉的一个小毛病:story.nag() 的 1.6 秒节流以前把 sfx('tap') 也一起吞了,念旁白时连点只有第一下有反馈。现在节流只节流气泡 —— 这是每个孩子每次玩的第一个交互面(53 个游戏 × 2 幕)。
15.5 两条硬规矩之二:难度上调一档,但失败惩罚依然是零
难度不是「更容易失败」,而是「多几步、判定收紧」。节选(完整表在 README):
| 典故 | 调了什么 |
|---|---|
| 曹冲称象 | 大象 3000 → 3300,容差 100 → 80:光搬大石头凑不出来 |
| 盘古开天地 | 撑天 30 下 → 40 下;「归位」的判定宽容度 70 → 52 |
| 女娲补天 | 五个洞的判定半径 74 → 62 |
| 精卫填海 / 愚公移山 | 30 下 / 30 担 → 40 下 / 40 担 |
| 夸父追日 | 一次跑完 6 秒 → 7.2 秒:河渭那一口要自己找时机 |
| 神农尝百草 | 六株草 → 八株(多了野薯与桂皮) |
| 烽火戏诸侯 | 五座烽火台 → 七座:火光要传得更远才到天边 |
| 大禹治水 | 筑堤 10 筐 → 14 筐;三段河道每段 3 个锚点 → 4 个 |
| 狡兔三窟 | 每个洞 3 铲 → 4 铲;土坡判定半径 120 → 100 |
| 百步穿杨 | 五片柳叶 44/36/30/24/19 → 36/30/25/20/15(画的和判的一起改) |
各场景「卡住才出现的提示」统一推后了 1.5–3 秒,扣分与失败依然不存在:点错了只是「再试试」,没有一个操作会让这一局玩不下去。
另一个方向上的调整也有:wait 类玩法原本真的要等 45–58 秒(太公钓鱼 58 秒、隔岸观火 45 秒),7–10 岁很难撑住。内容层改成 25 秒 / 22 秒,仍然保留「点诱惑会延后」的因果 —— 难度与耐心之间需要一条线,这条线是「孩子撑得住」而不是「设计上更纯粹」。
15.6 声音、休息与家长页
这三件事是同一轮(2026-10-18 产品审查)做出来的,代码落点都很小,但对「家长愿不愿意把手机给孩子」影响最大。
声音随时可关
stores/settings.ts 只做三件事:读回上次选择、setMuted 同步给 platform.audio、写回 localStorage。三处入口(游戏馆导航、游戏内右上角、家长页)共用同一个状态;setMuted(true) 会同时停掉正在念的旁白 —— 只关音效不关朗读,等于没关。TTS 音量也从 1.0 降到 0.8:孩子的设备常在耳边、公交上。
20 分钟休息提醒
stores/playtime.ts 的规则:
export const REST_AFTER_SECONDS = 20 * 60;
const restDue = computed(
() => !record.value.reminded && record.value.seconds >= REST_AFTER_SECONDS,
);
- 只统计「玩游戏的时间」:
PlayView里每 30 秒上报一次addSeconds(30),大厅与图鉴的时间不算; - 一天只提醒一次:点过「知道啦」就把
reminded置真,跨天自动归零; - 数据最小化:只存
{ date, seconds, rounds, reminded },不记任何可识别信息; - 跨天与坏数据都不炸:
read()里日期不符就按空档处理,字段缺了也不抛错。
家长页
入口是长按标题 3 秒(App.vue 的 startParentHold),孩子不会误入,家长又找得到。页内四件事:
- 安全边界三条:不联网 / 无广告内购 / 存档只在本机(与 README「给家长」一节同口径);
- 这周玩过哪些典故:按
lastPlayedAt倒序、最多 6 个,给家长看标题而不是内部 id; - 声音开关与清除本机存档:清档要再长按 3 秒(
parent-clear的pointerdown起计时),避免孩子误触; - 版本号(从
apps/site/package.json读)。
配合游戏内的退出确认层与「再来一次」,孩子这一侧的可控性也补齐了:误触不会中断一局,想退出随时能退,退出后大厅里还有「继续上次」。
15.7 分级、定性与「孩子能读懂的措辞」
内容层的适龄化口径散落在几个字段里,2026-10-18 这一轮做了一次系统性修订:
| 维度 | 做法 |
|---|---|
定性(kind) |
五分:myth / legend / history / fable / strategy。愚公移山原标「信史」(实为《列子》寓言)改成 fable;烽火戏诸侯的 chapter 说「未必是史实」,与定性打架 —— 一并修正 |
年代(era) |
从年号串改成孩子能懂的说法,如「东汉末年 · 约一千八百年前」;yearApprox 仍保留用于排序 |
年龄(ageRange) |
按难度重标 7 条,并同步 manifest 与测试 |
| 拼音 | 统一首字母大写(Mán tiān guò hǎi) |
| 文言原文 | 53/53 的图鉴卡默认收起为「原文」折叠,孩子先看寓意与迁移句 —— 最长 53 字的文言对 7–10 岁是噪音 |
| 三十六计的价值引导 | 每计必须写 thinkingTag(2–6 字)与 transfer(一句正向迁移,≥12 字):计名与按语保留,但「今天可以怎么用」必须在界面上看得见 |
| 防御性计策 | 笑里藏刀 / 反间计 / 美人计 / 苦肉计只教识别,不教怎么使(写在《三十六计方案》的六条适龄化口径里,由内容审查落实) |
「原文折叠」这一条是典型的孩子视角修正:从开发者的角度看,展示出处原文显得严谨;从孩子的角度看,那是看不懂的一屏字。严谨可以放在折叠里,理解必须放在第一屏。
15.8 复习题的防猜设计
到期复习上线后暴露的数据很刺眼:135 道题里 117 道(86.7%)的正确答案是第一个选项,「最长选项 = 答案」占 106/135(78.5%)。孩子只点第一个就能「通关」复习,评级毫无意义。
修法两层(第 10 章有代码):确定性洗牌(种子 = 内容 id + 题目 id,同一条内容每次顺序一致)+ 内容重写(53 条统一 3 题共 159 题,答案位置 53/53/53、「最长项 = 答案」 0/159、选项长度差 ≤4)。
这件事的教训不止于复习题:凡是「孩子能猜」的地方,设计就已经失败了。选对答案的概率不该由排版决定。
15.9 美术与音频策略
几何绘制:「换资源不改代码」
阶段 0 的美术全部用 Phaser Graphics 绘制:零图片资源,先把玩法与学习闭环跑通。统一的画法约定(写进 cartoon.ts):
- 厚描边(统一
THEME.outline暖黑,不纯黑)+ 圆角 + 落地阴影; - 同一套角色风格:圆脸、腮红、三头身比例;
- 反馈动画标准化:
popIn(出现)、squash(受重)、sparkleAt(成功)、createHand(引导)。
「换资源不改代码」是一条被结构保证的承诺:绘制代码全部收在 games/<id>/src/art.ts 的画法函数里,模板与工具箱只调用它们。将来切真素材时,只改 art.ts —— 模板、契约、外壳、测试全部不动。
配套的待办(方案中已列、实物未建):ASSETS-LICENSES.md 授权台账 —— 字体 / 音效 / 音乐逐个记录来源与许可证。项目文档把这条称为「会踩死人的坑,从第一天就做」;目前因为还没有引入外部资源,台账尚未创建。
音频
- 音效:六个(
tap/correct/wrong/splash/unlock/complete)用 WebAudio 振荡器实时合成,零文件(第 9 章有配方表);另有tone(freq, ms)支持「进度听得见」(盘古撑天音调渐高); - 旁白:浏览器 TTS(桌面 / 网页)+ 原生 TTS(安卓 —— WebView 没有 Web Speech API),中文 / 英文按 locale 选嗓音,音量 0.8;
- 静音优先级最高:静音时不只不发音,还会「停掉正在念的」;
- 将来换真人配音:按 locale 取音频文件,改的只是
AudioBus.speak的实现;方案给单文件定的预算是 3–8 秒切片、≤ 100 KB。
15.10 性能与体积预算
| 指标 | 预算 | 现状 |
|---|---|---|
| 首屏 JS + CSS | ≤ 60 KB gzip / 160 KB 原始 | 54.1 / 146.1 KB |
| 单个懒加载 chunk | ≤ 260 KB gzip / 1000 KB 原始 | 裁剪版 Phaser 220.6 / 856.3 KB |
| 打开一局的全部新增流量 | — | ≈226 KB gzip;之后每局 3–8 KB |
| 单图片 / 音频 | ≤ 200 KB / 合计 600 KB | 阶段 0 无资源文件 |
| 单局时长 | 3–5 分钟 | 由内容与关卡步数控制 |
| 中端安卓帧率 | 60fps;低端 ≥ 30fps | 低端降级策略属后续 |
体积的守卫随时在跑:check-size.mjs 的七项预算在 pnpm verify、ci.yml 与 release.yml 里都会执行。对低龄玩家的设备而言这不是洁癖,而是「旧手机能不能跑」。
15.11 明确「暂不做」与仍然欠着的
技术方案里有一节专门写「Anti-scope」,列的都是主动不做的事:账号系统 / 后端 API / 排行榜 / 多人联机 / 内购 / 广告 / 3D / 后台 CMS / PWA(计划中,未实现)。
理由不是「不会做」,而是它们都会显著抬高成本与合规风险,且对「让孩子记住典故」这个核心指标几乎无贡献。反过来,清单里每一项都留了触发条件:内容资产超过上百条且需要非程序员编辑时再引入 CMS;路由超过 5 条或需要嵌套路由时再引入 vue-router;需要跨设备时再换 ProgressStore 实现做云存档。
同一份清单也如实记着还欠着的(第 16 章会展开):
- Idle 提示阈值不统一(8000 × 5 / 8500 × 1 / 9000 × 13 / 11000 × 3):孩子对「什么时候会帮我」的预期不稳定,统一要改 20+ 模板;
- 失败反馈不统一:17 个记失误的模板里只有 6 个有 wrong 音效、9 个抖屏、10 个红色提示;
- 复习间隔封顶:现在最长 60 天,对 7–10 岁偏长,建议封顶 21 天(需产品确认);
- 图鉴 / 大厅折叠:53 张卡一次铺开,三十六计可考虑按六套折叠;
- PIN 型家长锁:目前只有长按,没有密码;
- Android 权限收紧与 Tauri CSP:需要真机 / 打包回归;
- PWA 与真美术资源:维持「计划未实现」。
对低龄向产品而言,少做一个功能往往比多做一个功能更需要勇气 —— 但把「没做」写清楚,比假装做了要重要得多。
15.12 小结
- 适龄规范不是口号,逐条都有落点或明确的「待做」标记;2026-10-18 那一轮把休息提醒、家长页、静音、退出确认、再来一次、跳过故事全部落地;
- 记忆工程五层各有实现位置,
keyMoment/quiz.kind/ 复习调度这些数据结构是「为什么」的答案; - 两条硬规矩:故事必须听完(
story:ready+ 兜底时长)、难度上调一档但失败惩罚为零; - 「少写字、多动手」由三个案例支撑:曹冲的重做、后羿的零惩罚、烽火的信用机制;
- 复习题的防猜提醒我们:凡是孩子能猜的地方,设计就已经失败了;
- 美术(几何绘制)与音频(合成 + TTS)都是「阶段 0 零资源、换资源不改代码」的策略;
- 历史准确性三条硬约束(出处、定性、存疑标注)与「原文折叠」的措辞修正,是教育产品在孩子视角下的底线。
下一章收尾:把散落在各章的所有踩坑记录集中起来,并回答常见问题。