From d15a9b9635c8792b0e7d684f1213db039e82a7c3 Mon Sep 17 00:00:00 2001 From: xiangsilian Date: Wed, 30 Sep 2026 23:51:54 +0800 Subject: [PATCH] save --- docs/audio/final-asset-check.md | 21 - docs/audio/round1-report.md | 72 -- docs/audio/round2-report.md | 59 -- docs/audio/round3-report.md | 68 -- docs/audio/round4-report.md | 102 --- docs/audio/round5-report.md | 39 - docs/audio/round6-report.md | 60 -- docs/design/26-audio-interaction-playbook.md | 729 +++++++++++++++++++ docs/design/README.md | 1 + 9 files changed, 730 insertions(+), 421 deletions(-) delete mode 100644 docs/audio/final-asset-check.md delete mode 100644 docs/audio/round1-report.md delete mode 100644 docs/audio/round2-report.md delete mode 100644 docs/audio/round3-report.md delete mode 100644 docs/audio/round4-report.md delete mode 100644 docs/audio/round5-report.md delete mode 100644 docs/audio/round6-report.md create mode 100644 docs/design/26-audio-interaction-playbook.md diff --git a/docs/audio/final-asset-check.md b/docs/audio/final-asset-check.md deleted file mode 100644 index 67a49cb5..00000000 --- a/docs/audio/final-asset-check.md +++ /dev/null @@ -1,21 +0,0 @@ -# 全项目音频资产终检(R5,2026-08-29) - -| 游戏 | ogg | m4a | 0字节 | 解码失败 | voice male/female id 一致 | -|---|---|---|---|---|---| -| storyline | 136 | 136 | 0 | 0 | ✅ 各 37(平铺 48 个角色/NPC 句,豁免) | -| escape | 169 | 169 | 0 | 0 | ✅ 各 75 | -| simon | 146 | 146 | 0 | 0 | ✅ 各 69 | -| rhythm | 75 | 75 | 0 | 0 | ✅ 各 32 | -| space | 119 | 119 | 0 | 0 | ✅ 各 53 | -| rpg | 283 | 283 | 0 | 0 | ✅ 各 114(平铺 14 个角色/NPC 句,豁免) | - -## 结论 - -全部通过:双格式配对完整、无 0 字节、全部可解码、voice 性别目录 id 集合一致(storyline/rpg 平铺角色句按约定豁免)。 - -## 附注 - -- storyline 平铺 = 角色台词(单音色永久保留)+ R1 旁白旧文件(遗产,待清理)。 -- rpg 平铺 = NPC 句(设计如此,单音色)。 -- R6 起 sfx 峰值标准为 -3dBFS(真峰值经解码回读校验);voice 目录响度策略不变(峰值 0.89)。 -- 终检脚本:`tools/check_all_assets.py`;爆音扫描/修复:`tools/scan_clicks.py` / `tools/fix_clicks.py`。 diff --git a/docs/audio/round1-report.md b/docs/audio/round1-report.md deleted file mode 100644 index 50cdb61a..00000000 --- a/docs/audio/round1-report.md +++ /dev/null @@ -1,72 +0,0 @@ -# R1 音频轮次报告(2026-08-29,音乐音效总监) - -本轮主题:双性别旁白资产管线 + simon BGM 移除。Mureka 花费 $0(无新 BGM,预算余额 $1.85)。 - -## 1. 新目录约定(给开发) - -- 旁白/系统音按性别分目录:`web//audio/voice/male/.{ogg,m4a}` 与 `web//audio/voice/female/.{ogg,m4a}`。 -- 两个目录的 **id 集合完全一致**(每游戏核对过),引擎按用户选择整目录切换即可,无需按 id 特判。 -- 双格式:每个 id 都有 `.ogg`(libvorbis q3 44.1k)和 `.m4a`(aac 128k 44.1k),沿用现有格式协商逻辑。 -- **平铺的旧文件 `voice/.*` 本轮保留未动**。引擎切到 male/female 子目录后,后续轮次再删除平铺文件(storyline 角色台词除外,见 §3)。 -- staging wav 保留在 `tools/gen_voice_dual_wav///`,供后续轮次增量重生成(管线支持断点续跑,已有且合格的 wav 会跳过)。 - -## 2. 生成统计 - -| 游戏 | ids/性别 | male 总时长 | female 总时长 | -|---|---|---|---| -| storyline | 36 | 806.6s | 960.6s | -| escape | 41 | 230.8s | 212.4s | -| simon | 30 | 63.4s | 62.9s | -| rhythm | 9 | 71.5s | 59.0s | -| space | 27 | 78.2s | 65.1s | -| **合计** | **143 × 2 = 286 条** | 1250.5s | 1360.0s | - -落盘文件 286 × 2 格式 = 572 个。质量控制:全部通过 时长>0.3s / 峰值>0.05 / 峰值归一 0.89;数字类同 instruct 整批生成;>40 字台词拆句拼接。两性时长离群(>3 倍)初检 3 条,已全部重生成并复检通过: - -- `simon/male/num_4`(8.00s → 0.48s) -- `simon/female/teach_end`(29.04s → 14.80s) -- `space/male/n_ge`(2.16s → 1.12s) - -## 3. storyline 角色音色映射(角色台词不做双性别) - -| 角色 | speaker | 文件 id(沿用平铺原文件) | -|---|---|---| -| 旁白/系统音 | male=Uncle_Fu / female=Vivian | 36 个双性别 id(见 asset-manifest.md) | -| 林晚(主角) | Serena | n02_linwan n03_radio n12_take n19_betray n21_redeem n24_open n25_secure | -| 猎狗(反派) | Dylan | n07_dog n18_dog | -| 小满(小男孩) | Vivian(童声 instruct) | e1_child | -| 林晨(妹妹录音) | Vivian(收音机 instruct) | n03_msg | -| 广播女声 | Vivian(广播 instruct) | n05_bcast | - -**需要开发配合**:角色台词在两个性别下使用同一份音频。建议引擎对 storyline 做回退:在 `voice//` 找不到的 id 回退读平铺 `voice/`;或由开发/后续轮次把 12 个角色 id 复制进两个子目录(资产侧未复制,避免重复文件进入仓库前等引擎方案定稿)。 - -注意:female 旁白选用 Vivian 而非 Serena,因为 Serena 是主角林晚的音色,旁白与主角同声会混淆;代价是旁白与小满/林晨/广播同 speaker,已用截然不同的 instruct(冷静低沉 vs 童声/收音机/机械广播)区分,请策划试听确认。 - -## 4. 音色分配表 - -| 游戏 | male | female | -|---|---|---| -| storyline | Uncle_Fu(沿用原旁白) | Vivian | -| escape | Uncle_Fu(沿用) | Serena | -| simon | Uncle_Fu | Serena(沿用) | -| rhythm | Uncle_Fu | Vivian(沿用) | -| space | Uncle_Fu | Serena(沿用) | - -Ryan(英语母语音色)未采用:项目已有中文发音投诉(反馈⑥),男声统一中文母语 Uncle_Fu。冒烟样本在 `tools/samples/ryan_zh_*.wav`,如策划试听认为可用,改 `tools/gen_voice_dual.py` 的 `SPEAKERS` 重跑即可。 - -## 5. 需要游戏代码配合的点 - -1. **引擎支持 voice 性别目录切换**:按用户设置在 `audio/voice/male|female/` 之间切换;storyline 需平铺回退(§3)。 -2. **simon 删除 BGM 引用**:资产 `web/simon/audio/bgm/theme.{ogg,m4a}` 已按用户要求删除(bgm/ 目录现为空),请移除 game.js 中的 bgm 加载/播放逻辑及 preload 清单条目。 -3. **语音选择 UI 入口**:需要「男声/女声」设置项(全局或每游戏),默认建议 male=storyline/escape、female=simon/rhythm/space(即各游戏原音色性别),具体由策划定。 -4. 平铺旧 voice 文件待引擎切换完成后通知音频侧清理(R2+)。 - -## 6. 质量遗留问题 - -- 本轮 QC 为自动检查(时长/峰值/两性离群),**未做人工试听**;发音准确性需策划/QA 试听把关。文本层多音字风险清单在 `docs/audio/asset-manifest.md`(如 storyline help 的「重听」建议改「重新听」),确认误读后改台词表重跑单条即可。 -- `simon/female/teach_end` 重生成后 14.8s(male 7.8s),比值正常但偏长,建议试听确认无重复啰嗦。 -- 管线脚本:`tools/gen_voice_dual.py`(含全部台词表,支持 `--game/--gender/--only/--check/--convert`)。 - -## 7. Mureka 花费 - -本轮 $0(无新 BGM 需求;simon BGM 为删除而非重制)。累计 $0.15 / $2.00,未动 `tools/mureka_log.md`(无新条目)。 diff --git a/docs/audio/round2-report.md b/docs/audio/round2-report.md deleted file mode 100644 index 02db1c3e..00000000 --- a/docs/audio/round2-report.md +++ /dev/null @@ -1,59 +0,0 @@ -# R2 音频轮次报告(2026-08-29,音乐音效总监) - -本轮主题:《暗室》《音序回响》重设计语音资产 + escape 环境音扩展。依据 `docs/design/01-escape-redesign.md`、`02-simon-redesign.md` 与 `00-global-ux.md`。Mureka 花费 $0。 - -## 1. 首要交付物 - -`docs/audio/round2-voice-ids.md` —— 两款游戏全部语音 id → 台词全文 → instruct 对照表,含新增/保留/删除标记和代码映射区间(time_band/score_band)。开发照此实现即可。 - -## 2. 新资产统计 - -| 游戏 | 性别 | id 数 | 总时长 | 文件数(ogg+m4a) | -|---|---|---|---|---| -| escape | male | 74 | 346.6s | 148 | -| escape | female | 74 | 320.6s | 148 | -| simon | male | 68 | 190.2s | 136 | -| simon | female | 68 | 154.3s | 136 | - -合计 142 id × 2 性别 = 284 条语音(568 文件)。全部按 R2 新规范重录:每游戏同一 speaker + 同一 instruct 常量(00 文档 §3.2),instruct 不含语速词;一句一文件整句生成,无数字拼接。escape=Uncle_Fu/Serena,simon=Uncle_Fu/Serena(沿用 R1 分配)。 - -环境音新增 5 条(Stable Audio 3,6s 循环,ogg+m4a):`amb_storage amb_cabinet amb_power amb_gate amb_gate_live`,统一归一到 -25dB 均值(对齐旧 amb_drip/-28.7、amb_wind/-21.3 之间),storage/power 轻微偏左、cabinet 轻微偏右 pan 增强房间辨识;staging wav 在 `tools/gen_amb_wav/`。 - -## 3. 删除清单(资产侧已执行,代码侧开发跟进) - -**escape voice**(20 id × 2 性别 × 2 格式 + 平铺): -- 数字拼接链:`n0–n9 nshi unit_min unit_sec time_prefix` → `time_band1–4` 替代 -- 旧开场:`intro tutorial` → GUIDE `g0–g7` 替代 -- 旧提示:`hint_key hint_cabinet hint_box hint_gate` → `stuck_*` 三级提示替代 - -**simon voice**(23 id × 2 性别 × 2 格式 + 平铺): -- 拼接件:`di lun num_shi num_0–num_9 score_pre best_pre` → `round_*`/`score_band*` 整句替代 -- 旧教学:`intro teach_up teach_down teach_left teach_right teach_tap teach_doubletap teach_end listen` → 交互教学 `t*_*` 替代 - -**sfx**:`simon/correct wrong newrecord`、`escape/ui_deny` 删除(反馈改震动,00 文档 §2)。 - -**平铺目录**:escape/simon 的 `audio/voice/*.{ogg,m4a}` 平铺旧文件已全部移除(引擎已支持 gender 目录)。storyline/rhythm/space 的平铺文件未动。 - -## 4. QC 结果 - -- 284 条全部通过:时长 >0.3s、峰值 >0.05、峰值归一 0.89。 -- 两性时长离群检测(>3 倍):escape、simon 均 **ALL OK**,本轮零重生成。 -- 双格式逐条核对:male/female 各目录 id 数与清单精确一致(74/68),无缺失(convert 已改为按台词表驱动,staging 残留旧 id 不会误落盘)。 -- 人工试听仍未做:发音准确性需策划/QA 把关;多音字规避已在新台词写作时执行(如"重听"→"重新听一遍"(reopen_prompt)、避免"还/得/着"歧义写法)。 - -## 5. 给开发的配合说明 - -1. 语音 id 以 `round2-voice-ids.md` 为唯一权威清单;删除 id 不得再引用(文件已不存在,引擎容错会静默跳过,但请清理引用)。 -2. `time_band1–4` 区间映射:`<120s / 120–300s / 300–600s / ≥600s`;`score_band1–6` 映射:`0 / 1–3 / 4–6 / 7–9 / 10–14 / ≥15`。 -3. escape L3 提示 = `stuck_<阶段>_3` + 按当前坐标算路链播 `nav_up/down/left/right`(两句以内);单击检查 = 先重播当前房间 `amb_*` 再播描述。 -4. escape 大门通电后环境音切 `amb_gate_live`;空房 `amb_wind`/`amb_hum` 轮换。 -5. simon 无 BGM(`bgm/` 已空,勿加载);`sfx/gameover` 保留为结束音。 -6. 菜单句 `menu_open/menu_restart/menu_home/menu_back` 两款游戏已各备一份;设置值播报("当前:女声,正常语速")为跨游戏需求,本清单未覆盖,建议 R3 统一处理。 - -## 6. 管线变更 - -- `tools/gen_voice_dual.py`:escape/simon 台词表整体替换为 R2 新版(全常量 instruct);`--convert` 改为按台词表驱动;其余游戏(storyline/rhythm/space)台词表与资产未动,脚本仍可全量复跑。 - -## 7. Mureka 花费 - -本轮 $0(两款游戏均无新 BGM 需求,simon 为去 BGM)。累计 $0.15 / $2.00。 diff --git a/docs/audio/round3-report.md b/docs/audio/round3-report.md deleted file mode 100644 index 583ca263..00000000 --- a/docs/audio/round3-report.md +++ /dev/null @@ -1,68 +0,0 @@ -# R3 音频轮次报告(2026-08-29,音乐音效总监) - -本轮主题:《节拍行者》重构语音 + 3 首 BPM 精确 BGM,《盲航》重构语音备产。依据 `docs/design/03-rhythm-voice-ids.md`、`03-rhythm-rebuild.md`、`04-space-voice-ids.md`。Mureka 本轮 $0.15 / $0.30,累计 $0.30 / $2.00。 - -## 1. rhythm 语音(重构版) - -- 台词表照录 `docs/design/03-rhythm-voice-ids.md`(31 句),合入 `tools/gen_voice_dual.py` 全量替换 R1 版。female=Vivian / male=Uncle_Fu,全表同一 instruct 常量。 -- 生成统计:**male 31 条 / 116.6s,female 31 条 / 105.4s**(ogg+m4a 各 62 文件)。 -- QC:时长/峰值/两性离群(>3 倍)全部 **ALL OK,零重生成**。 -- 旧 id 清理:平铺 9 个旧 id 全部删除(`welcome rules tut_start tut_done grade_* restart`,其中 `grade_* restart` 按新文案重录覆盖,`welcome` 文案变更重录);`tut_start` 彻底弃用。目录现仅含新表 31 id × 2 性别。 - -## 2. rhythm BGM(3 首,Mureka) - -| 曲目 | 选用候选(task/choice) | 源实测 BPM | 处理 | 成品实测 BPM | 循环时长 | -|---|---|---|---|---|---| -| track90《启程》 | 157963509563393 / 157963627134980(首提候选 1) | 98.75 | atempo=0.911392 拉伸 | **90.01** | 85.33s(32 小节) | -| track105《追风》 | 157963652431875 / 157963780292611(首提候选 0) | **105.00 精确** | 无拉伸 | **104.99** | 73.14s(32 小节) | -| track120《星爆》 | 157964212305921 / 157964317097985(重提候选 0) | **120.00 精确** | 无拉伸 | **120.00** | 72.00s(36 小节) | - -- 后处理:首个强 onset 对齐小节头裁剪 → 整数小节 + 末 2 拍与开头 acrossfade 无缝循环 → loudnorm I=-20/TP=-2 → ogg q3 + m4a 128k。成品 mean -21.2/-21.3/-19.9dB。 -- **BPM 测量方法学(重要)**:`librosa.beat.beat_track` 在 22050Hz/hop512 下按整帧量化,105 BPM 附近一档=4.3 BPM(读数 103.36/107.67 都是量化伪影)。改用自写梳状扫描 `tools/bpm_precise.py`(0.05 BPM 步进 + 相位扫描 + 抛物线插值,合成 105.00 点击轨校准通过)。`tools/bpm_check.py`(粗筛)+ `bpm_precise.py`(判定)均留在 tools/。 -- 重试记录:track90 首提 121.0/98.75、重提 80.0/120.0 四候选均不达标,已达"每首最多重试 1 次"上限,按规则停止重提,选用 98.75 拉伸修正(电子乐 8.9% 减速,瞬态影响可接受,成品实测 90.01)。track120 首提不达标、重提即中 120.00。track105 首提即中。 -- 旧 `bgm/groove` 已删除。花费明细已追加 `tools/mureka_log.md`(5 任务 × $0.03 = $0.15)。 - -## 3. space 语音(备产,已完成) - -`docs/design/04-space-voice-ids.md` 在本轮执行期间就绪(52 句),已全量生成: - -- **male 52 条 / 188.0s,female 52 条 / 159.4s**(ogg+m4a 各 104 文件)。female=Serena / male=Uncle_Fu,全表同一 instruct。 -- QC:**ALL OK,零重生成**。 -- 旧 id 清理:`num0–9 num_shi num_bai n_* intro1–4 hit1 hit2 gameover restart` 全部弃用删除(含平铺);`shield_ready shield_block` 沿用 id 重录。 -- 注意:重构版闪避改为**同向**(左来左滑),新台词已按此写死,与 R1 旧台词(反向)语义相反——开发务必以新 game.js + 新语音为准,不可混用。 - -## 4. 删除清单(资产侧已执行) - -- rhythm voice 平铺 9 id;rhythm sfx `judge_good judge_miss judge_perfect tick tick_accent`(反馈改震动、数拍融入 BGM);rhythm `bgm/groove`。 -- space voice 旧 27 id(平铺 + male/female 全部,新表 52 id 重录替换)。 - -## 5. 给开发的配合说明 - -1. **rhythm 乐器音 sfx(已于 R3 收尾补齐,见 §7)**:重构 §4 需要的 `inst_kick inst_snare inst_hat_l inst_hat_r inst_tom_hi inst_tom_lo inst_accent` 已生产落盘;`cue_*` 6 个旧提示音已确认不再使用并删除。 -2. BGM 对网格:三首成品 BPM 实测偏差 ≤0.01%,直接按设计 SPB(0.667/0.571/0.5s)排程即可;循环长度均为整数小节,`src.start(t0)` 从 0 播即是小节头。 -3. rhythm 结算评级句已含准确率档位文案(S=九成五/A=八成五/B=七成/C=不到七成),与 §2 判定阈值一致,代码直接按 rate 映射 grade_* 即可。 -4. space 里程碑触发点:10/25/50/100 颗(`ms_*`);结算 time 档 `<30/30–60/60–120/≥120s`,dodge 档 `<5/5–14/15–49/≥50`。 - -## 6. Mureka 花费 - -本轮 $0.15(5 任务,全部 succeeded);累计 **$0.30 / $2.00**。详见 `tools/mureka_log.md` R3 节。 - -## 7. 补产:rhythm 演奏乐器音 sfx(R3 收尾,2026-08-29) - -重构 §4 的 7 个手势乐器音已补齐,落 `web/rhythm/audio/sfx/`(ogg q3 + m4a 128k): - -| id | 内容 | 生成方式 | 时长 | 峰值 | 声像/调性 | -|---|---|---|---|---|---| -| inst_kick | 底鼓(tap) | Stable Audio 3("punchy deep EDM kick, dry, single hit") | 0.50s | -0.8dB | 中央 | -| inst_snare | 军鼓(doubletap) | Stable Audio 3 | 0.45s | -1.1dB | 中央 | -| inst_tom_hi | 高通鼓(up) | Stable Audio 3(high pitched synth tom) | 0.50s | -1.2dB | 中央,升调感 | -| inst_tom_lo | 低通鼓(down) | Stable Audio 3(low pitched deep synth tom) | 0.60s | -1.0dB | 中央,降调感 | -| inst_hat_l | 闭合镲(left) | Stable Audio 3(closed hi-hat) | 0.20s | -1.1dB | **极左**(L=-1.1dB / R=-22.7dB) | -| inst_hat_r | 开放镲(right) | Stable Audio 3(open hi-hat) | 0.60s | -2.2dB | **极右**(R=-2.2dB / L=-23.3dB) | -| inst_accent | Perfect 亮音叠加层 | **numpy 程序合成**(音高可控) | 0.38s | -1.8dB(0.8 峰值) | A minor 上行琶音 A5→E6→A6,三音 90ms 间隔快衰减 | - -- 调性处理:三首 BGM 均 A minor(Mureka prompt 指定);accent 用 A 小调音级(A/E/A)程序合成,音高精确对齐;鼓件为伪音高打击乐,tom_hi/lo 在 prompt 里分了高低音区。staging wav 在 `tools/gen_inst_wav/`。 -- 后处理:去头静音(silenceremove -45dB)→ 定长裁剪 + 末端 40-100ms 淡出防爆音 → 峰值归一 -1dBFS → hat 先 downmix 再 pan 后归一(避免了 pan 叠加削波的一版返工)。 -- **id 映射注意**:设计文档 §4 写的叠加层 id 是 `sfx/accent`,本轮按主 agent 指令统一命名 `inst_accent`(与其他 6 件同前缀),开发以 `inst_accent` 为准。 -- **cue_* 处置:删除**。通读 03-rhythm-rebuild.md,全文无 cue_* 资产引用(仅 §8.2 有内部变量 `_cues` 指期望音符),§4 手势音全部改走 inst_* 乐器音——旧指令音 `cue_double cue_down cue_left cue_right cue_tap cue_up`(6 id × 2 格式)已删。 -- Mureka 花费不变(本轮 $0,累计 $0.30 / $2.00)。 diff --git a/docs/audio/round4-report.md b/docs/audio/round4-report.md deleted file mode 100644 index 1645f01c..00000000 --- a/docs/audio/round4-report.md +++ /dev/null @@ -1,102 +0,0 @@ -# R4 音频轮次报告(2026-08-29,音乐音效总监) - -本轮主题:新 RPG《剑与声》第一章全套音频资产 + space `menu_main` 改词重录。依据 `docs/design/05-rpg-ch1-voice-ids.md`、`05-rpg-chapter1.md`、`05-rpg-addendum.md`。Mureka 本轮 $0.12 / $0.30,**累计 $0.42 / $2.00**。 - -## 1. 语音(107 旁白 ×2 + 14 NPC) - -| 类别 | 数量 | 总时长 | 落点 | -|---|---|---|---| -| 旁白 male(Uncle_Fu·说书人) | 107 | 282.8s | `web/rpg/audio/voice/male/` | -| 旁白 female(Serena·武侠故事) | 107 | 242.6s | `web/rpg/audio/voice/female/` | -| NPC 单音色(村长/老猎人/头目/寨主) | 14 | 50.9s | `web/rpg/audio/voice/` 平铺 | - -- 台词表照录 05-rpg-ch1-voice-ids.md(104 句)+ addendum §6.2 补录 `foe_hp_high/mid/low`(3 句)= 107 句,已合入 `tools/gen_voice_dual.py`(新增 `rpg` 条目与 `NPCS` 平铺机制,管线断点续跑/`--only`/`--check`/`--convert` 全部适用)。 -- **计数勘误**:台词表头写"NPC 16 句",实表只有 14 句(headman 4 + hunter 4 + elite 3 + boss1 3);合计 107×2+14 = 228 条(×2 格式 = 456 文件),以此为准。 -- QC:时长/峰值/两性离群(>3 倍)**ALL OK,零重生成**。 -- 已知风险:村长与黑风寨主同 speaker(Uncle_Fu)不同 instruct(慈祥和缓 vs 低沉阴狠),按设计"从不在同场景出现"处理,**需人工试听确认区分度**。 - -## 2. BGM(4 首,Mureka) - -| id | task/choice | 选用 | 成品 | -|---|---|---|---| -| village | 157968630808577 / 157968750477315 | 候选 1(229.2s) | 90.00s 循环,mean -23.0dB | -| battle | 157968792551426 / 157968893214722 | 候选 0(212.8s) | 90.00s,mean -20.5dB | -| boss | 157968932864001 / 157969029332994 | 候选 1(178.2s) | 90.00s,mean -22.6dB | -| ending | 157969071407105 / 157969188913155 | 候选 1(248.4s) | 90.00s,mean -21.9dB | - -- 4 任务一次成功无重试。处理:强 onset 起点 → 90s 裁剪 + 首尾 3s crossfade 无缝循环 → loudnorm -20 LUFS → ogg q3 + m4a。RPG 无节拍网格,BPM 不做硬校验。 -- addendum §3 裁定:`bgm/explore` 取消(探索期无音乐),amb 走 sfx 层。花费已追加 `tools/mureka_log.md`。 - -## 3. SFX(35 件) - -- **SA3 29 件**(`tools/gen_rpg_sfx.sh`,staging `tools/gen_rpg_sfx_wav/`):前摇 `tel_*` ×9(含 ch2/3 备用 croc/shaman/puppet/boss2,统一裁 1.0s 格挡窗口)、战斗 `atk_swing atk_hit parry skill_slash enemy_die potion`、探索 `chest drink_water rest door_heavy`、环境 `amb_*` ×11(含备用 swamp/peak,6s→1s 首尾交叉淡化成 5s 循环)。 -- **SYN 5 件**(`tools/gen_rpg_syn.py`):`hurt`(60Hz 低频闷击,与 atk_hit 区分)、`flee`(渐远脚步)、`step`(单步)、`coin`(硬币叮当)、`levelup`(A 小调五声 A4-C5-D5-E5-A5 上行钟声琶音)。 -- 响度:one-shot 峰值 -1dBFS;amb 均值 -25dB(amb_hunter/amb_hall 因 crackle 峰值封顶初次仅 -31dB,已用 `alimiter level=false` 限幅重做至 -25.9dB——R2 学到的 level=true 陷阱的反向应用)。 -- `ui_deny` 按设计不做。`web/rpg/gen/README.md` 有完整清单与再生成方法。 - -## 4. space `menu_main` 改词重录 - -按 round2-review 裁决②(全球菜单约定:单击切换/双击确认),文本由「菜单。左右滑选择,单击确认。」改为「**菜单。单击切换选项,双击确认。**」,双性别重录覆盖 `web/space/audio/voice/male|female/menu_main.*`。 -**同类遗留**:rhythm `menu_main` 文本同为旧约定("左右滑选择,单击确认"),且 rpg 的 `sys_menu`/`shop_open` 按 RPG 豁免条款保持"左右滑+单击"不变(正确)。rhythm 那句建议 R5 一并处理(rhythm 选曲界面非模态菜单仍用左右滑+单击,仅模态菜单改新约定——需策划确认文案后我重录)。 - -## 5. 给开发的 id 总表(可直接 import) - -语音 id 分组(旁白 male/female 同集,NPC 平铺单集): - -```js -const RPG_VOICE = { - intro: ['intro_1','intro_2','intro_3'], - system: ['sys_continue_ask','sys_new_game','sys_saved','sys_rest','sys_drink', - 'sys_menu','sys_menu_resume','sys_menu_save','sys_menu_help', - 'help_explore','help_battle','settings_hint'], - status: ['st_hp_high','st_hp_mid','st_hp_low','st_gold_poor','st_gold_some','st_gold_rich', - 'st_lv_low','st_lv_mid','st_lv_high', - 'st_weapon_none','st_weapon_iron','st_weapon_steel','st_weapon_wind', - 'st_armor_none','st_armor_leather','st_armor_iron'], - foeHp: ['foe_hp_high','foe_hp_mid','foe_hp_low'], // 敌方血量档位(addendum §6.2) - desc: ['desc_village','desc_village_re','desc_path','desc_path_re','desc_fork','desc_fork_re', - 'desc_chest','desc_chest_re','desc_stream','desc_stream_re','desc_hunter','desc_hunter_re', - 'desc_deepwood','desc_deepwood_re','desc_gate','desc_hall'], - option: ['opt_go_path','opt_go_fork','opt_go_chest','opt_go_stream','opt_go_hunter', - 'opt_go_deepwood','opt_go_gate','opt_back','opt_shop','opt_rest','opt_status', - 'opt_investigate','opt_open_chest','opt_drink','opt_talk_hunter','opt_assault', - 'opt_sneak','opt_leave'], - event: ['evt_gold15','evt_nothing','evt_chest','evt_hunter_gift','evt_gate_open','evt_ch1_end'], - battle: ['btl_start_wolf','btl_start_bandit','btl_start_spider','btl_start_elite','btl_start_boss1', - 'cmd_attack','cmd_skill','cmd_defend','cmd_item','btl_nomp','btl_noitem','btl_flee', - 'btl_noflee','btl_enemy_die','btl_win','btl_loot_small','btl_loot_mid','btl_loot_big', - 'btl_levelup','btl_lose','btl_lose_hint','tut_b1','tut_b2','tut_b3','tut_b4','tut_b5'], - shop: ['shop_open','shop_price_sword','shop_price_armor','shop_price_potion', - 'shop_owned','shop_poor','shop_buy'], - npc: ['npc_headman_1','npc_headman_2','npc_headman_3','npc_headman_4', - 'npc_hunter_1','npc_hunter_2','npc_hunter_3','npc_hunter_4', - 'npc_elite_1','npc_elite_half','npc_elite_die', - 'npc_boss1_1','npc_boss1_2','npc_boss1_die'], // 平铺 voice/,无性别目录 -}; -const RPG_BGM = ['village','battle','boss','ending']; -const RPG_SFX = { - telegraph: ['tel_wolf','tel_bandit','tel_spider','tel_elite','tel_boss1', // 1.0s 格挡窗口 - 'tel_croc','tel_shaman','tel_puppet','tel_boss2'], // ch2/3 备用 - combat: ['atk_swing','atk_hit','parry','skill_slash','enemy_die','potion','hurt','flee'], - explore: ['step','coin','chest','drink_water','rest','levelup','door_heavy'], - ambient: ['amb_village','amb_path','amb_fork','amb_chest','amb_stream','amb_hunter', - 'amb_deepwood','amb_gate','amb_hall','amb_swamp','amb_peak'], // 5s 循环,手动 loop 接 sfx 总线 -}; -``` - -配合要点: -1. `amb_*` 需手动 BufferSource loop 接 `_sfxBus`(addendum §3 对实现的唯一新要求;不受 bgm=off 设置影响)。 -2. 前摇音 `tel_` 起点即格挡窗口起点(1.0s),Boss 二阶段 `tel_boss1` 以 playbackRate 1.2 重播(不加资产)。 -3. `foe_hp_*` 为战斗态上滑敌方档位句;降档才播 `st_hp_mid/low`。 -4. 场景 `desc_*` 首次/回访成对(`_re` 后缀为回访短句)。 -5. **设计缺口(请策划补)**:敌人表中山贼标了半血句 `btl_half_bandit`,但台词表无此 id(头目有 `npc_elite_half`)——按 chapter1 §3"无此语音的敌人跳过"可运行,如需补齐请发文案我重录。`npc_boss3_warn`/`btl_half_shaman`/`btl_half_shadow` 属 ch2/3 最小集(addendum §5),等 ch2 台词表。 - -## 6. 遗留问题 - -- 村长/寨主同 speaker 区分度、全部新语音发音准确性:未做人工试听(QC 均为自动检查)。 -- rhythm `menu_main` 旧菜单约定文案(§4)。 -- rhythm 乐器音 `inst_accent` 与设计文档 §4 的 `accent` 命名差异(R3 已按 inst_accent 落盘,开发以此为准)。 - -## 7. Mureka 花费 - -本轮 $0.12(4 任务全成功);累计 **$0.42 / $2.00**,剩 $1.58。明细见 `tools/mureka_log.md` R4 节。 diff --git a/docs/audio/round5-report.md b/docs/audio/round5-report.md deleted file mode 100644 index 95ead435..00000000 --- a/docs/audio/round5-report.md +++ /dev/null @@ -1,39 +0,0 @@ -# R5 音频轮次报告(2026-08-29,音乐音效总监) - -最终轮收尾:两处小批量补录 + 全项目资产终检。Mureka 本轮 $0(累计 **$0.42 / $2.00**,未再变动)。 - -## 1. 补录明细 - -### rhythm `menu_main` 改词重录(与 space R4 同款问题) - -- 旧文案「菜单。左右滑选择,单击确认。」→ 新文案「**菜单。单击切换选项,双击确认。**」(round2-review 裁决②全球菜单约定;rhythm 选曲界面非模态菜单,仍左右滑+单击,不受影响)。 -- 双性别重录(male=Uncle_Fu 4.00s / female=Vivian 4.16s,沿用 rhythm 统一 instruct),覆盖 `web/rhythm/audio/voice/male|female/menu_main.{ogg,m4a}`。台词表已同步更新(`tools/gen_voice_dual.py` RHYTHM_LINES)。 - -### rpg `btl_half_bandit` 补录(设计缺口) - -- 敌人表(05-rpg-chapter1 §4.2)给山贼标了半血句但台词表漏发;按 B 组风格补写「**山贼的攻势缓了下来。**」,已合入 `tools/gen_voice_dual.py` RPG_LINES(B 组,27→含此句)。 -- 双性别生成(male=Uncle_Fu 2.64s / female=Serena 1.52s),落 `web/rpg/audio/voice/male|female/btl_half_bandit.{ogg,m4a}`。rpg 旁白现为 108 id ×2。 -- 触发点:`CHECK_ENEMY_DEAD` 前、山贼 HP 首次 ≤50% 时插播(chapter1 §3 半血叙事钩子)。 - -## 2. 全项目资产终检(`docs/audio/final-asset-check.md`) - -终检脚本 `tools/check_all_assets.py`(可重复执行),检查 6 游戏:双格式配对、0 字节、ffprobe 全量解码、voice 性别目录 id 集合一致性(storyline/rpg 平铺角色句豁免): - -| 游戏 | ogg | m4a | voice 性别一致性 | -|---|---|---|---| -| storyline | 133 | 133 | ✅ 各 36(平铺 48 = 12 角色 + 36 R1 旁白遗产) | -| escape | 166 | 166 | ✅ 各 74 | -| simon | 143 | 143 | ✅ 各 68 | -| rhythm | 72 | 72 | ✅ 各 31 | -| space | 116 | 116 | ✅ 各 52 | -| rpg | 269 | 269 | ✅ 各 108(平铺 14 NPC) | -| **合计** | **899** | **899** | 全部通过 | - -零 0 字节、零解码失败、零格式缺失。 - -## 3. 五轮累计遗留(供后续迭代参考) - -1. **人工试听从未执行**:全部 QC 为自动检查(时长/峰值/离群/BPM/电平)。多音字风险清单在 asset-manifest.md,村长 vs 寨主同 speaker 区分度(R4)为最高优先试听项。 -2. storyline 平铺 36 个 R1 旁白遗产文件待引擎切换验证后清理。 -3. ch2/3 台词表最小集(addendum §5:`npc_boss3_warn`、`btl_half_shaman`、`btl_half_shadow`)待策划补发。 -4. Ryan 音色未采用样本在 `tools/samples/ryan_zh_*.wav`(如策划想给某游戏换年轻男声,改 SPEAKERS 重跑即可)。 diff --git a/docs/audio/round6-report.md b/docs/audio/round6-report.md deleted file mode 100644 index 05acf6d0..00000000 --- a/docs/audio/round6-report.md +++ /dev/null @@ -1,60 +0,0 @@ -# R6 音频轮次报告(2026-08-30,音乐音效总监) - -本轮主题:爆音资产排查修复 + 状态 BGM 7 首 + RPG 战斗教学/全局跳过提示语音。Mureka 本轮 $0.21 / $1.00,**累计 $0.63 / $2.00**。 - -## 1. 爆音排查与修复 - -**工具**:`tools/scan_clicks.py`(扫描)/ `tools/fix_clicks.py`(修复),全程可复跑;原件备份在 `tools/asset_fix_backup/`(71 个文件)。 - -**扫描口径**:峰值 >-1dBFS / 首尾电平突变(首末 1ms 幅值 > 局部 50ms 峰值 15%,局部低于 -40dB 的静音区不判)/ 直流偏置 |DC|>0.001。voice 目录按任务要求**不动**(峰值 0.89 归一策略保留)。 - -**初扫结果(899 个 ogg 全量)**: - -| 类别 | 数量 | 说明 | -|---|---|---| -| sfx/bgm 峰值过热 | 27 | 其中 rpg 15 个 SA3 one-shot 解码峰值高达 +0.9~+6.6dBFS(真·爆音源) | -| sfx/bgm 直流偏置 | 43 | 最大 0.076(rpg/atk_swing) | -| sfx/bgm 首尾突变 | 48 | 老资产无淡入淡出(storyline/escape/simon/space 为主) | -| voice 过热 | 340 | 不修(语音响度策略不动,见任务说明) | - -**根因发现(重要)**:R4 的 rpg SA3 one-shot 源是近满幅高密度波形(rms 0.84 / 峰值 0dBFS,>15kHz 能量 22%),MDCT 有损编码(vorbis/aac/opus 全部)重建时过冲 +4~8dB——ffmpeg `volumedetect` 走 s16 中间格式把超幅削到 0.0dB 显示,**之前的 QC 完全没发现**。本轮修复 encode 环节改为「编码→解码回读真峰值→超 -3dB 则降增益 + 首轮 18kHz 低通→重编」(最多 3 次迭代)。 - -**修复结果**:71 个 sfx/bgm 文件重处理(storyline 10 / escape 10 / simon 7 / rhythm 7 / space 11 / rpg 26),统一标准:one-shot 去直流 + 首尾 8ms cos² 淡入淡出 + 峰值 -3dBFS(真峰值校验后);loop(amb_*/bgm)只去直流(交叉淡化循环不加首尾淡入淡出,避免破坏循环)。rpg one-shot 从 staging 无损 wav 重做,其余从备份初代源修复(避免多次有损转码)。 - -**复扫(终态)**:sfx/bgm 99 个 —— 过热 0 / 直流 0 / 首尾突变 0。**全绿。** - -## 2. 状态 BGM(7 首,docs/design/10-feedback2-state-bgm.md) - -| 资产 | task id | 响度 | 成品 | -|---|---|---|---| -| storyline `bgm/menu` | 158096439705601 | -24 LUFS(menu pad 档) | 90s 循环,mean -24.4dB | -| escape `bgm/menu` | 158096580149249 | -24 | mean -26.1dB | -| simon `bgm/menu` | 158096714629126 | -24(引擎再按 volume 0.3) | mean -25.8dB | -| rhythm `bgm/menu` | 158096850812929 | -24 | mean -25.3dB | -| space `bgm/menu` | 158097039425538 | -24 | mean -25.1dB | -| rpg `bgm/menu` | 158097182294017 | -24 | mean -25.9dB | -| rpg `bgm/explore`(恢复) | 158097360420865 | -20(引擎 volume 0.35) | mean -22.3dB | - -全部一次成功选候选 0;强 onset 起点 + 90s + 3s crossfade 无缝循环 + ogg q3/m4a。既有主题曲全部不动(storyline dark_drone、escape drone、rhythm track90/105/120、space engine、rpg village/battle/boss/ending)。状态→BGM 映射与切换时机以 10 文档 §2-§7 为准。 - -## 3. 新语音(22 条,双性别) - -| id | 游戏 | 条数 | 文案 | -|---|---|---|---| -| `tut_battle_1..5` | rpg | 5×2 | 首次战斗教学:回合制说明 / 左右滑选招式单击出手 / 听前摇双击格挡 / 防御减半与金疮药 / 开场收束 | -| `skip_hint` | 全 6 游戏 | 1×2×6 | 「单击可跳过旁白。」(各游戏既有 instruct 常量) | - -均已合入 `tools/gen_voice_dual.py` 台词表并落盘 male/female 双格式。rpg 旁白现 113 id ×2(+NPC 14 平铺)。 - -## 4. 给游戏层的接线约定 - -1. **菜单/结算音乐**:所有游戏统一 `bgm/menu` id;切换时机与淡入时长按 10 文档逐游戏表(storyline 500ms、escape 400ms/结算 1500ms、simon 600ms/结算延后 1000ms、rhythm 800ms、space 300ms、rpg 战斗 400ms 快切)。 -2. simon 游玩全程不得加载任何 bgm;`bgm/menu` 仅菜单与结算,volume 0.3。 -3. rpg 双层:BGM 层报状态(menu/explore/village/battle/boss/ending),sfx 层 amb 报场景(手动 loop 接 `_sfxBus`,不变);胜利过渡 = battle 淡出 1500ms → explore 淡入 2000ms。 -4. `tut_battle_1..5`:首场战斗(enc_teach_wolf)B_START 时按序链播,二周目起跳过;`skip_hint` 为备选通用句,接入与否由游戏层定。 -5. `bgm=0` 时全部状态音乐不加载(含 menu pad 与 space engine——10 文档 §1.5 裁定)。 - -## 5. 复跑与终检 - -- `tools/check_all_assets.py` 终检已更新(`docs/audio/final-asset-check.md`):928 ogg + 928 m4a,双格式/0 字节/解码/性别集合一致性全绿。 -- 遗留:voice 层 340 个文件峰值 >-1dBFS 属 R1 既定归一(0.89 峰值),非爆音(有 TTS 天然淡入);如需统一可考虑 R7 评估,但会破坏既有响度平衡,不建议。 diff --git a/docs/design/26-audio-interaction-playbook.md b/docs/design/26-audio-interaction-playbook.md new file mode 100644 index 00000000..95871683 --- /dev/null +++ b/docs/design/26-audio-interaction-playbook.md @@ -0,0 +1,729 @@ +# 26 · 纯音频游戏交互设计:第一性原理与落地手册 + +> **给开发者的实现要点摘要** +> 1. 纯音频游戏是玩家和游戏之间的一场对话。好对话需要的每样东西都要做出来:知道谁在说、知道轮到我了、我一开口对方就停、我说「啊?」对方会换个说法、对方不啰嗦(§1、§2)。 +> 2. 原则共 16 条,分为五组:状态可感、输入可信、语言经济、做中学、动机(§3)。 +> 3. 落地靠六个通用模块,全部放在引擎层,所有游戏都能继承:发言权管理器、输入回执、新手手势模式、三档提示、迷茫检测、语音优先级(§4.1~§4.6)。 +> 4. 每款游戏都要交三张设计单:状态×手势矩阵、等待点配置表、首 3 分钟逐秒脚本(§4.7~§4.10,模板见附录)。 +> 5. 验证分三层:自己闭眼冷启动、乱点机器人、真人冷启动测试。合格指标见 §6。 +> 6. 改造现有游戏按 P0~P3 的顺序做,P0 是半天级的小改动,效果最大(§5)。 +> +> 本文和 `research/02-methodology/methodology.md` 的关系:那一份是基于文献、经模拟玩家验证的方法论。本文补的是它漏掉的部分——真实新玩家的行为:会乱点、没耐心、不会先听完再操作。两份冲突时,以本文为准。 + +--- + +## 1. 第一性原理:七个改变不了的事实 + +所有设计决策都从这七条推出来。它们是物理规律和认知规律,不是偏好,不能商量。 + +| # | 事实 | 推论 | +|---|---|---| +| F1 | 声音是串行的,一过就没了,没办法「再瞟一眼」 | 玩家需要的状态信息,要么一直在响,要么随时能查到 | +| F2 | 人同一时间只能听懂一条语言,两句叠在一起两句都听不懂 | 语言通道一次只给一个人用,必须有调度 | +| F3 | 声音记忆只有 2~3 秒,工作记忆大约 4 项 | 一次只说一件事;选项不超过 3 个;关键词放在句首或句尾 | +| F4 | 沉默没有明确含义:可能是卡了、在等我、在处理、故意留白 | 每一段沉默都要被设计出来,而且能听出是哪一种 | +| F5 | 看不见「哪里能点」,屏幕是一整块空白 | 可以做什么,只能靠「试一下就知道」来发现,试错必须没有代价 | +| F6 | 手指离开屏幕后,玩家不知道这一下算没算 | 「操作被收到」这件事本身需要反馈 | +| F7 | 新玩家先动手后看说明,耐心大约 30~60 秒 | 第一分钟之内必须给出第一次成功 | + +--- + +## 2. 核心模型:游戏是一场对话 + +把真人对话的机制逐条翻译成游戏机制。这张表是整个方案的骨架。 + +| 对话机制 | 真人对话里 | 游戏机制 | 落地模块 | +|---|---|---|---| +| 知道谁在说 | 听声音、看嘴型 | 发言权信号:游戏说话时和等玩家时,背景声明显不同 | §4.1 发言权管理器 | +| 交接话轮 | 说完停下来,看着你 | 固定的「该你了」提示音,之后接持续的等待声 | §4.1 | +| 附和回应 | 「嗯」「好」 | 每次操作 100ms 内响一个回执声 | §4.2 输入回执 | +| 插话 | 你一开口对方就停 | 玩家一操作,游戏立刻闭嘴 | §4.6 语音优先级 | +| 修复误解 | 「啊?」然后换个说法 | 主动重听;第二次用更短的说法 | §4.4 三档提示 | +| 确认听懂 | 看对方反应 | 让玩家做一次动作,不问「听懂了吗」 | §4.10 首 3 分钟脚本 | +| 不啰嗦 | 说过的不原样重复 | 同一信息每出现一次就更短 | §4.4 | +| 看出对方迷糊 | 对方皱眉,你就放慢、拆开讲 | 从乱点里识别迷茫,然后接管 | §4.5 迷茫检测 | + +**检验方法**:把游戏想象成坐在你对面的一个人,问自己:「一个正常人这样跟我说话,我会不会觉得他很怪?」会,就是设计有问题。 + +--- + +## 3. 十六条原则 + +每条都写了「规则」和「参数」。参数是起步值,真人测试后再调。 + +### A 组:状态可感 + +**原则 1:两种基本状态要一直听得出来。** +- 规则: + - 「游戏在说」的时候,音乐被压低。 + - 「轮到你」的时候,先响一次交接提示音,再接一个持续的等待声,一直到玩家操作为止。 + - 语音链的句与句之间不能掉回「轮到你」的听感:句间不恢复音乐音量,也不起等待声。 +- 参数:提示音响完 400ms 后起等待声;玩家一操作,等待声在 80ms 内淡出。 +- 反例:最后一句说完就彻底安静。 + +**原则 2:状态要少,每一种都要教。** +- 规则:新手期最多暴露 3 种状态(听讲 / 轮到你 / 危险)。每个状态声第一次出现时,配一句解释。 +- 反例:设计了 8 个状态提示音,一个都没教。 + +**原则 3:随时能问「我在哪、我该干嘛」。** +- 规则:用一个固定手势触发,建议用上滑。回答只有一句,按「位置 → 目标 → 现在能做的一个动作」的顺序说。 +- 参数:不超过 20 个字,约 4~5 秒。 +- 反例:一上滑就念 26 秒的完整帮助。 + +### B 组:输入可信 + +**原则 4:每次操作都有声音回执。** +- 规则:回执统一三种——执行了、此刻无效、请稍等(§4.2)。必须是声音,震动只能当补充。 +- 参数:100ms 内出声;回执声长度不超过 0.2 秒。 +- 反例:无效操作只震一下。电脑和 iPhone 上就是一片安静。 + +**原则 5:玩家一动,游戏就停。** +- 规则: + - 游戏说话时玩家操作:立刻停下当前这句,先响应这次操作。 + - 排队中已经过时的提示、纠错句全部丢掉。 + - 只有「关键句」可以不打断。关键句要短于 3 秒,玩家此时操作给「请稍等」回执。 +- 反例:玩家已经走到下一步了,游戏还在排队播上一步的纠错。 + +**原则 6:手势少,而且含义永远不变。** +- 规则: + - 新手期只开放三个手势:单击、左右滑、上滑。不识别双击。 + - 所有游戏共用同一张手势表。 + - 容易误触的操作(连点、乱滑)绝不绑定重播、重开等破坏性功能。 + - 破坏性操作要二次确认。 +- 反例:双击 = 整段剧情从头重播。 + +**原则 7:无效操作解释一次,之后只给回执。** +- 规则:第一次无效操作,说一句原因和正确做法;以后只给回执声;连续 3 次无效操作,触发迷茫接管。 + +### C 组:语言经济 + +**原则 8:一次只说一件事,动作放在最前。** +- 规则:指令句按「动作 + 结果」来写,例如「向左滑,开门。」 +- 参数: + - 指令句不超过 12 个字,约 3 秒。 + - 叙事单句不超过 25 个字。 + - 选项不超过 3 个。 + +**原则 9:同一条信息,每重复一次就更短。** +- 规则: + - 每条提示写三档:完整句 → 短句 → 只有提示音。 + - 游戏主动给的提示,永远不原样重复。 + - 只有玩家主动要求重听,才播完整原句,并在前面加「再说一遍:」。 + +**原则 10:语言、音效、系统音、音乐各管一件事。** +- 规则: + - 语言:你要做什么、发生了什么具体的事。 + - 内容音效:发生了哪一类事。 + - 系统音:你的操作收到没有、现在轮到谁。 + - 音乐和环境声:在哪个场景、有多紧张。 +- 系统音和内容音的音色必须明显不同。建议系统音一律用合成音,内容音一律用真实录音。 + +### D 组:做中学 + +**原则 11:先做后讲,一次只学一样,学完马上用。** +- 规则: + - 第一句话就让玩家动手。 + - 一次只教一个新东西,教完马上用一次。 + - 用对了:给一个奖励声,加一句不超过 4 个字的肯定,比如「对。」,然后马上进入下一步。 + - 做对过的事不再教第二遍。 + - 需要两句以上才讲得清的机制,简化或者砍掉。 +- 渐进撤离:同一件事连续做对 3 次,就不再给这件事的语音提示,只给提示音。 + +**原则 12:迷茫要从「乱点」里识别,不只看「不动」。** +- 规则:用 §4.5 的信号判断迷茫,分三级接管:短提示 → 缩小选择 → 自动演示。 + +### E 组:动机 + +**原则 13:乱点也要有收获。** +- 规则:每个方向、每个物体都给一个有特点、带信息的声音反应(比如「这边是墙」)。新手期乱点不扣任何资源。 + +**原则 14:三层循环,每层都有回报。** +- 参数: + - 微循环 1~3 秒:操作一次,得到一次反馈。 + - 小循环 20~30 秒:完成一个小目标,听到进展。 + - 大循环 3~5 分钟:一局结束,给结算,并留一个悬念。 +- 检查方法:每 30 秒切一刀,看这一段有没有能听出来的进展。没有,就是流失点。 + +**原则 15:失败要便宜。** +- 规则: + - 失败后 2 秒内用一句话归因,格式是「原因 + 下次怎么做」。 + - 原因必须真实。玩家根本没出手,就不能说「慢了半拍」。 + - 说完直接回到失败前那一步。 + - 新手期前两次失败不扣资源,第三次失败自动降难度。 + +**原则 16:随时能走,回来一句话接上。** +- 规则:任何时刻退出都自动存档;回来 5 秒内,用一句话复述「刚才在做什么 + 现在做什么」。 + +--- + +## 4. 落地方法 + +本节给的是可以直接照做的设计方法和实现骨架。代码都是**示意**,写法对齐本项目引擎的现有接口(`web/engine/audio.js`、`gesture.js`、`guide.js`);实际写的时候以引擎代码为准。 + +### 4.1 发言权管理器(Floor):解决「搞不清现在是什么状态」 + +**是什么**:全局只维护一个「现在谁在说话」的状态。所有语音播放和交出话轮的动作都经过它;它负责背景声怎么变。 + +**状态定义**: + +| 状态 | 含义 | 听感 | 玩家操作时 | +|---|---|---|---| +| `SPEAKING` | 游戏在说一段话,整条语音链都算 | 音乐压低;句间不恢复音乐、不起等待声 | 可打断的句:立刻停,按这次操作处理;关键句:给「请稍等」回执 | +| `YOUR_TURN` | 在等玩家操作 | 交接提示音,然后是持续的等待声 | 等待声淡出,交给游戏处理 | +| `BUSY` | 游戏在演出动画音效,或者在处理 | 只有内容音效,没有等待声 | 给「请稍等」回执,不执行 | +| `ACTION` | 实时玩法中(节奏、闪避) | 玩法本身的声音就是状态 | 直接交给游戏,不打断任何东西 | + +**转换规则**: +1. 调 `speak()` 时进入 `SPEAKING`。整条链播完后才离开,句与句之间一直算 `SPEAKING`。 +2. 离开 `SPEAKING` 时,只能去两个地方:`YOUR_TURN`(交出话轮)或 `BUSY`(接着演出)。不允许「什么都不是」的安静状态。 +3. 进入 `YOUR_TURN` 必须经过 `yieldTurn(waitPoint)`。它会一起做四件事:播交接提示音、起等待声、挂提示计时、开启迷茫检测。 + +**等待声怎么做**(运行时合成,不需要资产): +- 听感要求:轻、有规律、不烦人、手机外放也能听到。手机扬声器 200Hz 以下基本放不出来,所以频率不能太低。 +- 起步参数:三角波 220Hz 和 330Hz 两声「嘟—嘟」,每声 60ms,两声间隔 180ms,每 1.4 秒重复一次;增益 0.03,大约是 `guide.js` 里提示音增益 0.12 的四分之一。 +- 等待时间超过 20 秒,等待声的间隔拉长到 2.5 秒,更像在耐心地等,不显得催促。 +- 剧情类游戏可以把合成声换成一段很轻的环境循环(风声、钟摆),但必须全游戏统一。 + +**实现骨架(示意)**: + +```js +// web/engine/floor.js(示意) +export class Floor { + constructor(audio, guide) { + this.audio = audio; + this.guide = guide; + this.state = 'BUSY'; + this._bed = null; // 等待声定时器 + this._chain = null; // 当前语音链 { ids, i, critical, onDone, token } + this._token = 0; + } + + /** 播一段话(整条链)。critical=true 表示关键句,不可被打断。 */ + speak(ids, { critical = false, onDone } = {}) { + this._stopBed(); + this.state = 'SPEAKING'; + const token = ++this._token; + this._chain = { ids, i: 0, critical, onDone, token }; + this._next(token); + } + + _next(token) { + const c = this._chain; + if (!c || c.token !== token) return; // 链已被打断,作废 + if (c.i >= c.ids.length) { // 整条链播完 + this._chain = null; + this.state = 'BUSY'; // 由 onDone 决定交出话轮还是继续演出 + c.onDone && c.onDone(); + return; + } + const id = c.ids[c.i++]; + this.audio.play(`voice/${id}`, { onComplete: () => this._next(token) }); + } + + /** 交出话轮:交接提示音 → 400ms 后起等待声 → 挂提示与迷茫检测。 */ + yieldTurn(waitPoint) { + this.state = 'YOUR_TURN'; + this.guide.turn({}); + setTimeout(() => this.state === 'YOUR_TURN' && this._startBed(), 400); + waitPoint && waitPoint.arm(); // 见 §4.4 / §4.5 + } + + /** + * 输入先经过这里。返回值: + * 'consumed' = 发言权层已处理(例如打断了语音),游戏不必再处理; + * 'busy' = 忽略这次操作,给「请稍等」回执; + * null = 交给游戏处理。 + */ + handleInput(g) { + if (this.state === 'SPEAKING') { + if (this._chain && this._chain.critical) return 'busy'; + this._token++; // 作废整条链 + this._chain = null; + this.audio.stopVoice(); + this.state = 'YOUR_TURN'; + return null; // 打断后,这次操作照常交给游戏 + } + if (this.state === 'BUSY') return 'busy'; + this._stopBed(); + return null; + } + + _startBed() { /* 用 audio.ctx 合成两声「嘟—嘟」,setInterval 1400ms 循环 */ } + _stopBed() { /* 清除定时器,当前这声 80ms 淡出 */ } +} +``` + +> 打断之后,这次操作是「只打断」还是「打断并执行」? +> - 单击:只打断,等于跳过这句。原因是玩家单击多半只是想跳过。 +> - 滑动、上滑:打断并执行。滑动是有明确意图的操作。 +> +> 这条规则写进手势表(§4.3),所有游戏一致。 + +**音乐压低要跨整条链**:`audio.js` 现在每句播完都会检查队列,队列空了就恢复音乐音量。如果链是游戏层用 `onComplete` 一句一句接起来的,句间队列是空的,音乐就会被恢复,玩家就听到一次「好像轮到我了」。有两种修法:让 Floor 在整条链期间自己持有一次压低;或者给 `audio` 加一个 `holdDuck(true/false)`。 + +### 4.2 输入回执:解决「点了没反应 / 不知道这下算没算」 + +**三种回执**。音色要避开 `guide.js` 里已有的提示音——`turn` 是 1318Hz 的风铃声,`nudge` 是 880Hz 的木鱼声: + +| 回执 | 用在哪里 | 合成参数(起步值) | 听感 | +|---|---|---|---| +| `ok` 执行了 | 操作被游戏接受并生效 | 三角波 1600Hz,时长 35ms,起音 2ms,快速衰减,增益 0.08 | 清脆的「嗒」 | +| `invalid` 此刻无效 | 收到了,但现在用不上 | 正弦波 260Hz 滑到 200Hz,时长 120ms,起音 10ms,增益 0.10 | 低沉柔和的「咚」,不像惩罚 | +| `busy` 请稍等 | 游戏正在说关键句或正在处理 | 正弦波 520Hz,时长 40ms,增益 0.04 | 很轻的「嗯」 | + +**规则**: +- 回执由引擎统一播,游戏只负责告诉引擎这次操作的结果是哪一种。 +- 游戏已经为这次操作播了很明显的内容音(脚步、开门),可以省掉 `ok` 回执,免得声音堆叠。`invalid` 和 `busy` 永远都要播。 +- 回执在任何设备上都要出声,不依赖震动。震动可以照常叠加。 + +**接线方式**:在手势回调外面包一层管道。 + +```js +// 示意:每款游戏的 attachGestures 回调都换成这个管道 +function makeInputPipeline({ floor, confusion, receipts, invalidHints, game }) { + return (g, info) => { + confusion.record(g); // §4.5 + const pre = floor.handleInput(g); // §4.1 + if (pre === 'busy') { receipts.play('busy'); return; } + if (pre === 'consumed') { receipts.play('ok'); return; } + + // 游戏处理函数返回 'ok' | 'invalid' | 'silent-ok'(已有明显内容音) + const r = game.onGesture(g, info) || 'ok'; + if (r === 'ok') receipts.play('ok'); + if (r === 'invalid') { + receipts.play('invalid'); + invalidHints.explainOnce(game.stateName(), g); // 原则 7:每个「状态 × 手势」只解释一次 + confusion.recordInvalid(); + } + }; +} +``` + +**防连点**:单击执行了「继续」或「跳过」之后的 400ms 内,再来的单击只给 `busy` 回执,不执行。这样点两下不会连跳两句,也不会误进下一段。 + +### 4.3 手势宪法与新手模式:解决「操作不符合预期」 + +**新手手势表**(全部游戏共用): + +| 手势 | 唯一含义 | 游戏在说话时 | 等你操作时 | +|---|---|---|---| +| 单击 | 继续 / 确定 / 听一下 | 只跳过当前这句 | 执行「继续」或「确定」 | +| 左滑 / 右滑 | 选择 / 移动 | 打断,然后执行选择或移动 | 执行选择或移动 | +| 上滑 | 我在哪、我该干嘛 | 打断,然后回答一句 | 回答一句 | +| 双击 | 新手期不存在 | 按一次单击处理 | 按一次单击处理 | +| 下滑、长按 | 新手期不开放 | 给 `invalid` 回执 | 给 `invalid` 回执,第一次附一句解释 | + +**怎么关掉双击、同时去掉 300ms 延迟**:`attachGestures` 支持第三个参数,把双击窗口设成 0: + +```js +attachGestures(el, pipeline, { doubleTapWindow: 0 }); +``` + +这样松手就马上判定为单击,不会再产生双击。键盘上的回车仍然会直接发 `doubletap`,所以游戏层在新手模式下要把 `doubletap` 当成 `tap` 处理。 + +**解锁阶梯**:满足条件后解锁下一个手势,按原则 11 的方式教——先说一句,马上用一次。 + +| 阶段 | 解锁条件 | 解锁的手势 | 教学句示例 | +|---|---|---|---| +| 0 | 首次进入 | 单击、左右滑 | 「点一下屏幕。」 | +| 1 | 完成第一个小目标 | 上滑 | 「想知道自己在哪,就向上滑。」 | +| 2 | 完成第一局 | 下滑(菜单) | 「向下滑,可以打开菜单。」 | +| 3 | 累计玩了 3 局 | 双击(重听) | 「没听清,就双击,再听一遍。」 | +| 4 | 玩家主动在大厅开启 | 游戏专属的高级手势 | 由各游戏自己定义 | + +解锁进度存到 `_settings` 或各游戏的进度数据里,跨局记住。老玩家可以在大厅一键「全部解锁」。 + +**如果某款游戏的核心玩法必须用四向滑动**:把帮助换到长按,而且所有「四向滑动是玩法」的游戏都统一这么换。不允许每款游戏各换各的。 + +### 4.4 三档提示:解决「话音重复」 + +**数据结构**:每个等待点的提示写成三档。 + +```js +// 示意:等待点的提示配置 +const HINT_FIND_KEY = { + full: 'hint_key_full', // 「钥匙在左边的抽屉里,向左滑去拿。」 + short: 'hint_key_short', // 「向左滑。」 + earcon: 'turn', // 只播交接提示音 +}; +``` + +**选择规则**: + +```js +// 示意:同一等待点的第 n 次被动提示 +function pickHint(hint, n) { + if (n === 0) return { voice: hint.full }; + if (n === 1) return { voice: hint.short }; + return { earcon: hint.earcon }; // 第 3 次以后只给提示音 +} + +// 玩家主动要求重听:永远给完整句,并加前缀 +function replayOnRequest(hint) { + return ['sys_say_again', hint.full]; // 「再说一遍:」+ 完整句 +} +``` + +**台词表约定**: +- 每条提示的三档 id 用同一个前缀,后缀是 `_full` 和 `_short`。 +- `_short` 不超过 6 个字,只保留动作。 +- 全项目只需要一句 `sys_say_again`「再说一遍:」,双性别各录一条。 + +**计数的作用范围**:计数按「这一个等待点」来算,离开这个点就清零。同一个点,玩家做了一次有效操作但还没完成目标,计数不清零,下一次提示接着变短。 + +### 4.5 迷茫检测:解决「瞎点、不知道怎么玩」 + +**信号**(在滑动窗口里统计): + +| 信号 | 判定 | 权重 | +|---|---|---| +| 连续无效 | 10 秒内有 3 次 `invalid` | 2 | +| 反复重听 | 10 秒内主动重听 2 次以上 | 1 | +| 乱滑 | 2 秒内滑了 3 个以上不同方向 | 1 | +| 同步骤失败 | 同一个等待点失败 2 次 | 2 | +| 静默 | 动作类 6 秒、剧情类 18 秒没有任何操作 | 1 | + +**判定**:权重累计达到 2 就触发接管,然后进入 15 秒冷却期,期间不再触发。 + +**三级接管**: + +| 级别 | 做什么 | 例子 | +|---|---|---| +| 1 提示 | 只说下一步的一个动作,用该等待点的 `_short` 句 | 「向左滑。」 | +| 2 缩小 | 只保留一个正确手势,其他手势只给 `invalid` 回执、不产生任何效果 | 「现在只要向左滑。」 | +| 3 演示 | 游戏自己完成这一步,再把话轮交回 | 「听我做一次。」→ 播放向左走的声音 →「现在你来。」 | + +同一个等待点,每触发一次接管就升一级。第 3 级用过后,这个等待点就停止接管,只保留等待声。 + +**实现骨架(示意)**: + +```js +// web/engine/confusion.js(示意) +export class Confusion { + constructor({ onConfused }) { + this.events = []; // { t, kind, dir? } + this.cooldownUntil = 0; + this.onConfused = onConfused; // (level) => void,由当前等待点实现 + this.level = 0; + } + record(g) { this._push({ kind: 'input', dir: g }); } + recordInvalid() { this._push({ kind: 'invalid' }); } + recordReplay() { this._push({ kind: 'replay' }); } + recordFail() { this._push({ kind: 'fail' }); } + resetForWaitPoint() { this.events = []; this.level = 0; } + + _push(e) { + const now = performance.now(); + this.events.push({ t: now, ...e }); + this.events = this.events.filter(x => now - x.t < 10000); + if (now < this.cooldownUntil) return; + if (this._score(now) >= 2 && this.level < 3) { + this.level++; + this.cooldownUntil = now + 15000; + this.onConfused(this.level); + } + } + + _score(now) { + const n = k => this.events.filter(x => x.kind === k).length; + const recentDirs = new Set(this.events + .filter(x => x.kind === 'input' && now - x.t < 2000 && x.dir !== 'tap') + .map(x => x.dir)); + let s = 0; + if (n('invalid') >= 3) s += 2; + if (n('replay') >= 2) s += 1; + if (recentDirs.size >= 3) s += 1; + if (n('fail') >= 2) s += 2; + return s; + } +} +``` + +静默信号继续用现有的 `guide.watchdog`,把它到点的回调也接进同一套接管逻辑。 + +### 4.6 语音优先级:解决「我操作了它还在说」「纠错迟到」 + +每一句语音在台词表里标一个类别: + +| 类别 | 例子 | 能否被玩家操作打断 | 排队时是否会过期 | +|---|---|---|---| +| `critical` 关键 | 规则的唯一一次说明、结局、关键获得 | 不能(给 `busy` 回执) | 不过期 | +| `content` 内容 | 剧情、场景描述、对白 | 能 | 不过期 | +| `hint` 提示 | 看门狗提醒、操作提示 | 能 | 1.5 秒内没能播出就丢弃 | +| `feedback` 反馈 | 归因、纠错、「对。」 | 能 | 1.0 秒内没能播出就丢弃 | + +**硬规则**: +- `critical` 句必须短于 3 秒。超过 3 秒就拆开,只把真正关键的那一句标成 `critical`。 +- `hint` 和 `feedback` 永远不排到长链后面。这类句子只在当前空闲时播,播不出去就丢掉。现有 `guide._say` 的「对白在播时跳过」已经是这个思路,这里再加上过期时间。 +- 玩家的一次有效操作,会把排队中所有 `hint` 和 `feedback` 清空。它们针对的是旧局面。 + +### 4.7 状态 × 手势矩阵:消灭「瞎点不符合预期」 + +**做法**: +1. 每款游戏列出玩家能感知的状态(最多 3~6 个)。 +2. 列出新手期开放的所有手势,再加一列「连点 / 乱滑」。 +3. 每一格写:会发生什么、给哪种回执、有没有语音。 + +**示例(《找猫》)**: + +| 状态 | 单击 | 左 / 右滑 | 上滑 | 下滑(未解锁) | 连点 / 乱滑 | +|---|---|---|---|---|---| +| 游戏在说 | 跳过这句 + `ok` | 打断,执行移动 | 打断,回答「我在哪」 | `invalid` + 首次解释 | 只处理第一下,其余 `busy` | +| 轮到你 | 听一下周围(猫叫) | 移动 + 脚步声 | 回答「我在哪」 | `invalid` + 首次解释 | 计入迷茫信号 | +| 结算 | 再来一局 | `invalid` + 首次解释 | 播本局结果 | `invalid` | 只处理第一下 | + +**验收**: +- 没有空格。 +- 没有任何一格是「没有声音」。 +- 「连点 / 乱滑」列里没有重播、重开、跳过多段这类效果。 +- 同一列里,同一手势的含义基本一致。 + +**可以自动检查**:在 `web/harness/` 里遍历每个状态,每个手势都发一次,断言 100ms 内有一次回执或内容音。见 §6.2。 + +### 4.8 台词写作规范:让每一句都值得听 + +**句式模板**: + +| 用途 | 模板 | 好的写法 | 差的写法 | +|---|---|---|---| +| 指令 | 动作,结果。 | 「向左滑,开门。」 | 「如果你想开门,可以尝试向左滑动。」 | +| 交出选择 | 选项一 + 手势,选项二 + 手势。 | 「左滑,救人。右滑,离开。」 | 「你现在面临一个艰难的选择,你可以……」 | +| 我在哪 | 位置。目标。一个动作。 | 「厨房。找钥匙。左右滑听听周围。」 | 念一遍完整规则 | +| 归因 | 原因。下次怎么做。 | 「慢了。听到猫叫马上滑。」 | 「很遗憾,失败了,没关系。」 | +| 肯定 | 不超过 4 个字 | 「对。」「漂亮。」 | 「你做得非常好,现在我们继续下一步。」 | +| 解释无效操作 | 现在是什么情况。能做什么。 | 「还在讲故事。点一下可以跳过。」 | 「这个操作在这里用不上。」(没有告诉玩家该做什么) | + +**检查规则**(可以写一个脚本扫台词表): +- 指令类不超过 12 个字,短句档不超过 6 个字,叙事句不超过 25 个字。 +- 句中出现「如果」「可以尝试」「您可以」,标黄复查。 +- 同一句里出现 2 个以上手势名,标黄复查:一句只教一个。 + +### 4.9 失败与重试 + +**每款游戏都要写一张失败表**: + +| 失败类型 | 怎么判定 | 归因句 | 回到哪里 | 新手期代价 | +|---|---|---|---|---| +| 方向错 | 手势方向和目标不一致 | 「在左边。」 | 这一步开头 | 不扣 | +| 太慢 | 窗口内出过手,但晚了 | 「慢了。听到就滑。」 | 这一步开头 | 不扣 | +| 没出手 | 窗口内没有任何操作 | 「这下没接。声音一停就轮到你。」 | 这一步开头 | 不扣 | +| 第三次失败 | 同一步失败 3 次 | 「我放慢一些。」 | 这一步开头,降速重来 | 不扣 | + +**规则**: +- 归因必须在 2 秒内播出。玩家一操作,归因句就作废(§4.6 的 `feedback` 类)。 +- 「回到哪里」一定是离失败最近的检查点,不重播开场,不重念规则。 + +### 4.10 首 3 分钟逐秒脚本 + +每款游戏都要写一份,并且真人测过一次。 + +**模板**: + +| 时间 | 游戏说 / 播 | 期待玩家做 | 如果玩家没做 | 如果玩家做错 | 这一步的目的 | +|---|---|---|---|---|---| +| 0~3 秒 | | | | | 第一句就让玩家动手 | +| 3~10 秒 | | | | | 第一次成功 | +| 10~30 秒 | | | | | 教会「轮到你」这个状态 | +| 30~60 秒 | | | | | 在熟悉的动作上建立信心 | +| 60~120 秒 | | | | | 教第二个手势,马上用 | +| 120~180 秒 | | | | | 第一次小胜利 + 留悬念 | + +**示例(《找猫》)**: + +| 时间 | 游戏说 / 播 | 期待玩家做 | 如果玩家没做 | 如果玩家做错 | +|---|---|---|---|---| +| 0~3 秒 | 一声清晰的猫叫 →「点一下屏幕。」 | 单击 | 6 秒后:「点一下。」再 6 秒只给提示音 | 滑动:`invalid` +「先点一下就好。」 | +| 3~6 秒 | `ok` 回执 → 右边一声猫叫 →「猫在右边。向右滑。」 | 右滑 | 「向右滑。」 | 左滑:撞墙声 +「在右边。」 | +| 6~10 秒 | 脚步声 → 呼噜声 → 奖励音 →「找到了!」 | 无 | | | +| 10~15 秒 | 「听到这个声音,就是轮到你了。」→ 交接提示音 → 等待声 | 无 | | | +| 15~60 秒 | 3 只猫,方向随机,每只都先交接再等 | 左 / 右滑 | 走三档提示 | 归因 + 本步重来 | +| 约 60 秒 | 「想知道自己在哪,就向上滑。」 | 上滑 | 「向上滑。」 | `invalid` + 首次解释 | +| 60~180 秒 | 猫叫变远变轻,加一个干扰声 | 左 / 右滑 | 迷茫接管 | 第 3 次失败降速 | +| 约 180 秒 | 奖励音 + 结算 →「下一关,会有两只猫。」 | 单击 = 下一局 | | | + +**节奏检查**: +- 3 秒内有第一次操作。 +- 10 秒内有第一次成功。 +- 每 30 秒至少一次能听出来的进展。 +- 整段没有一句纯规则说明。 + +### 4.11 声音分层与混音 + +| 优先级 | 层 | 相对电平(起步值) | 规则 | +|---|---|---|---| +| 1 | 输入回执 | 增益 0.04~0.10 | 永远最先响,不被任何东西压住 | +| 2 | 语音 | 基准 | 同一时间只有一条 | +| 3 | 关键事件音(危险预警) | 比背景响约 10dB | 起音快;预警到判定之间至少 1.5 秒 | +| 4 | 普通内容音效 | 正常 | | +| 5 | 等待声 / 状态声 | 增益约 0.03 | 很轻,但不停 | +| 6 | 音乐、环境声 | 语音播放时压低约 12dB | 整条语音链期间都保持压低(§4.1) | + +**硬规则**: +- 同一时间能听清的声音流最多 2 条。 +- 区分东西主要靠音色,其次靠节奏,不能单靠音高。 +- 系统音(回执、交接提示音、等待声)全部用合成音;内容音用录音。两类永远不混用。 + +### 4.12 断点复述 + +- 退出时,把当前等待点的 id 和一句「复述句 id」存进进度数据。 +- 回来后,先播状态声,再播复述句,然后 `yieldTurn()`。 +- 复述句模板:「你在 X,正在 Y。Z 可以继续。」例如「你在厨房,正在找钥匙。向右滑去卧室。」 + +--- + +## 5. 改造现有游戏的顺序 + +按性价比排序。每一档做完都拿 §6.1 的冷启动测试试一次,再决定做不做下一档。 + +### P0:半天级小改动,直接解决「点两下就重复」「点了没反应」 + +1. **新手模式关掉双击**:`attachGestures(..., { doubleTapWindow: 0 })`,`doubletap` 当 `tap` 处理。点两下就不会再从头重播,单击也没有 300ms 延迟了。 +2. **回执三件套**:用 `audio.ctx` 合成 `ok`、`invalid`、`busy` 三个声音,接进 §4.2 的输入管道。先保证「无效操作有声音」。 +3. **防连点**:单击执行「继续」或「跳过」之后 400ms 内的单击只给 `busy`。 +4. **滑动打断**:游戏说话时玩家滑动,立刻 `stopVoice()`,然后执行这次滑动。 + +### P1:解决「搞不清当前状态」 + +5. **发言权管理器和等待声**(§4.1),包括音乐压低跨整条链。 +6. **三档提示**(§4.4):先改每款游戏最常出现的 3 个等待点,加 `sys_say_again`。 +7. **语音分类和过期**(§4.6):给台词表加类别列,引擎按类别处理。 + +### P2:解决「不知道怎么玩」 + +8. **重写首 3 分钟**(§4.10):每款游戏一份逐秒脚本,砍掉开场的规则说明。 +9. **迷茫检测**(§4.5):接入三级接管。 +10. **状态 × 手势矩阵**(§4.7):每款游戏补一张,逐格修掉没有声音的格子。 + +### P3:解决「没耐心继续」 + +11. **手势解锁阶梯**(§4.3)。 +12. **失败表和就近重试**(§4.9)。 +13. **30 秒进展检查**:每款游戏按原则 14 切片检查,补奖励点。 +14. **断点复述**(§4.12)。 + +**建议的起步方式**:先只挑一款游戏,建议 `storyline`(交互最简单,问题最集中)做完 P0 和 P1,自己闭眼玩一次,确认体感改善了,再推广到其他游戏,并把通用部分抽进引擎。 + +--- + +## 6. 验证方法 + +### 6.1 自己闭眼冷启动(每次改动后,10 分钟) + +- 用手机、戴耳机、闭眼,假装第一次玩。 +- 必须故意做这些事: + 1. 不等语音说完就单击; + 2. 连点两下、连点三下; + 3. 往错误方向滑; + 4. 乱滑四个方向; + 5. 放着不动 30 秒; + 6. 中途退出再进来。 +- 每一次「这下发生了什么?」都记一行:时间、做了什么、听到了什么、期待的是什么。 + +### 6.2 乱点机器人(自动化,每次提交跑一次) + +在 `web/harness/` 加一个急躁新手模拟器,模拟真实新手的坏习惯: + +| 行为 | 概率 / 参数 | +|---|---| +| 语音开始后 0.5~2 秒就单击 | 40% | +| 连点两到三下(间隔 80~250ms) | 20% | +| 随机方向滑动 | 20% | +| 1 秒内没听到回应就再点一次 | 100% | +| 发呆 10~30 秒 | 10% | + +**自动统计的指标**: +- 无声操作数:操作后 100ms 内没有任何回执或内容音。 +- 被动原句重复数:同一个语音 id 在没有主动重听请求的情况下,30 秒内播了两次。 +- 打断失败数:操作之后,同一句语音还在播超过 150ms(`critical` 句除外)。 +- 过期提示数:`hint`/`feedback` 句在触发它的操作之后超过 1.5 秒才播。 +- 迷茫触发次数,以及触发后多久完成当前等待点。 +- 第一次成功的时间。 + +### 6.3 真人冷启动测试(每档改完做一次,3~5 人) + +**规则**:找没玩过的人,每人 5 分钟,全程不解释任何东西。请他边玩边把想法说出来。 + +**记录表**: + +| 项目 | 第 1 人 | 第 2 人 | 第 3 人 | +|---|---|---|---| +| 第一次操作(秒) | | | | +| 第一次成功(秒) | | | | +| 第一次说出「哦,懂了」之类的话(秒) | | | | +| 说「现在呢?」「怎么又来了」的次数 | | | | +| 乱点连续超过 3 下的次数 | | | | +| 主动放弃的时间点,以及当时在做什么 | | | | +| 结束后一句话评价 | | | | + +### 6.4 合格指标 + +| 指标 | 目标 | +|---|---| +| 第一次操作 | 10 秒以内 | +| 第一次成功 | 20 秒以内 | +| 无声操作 | 0 次 | +| 被动原句重复 | 0 次 | +| 打断失败 | 0 次 | +| 过期提示 | 0 次 | +| 真人 5 分钟内主动放弃 | 5 个人里不超过 1 个 | +| 真人说「现在呢?」 | 平均每人不超过 2 次 | + +--- + +## 7. 一票否决的反模式 + +1. 玩家操作后没有任何声音。 +2. 玩家已经操作了,游戏还在说之前的话(`critical` 句除外)。 +3. 游戏主动把同一句话原样重复。 +4. 同一手势在不同时刻、不同游戏里意思不同。 +5. 连点、双击绑定了重播、重开、连跳多段。 +6. 开场先念规则,3 秒内玩家还不能动手。 +7. 一个机制要两句以上才讲得清。 +8. 等玩家的时候一片安静。 +9. 语音链句间掉回「轮到你」的听感。 +10. 状态提示音从来没教过。 +11. 归因和玩家的实际行为对不上。 +12. 失败后要从很前面重新开始。 +13. 重要反馈只靠震动。 +14. 纠错、提示句排在长链后面迟到播出。 + +## 8. 随手自检五问 + +任意时刻停下来问自己: + +1. 现在是游戏在说,还是轮到我?2 秒内能听出来吗?(原则 1、2 → §4.1) +2. 我刚才那一下,游戏收到了吗?(原则 4 → §4.2) +3. 如果我现在随便点一下,会发生什么?我能猜到吗?(原则 6 → §4.3、§4.7) +4. 我知道下一步该做的一个动作吗?(原则 3、11 → §4.4、§4.10) +5. 最近 30 秒里我有没有得到一次进展?(原则 14 → §4.10) + +--- + +## 附录 A:等待点配置表模板 + +每个需要玩家操作的时刻填一行。 + +| 等待点 id | 期待的手势 | 交接提示音 | 等待声 | 提示 `_full` | 提示 `_short` | 静默阈值 | 迷茫第 2 级只保留的手势 | 迷茫第 3 级演示内容 | 成功反馈 | 失败归因 | +|---|---|---|---|---|---|---|---|---|---|---| +| `find_cat_1` | 右滑 | `turn` | 默认 | 「猫在右边。向右滑。」 | 「向右滑。」 | 6 秒 | 右滑 | 游戏自己向右走一步 | 呼噜声 +「找到了!」 | 左滑:「在右边。」 | + +## 附录 B:状态 × 手势矩阵模板 + +| 状态 \ 手势 | 单击 | 左滑 | 右滑 | 上滑 | 下滑 | 长按 | 双击 | 连点 / 乱滑 | +|---|---|---|---|---|---|---|---|---| +| 状态 1 | 效果 / 回执 / 语音 | | | | | | | | +| 状态 2 | | | | | | | | | +| 状态 3 | | | | | | | | | + +## 附录 C:单款游戏设计单(开工前填完) + +1. 一句话核心动作:________(写不成一句,就先简化玩法) +2. 玩家能感知的状态(不超过 3 个)以及每个状态的声音身份:________ +3. 新手手势表,以及解锁阶梯:________ +4. 状态 × 手势矩阵(附录 B) +5. 所有等待点配置(附录 A) +6. 首 3 分钟逐秒脚本(§4.10) +7. 失败表(§4.9) +8. 台词表:每句标类别(critical / content / hint / feedback),提示类都要有 `_full` 和 `_short` +9. 断点复述句 +10. 测试记录:闭眼冷启动 + 乱点机器人 + 真人冷启动的结果 diff --git a/docs/design/README.md b/docs/design/README.md index 126207ad..b9780668 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -9,6 +9,7 @@ | 文档 | 内容 | 对应用户反馈 | |---|---|---| | [00-global-ux.md](00-global-ux.md) | 全局交互规范(终审修订版):设置系统、haptics 震动语言(10 震型)、旁白整句化、手势约定与模态菜单、用户系统 API | ⑥⑦ + 全局需求 | +| [26-audio-interaction-playbook.md](26-audio-interaction-playbook.md) | 纯音频交互第一性原理与落地手册:对话模型、16 原则、发言权管理器/输入回执/新手手势/三档提示/迷茫检测/语音优先级、三张设计单模板、P0-P3 改造顺序、乱点机器人与真人冷启动验证 | 真人试玩「搞不清状态、点两下就重复、不知道怎么玩」 | | [01-escape-redesign.md](01-escape-redesign.md) | 《暗室》重设计:引导式开场、三级卡关提示、线索音可重听 | ⑤ | | [02-simon-redesign.md](02-simon-redesign.md) | 《音序回响》重设计:去 BGM、旁白整句化、交互式教学、复活机制(含双击语义裁定) | ⑥⑦ | | [03-rhythm-rebuild.md](03-rhythm-rebuild.md) + [03-rhythm-voice-ids.md](03-rhythm-voice-ids.md) | 《节拍行者》重构施工稿:跟随领奏玩法、三首完整谱面、判定契约、31 句台词表 | ⑧ |