Files
SoundGame/docs/games/reflex.md
T
SoundGame Dev f0f0a9b50e 初始提交:3 款纯音频游戏 + 共享引擎 + 完整文档与资产管线
- 共享引擎(app/src/main):GestureEngine 6 手势识别、AudioEngine 三层音频
  (ExoPlayer BGM 交叉淡入 / 对白 ducking / SoundPool 低延迟)、Game/GameEngine
  状态机接口、MainActivity 全屏封面 + TalkBack
- 《声命线》storyline:末世互动广播剧,3 幕 8 抉择点 4 结局
- 《声东击西》reflex:听声辨位反应,12 波战斗 + BGM 三档 BPM 变速
- 《暗室》escape:3x3 网格密室逃脱,钥匙→铁柜→保险丝→电箱→大门谜题链
- 文档:setup(从零搭建)/ architecture(架构)/ engine-api(引擎 API)/
  asset-pipeline(资产流水线)/ games/*(三款游戏设计文档)
- 资产管线:Qwen3-TTS 对白 + 程序合成音效/BGM + Mureka BGM 的 gen 脚本与台账,
  全部 ogg 资产入库可复现
- env.d 环境脚本:android/tts 入库;sfx/mureka 含密钥不入库,仅提交 .example.sh 模板
2026-08-29 13:00:34 +08:00

110 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 《声东击西》(reflex) — 听声辨位反应 设计文档
- 包名:`com.soundgame.reflex`
- 代码:`app/src/reflex/java/com/soundgame/game/GameFactory.kt`
(`ReflexGame` 与 `GameFactory` 同文件,与其他两款游戏的拆文件风格不同)
- 生成脚本:`app/src/reflex/gen/`(`gen_voice.py` / `gen_sfx_bgm.py` /
`convert_audio.py` / `fix_num6.py`)
敌人从四个方向来袭,玩家纯靠听声音方位做出对应手势。教学演练 → 12 波战斗
(节奏递增、BGM 三档变速)→ 结算播报得分。共 5 点血,血尽或通关进结算。
## 方位声音设计(gen_sfx_bgm.py)
| 方位 | 提示音 | 声学设计 | 应对手势 |
|---|---|---|---|
| 左 | cue_left | 0.45s 噪声扫频 whoosh,声像硬打左声道 | 左滑 |
| 右 | cue_right | 同上,硬打右声道 | 右滑 |
| 上 | cue_up | 高音上行琶音 E6-G6-B6(1319/1568/1976 Hz) | 上滑 |
| 下 | cue_down | 低音下行轰击 110→55Hz + 闷响 | 下滑 |
| 正面 | warn_front | 急促三响警报(880/660 交替,约 1.0s) | 单击格挡 |
正面警报特意做到 ~1s 且反应窗口强制 ≥ 1100ms(`FRONT_MIN_WINDOW_MS`),
用来覆盖单击因双击判定窗口产生的 ~300ms 回调延迟(见 docs/architecture.md)。
## 手势映射
| 手势 | INTRO | PRACTICE | BATTLE | ENDED |
|---|---|---|---|---|
| 四向滑动 | 忽略 | 对应方向则过,错则重播提示 | 击杀对应方向敌人;划错断连击不扣血 | 忽略 |
| 单击 | 忽略 | 正面演练格挡 | 格挡正面攻击;无正面攻击不惩罚 | 重开(跳过教学) |
| 双击 | 忽略 | 忽略 | 音爆冲击清屏(12s 冷却) | 忽略 |
教学旁白(INTRO)期间完全不响应手势,防连点。
## 波次表(waveCfg,共 12 波)
| 波次 w | 出怪数 4+w | 出怪间隔 ms | 反应窗口 ms | 正面概率 | BGM 档 |
|---|---|---|---|---|---|
| 1 | 5 | 2000 | 1700 | 7.3% | calm (100 BPM) |
| 2 | 6 | 1873 | 1614 | 8.6% | calm |
| 3 | 7 | 1746 | 1528 | 9.9% | calm |
| 4 | 8 | 1619 | 1442 | 11.2% | **tense (128 BPM)** + 语音播报 |
| 5 | 9 | 1492 | 1356 | 12.5% | tense |
| 6 | 10 | 1365 | 1270 | 13.8% | tense |
| 7 | 11 | 1238 | 1184 | 15.1% | tense + 语音播报 |
| 8 | 12 | 1111 | 1098 | 16.4% | tense |
| 9 | 13 | 984 | 1012 | 17.7% | **frantic (152 BPM)** |
| 10 | 14 | 857 | 926 | 19.0% | frantic + 语音播报 |
| 11 | 15 | 730 | 840 | 20.3% | frantic |
| 12 | 16 | 600(触底) | 750(触底) | 21.6% | frantic + 总攻语音 |
- 出怪间隔公式:`(2000 - (w-1)*127).coerceAtLeast(600)`
- 反应窗口公式:`(1700 - (w-1)*86).coerceAtLeast(750)`;正面攻击取
`max(窗口, 1100)`
- 正面概率公式:`0.06 + w*0.013`
- 每波首怪在波开始后 1400ms 出现;波间间隔 1800ms
## 计分与状态
- 击杀:`100 + 10 × 当前连击`;技能击杀固定 50/个;连击 5/10/15(每5) 有音效,
连击 10 追加战友语音夸奖。
- 漏怪:扣 1 血、断连击;HP=2 / HP=1 时各播报一次警告语音。
- 双击技能:清空全部在场敌人,冷却 12s,冷却完成有语音提醒,冷却中双击
播 `skill_denied`。
- 结算:逐位播报得分(num_0..num_9 数字语音拼接),破纪录播 `new_record`,
否则播报历史最高。**最高分为进程内变量,杀进程即丢。**
- 时间调度全部走主线程 `Handler`(手势回调也在主线程),无需加锁;
`onStop` 清空全部回调。
## 角色音色分配(gen_voice.py)
| 角色 | speaker | instruct | 负责内容 |
|---|---|---|---|
| 教官/旁白 | Uncle_Fu(低沉醇厚中年男声) | 战场教官语气(教学一字一句、战前激昂、重伤焦急等逐条指定) | 教学 tut_01-07、战报 wave_04/07/10/12、血量警告、胜负播报、报分、数字 0-9 |
| 战友 | Vivian(明亮略带锋芒年轻女声) | 兴奋赞叹/轻快提醒/惊喜欢呼 | combo_praise、skill_ready、new_record、restart_hint、replay |
## 资产清单
| 层 | 数量 | 内容 |
|---|---|---|
| voice | 33 | 教学 7 + 战报 4 + 血量 2 + 激励 3 + 胜负/报分 6 + 重开 1 + 数字 10 |
| sfx | 18 | 方位 cue 5(cue_left/right/up/down + warn_front)+ 反馈 5(hit/block/hurt/skill/skill_denied)+ 连击 3 + wave_horn + ui_start + warmup + victory_fanfare + gameover_sting |
| bgm | 3 | bgm_calm / bgm_tense / bgm_frantic(程序合成脉冲节奏,100/128/152 BPM 各 8 小节,无缝循环) |
BGM 保留程序合成是有意为之:玩法要求三档 BPM 随波次精确切换,AI 生成曲目
BPM 不精确会破坏节奏机制(见 README 待办与 mureka_log.md)。
## 已知缺陷 / 遗留线索
- **`fix_num6.py`**:数字"六"的 TTS 生成不稳(单次生成时长经常超界),
单独写了个重试脚本(最多 4 次,取 0.3s~1.5s 的一条)。重新批量生成
`gen_voice.py` 时 num_6 可能再次翻车,需重跑 fix 脚本。
- **`warmup.wav` 是假资产**:0.1s 近静音文件(+1e-4 绕过峰值校验),
只为 SoundPool 预热存在。
- **最高分不持久化**:`highScore` 是 companion object 的 var,进程内有效。
- 划错方向只断连击不扣血、无正面攻击时单击不惩罚——为掩盖单击延迟的
有意宽容设计,严格模式下可视为难度漏洞。
- 结算逐位报分在长分数时较拖沓(每位一条独立语音文件)。
## 调参入口
| 位置 | 参数 | 控制什么 |
|---|---|---|
| `ReflexGame` companion | `MAX_HP=5` / `TOTAL_WAVES=12` / `SKILL_COOLDOWN_MS=12000` / `FRONT_MIN_WINDOW_MS=1100` | 血量/波数/技能冷却/正面窗口下限 |
| `waveCfg()` | attacks/interval/reaction/frontProb 四个公式 | 难度曲线核心 |
| `startWave()` | BGM 切换阈值(w≥4 / w≥9)、语音播报波次 | 节奏与叙事节拍 |
| `kill()` | 100/50 基础分、连击加成 10×combo | 计分 |
| `gen_sfx_bgm.py` | cue 音频率/声像、`make_pulse_bgm(bpm, bars)` | 方位可辨度、BGM 速度与密度 |
| `convert_audio.py` | voice 峰值归一 0.6、`-q:a 4/4/3` | 响度与体积 |