Files
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

192 lines
7.5 KiB
Markdown
Raw Permalink 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.
# SoundGame 引擎 API 文档
面向游戏开发 agent:本文档说明如何在共享引擎骨架上开发一款纯音频游戏。
骨架已提供:手势识别(GestureEngine)、分层音频(AudioEngine)、状态机基类
(GameEngine)、全屏封面 Activity(MainActivity)。**你只需要实现 `Game`
接口并提供资产,不需要碰 Activity、Compose 或音频底层。**
## 构建与环境
```bash
source /home/ubuntu/SoundGame/env.d/android.sh # 必须先执行(JDK17 / SDK / ffmpeg)
cd /home/ubuntu/SoundGame
# 构建某一个 flavor(各自独立安装,applicationId 不同)
./gradlew assembleStorylineDebug # com.soundgame.storyline
./gradlew assembleReflexDebug # com.soundgame.reflex
./gradlew assembleEscapeDebug # com.soundgame.escape
# 一次构建全部
./gradlew assembleDebug
```
APK 输出在 `app/build/outputs/apk/<flavor>/debug/`。
首次构建会下载依赖,较慢属正常(daemon 有缓存,重跑会快很多)。
## 工程结构
```
app/
build.gradle.kts # flavor 定义、依赖
src/
main/ # 共享引擎,不要放游戏逻辑
java/com/soundgame/
MainActivity.kt # 全屏封面 + 手势路由(一般不用改)
engine/
Gesture.kt # Gesture 枚举
GestureEngine.kt # Modifier.gestureDetector
AudioEngine.kt # AudioEngine 接口 + DefaultAudioEngine
Game.kt # Game 接口 + GameEngine<S> 状态机基类
AndroidManifest.xml
storyline/ # 你的游戏放这里(以 storyline 为例)
java/com/soundgame/game/
GameFactory.kt # 注册点:创建你的 Game 实现
... # 你的其他游戏代码,随意组织
res/drawable/cover.png # 封面图(1080x1920,必须叫 cover)
assets/audio/ # 全部音频资产
bgm/ voice/ sfx/
```
## 快速上手:实现一款游戏
**第 1 步**:在 `app/src/<你的flavor>/java/com/soundgame/game/` 下写游戏类。
推荐继承 `GameEngine<S>`(状态机模式):
```kotlin
package com.soundgame.game
import com.soundgame.engine.AudioEngine
import com.soundgame.engine.GameEngine
import com.soundgame.engine.Gesture
sealed interface Scene {
data object Intro : Scene
data object Hallway : Scene
data object Ending : Scene
}
class MyGame(audio: AudioEngine) : GameEngine<Scene>(audio) {
override val initialState = Scene.Intro
// 状态转移:当前状态 + 手势 -> 新状态(返回原状态表示无转移)
override fun transition(state: Scene, gesture: Gesture): Scene = when (state) {
Scene.Intro -> when (gesture) {
Gesture.TAP -> Scene.Hallway
else -> state
}
Scene.Hallway -> when (gesture) {
Gesture.SWIPE_LEFT, Gesture.SWIPE_RIGHT -> Scene.Ending
else -> state
}
Scene.Ending -> state
}
// 进入新状态时播放音频(onStart 时也会以 previous=null 调一次 initialState)
override fun onEnterState(state: Scene, previous: Scene?) {
when (state) {
Scene.Intro -> {
audio.playBgm("bgm/main_theme")
audio.playVoice("voice/intro")
}
Scene.Hallway -> audio.playVoice("voice/hallway")
Scene.Ending -> {
audio.stopBgm()
audio.playVoice("voice/ending")
}
}
}
}
```
如果你的玩法不是状态机(如 reflex 的计时反应),直接实现 `Game` 接口:
```kotlin
class MyGame(private val audio: AudioEngine) : Game {
override fun onGesture(gesture: Gesture) { /* 任意逻辑 */ }
override fun onStart() { /* 开场 */ }
override fun onStop() { /* 收尾 */ }
}
```
**第 2 步**:在 `GameFactory.kt`(已在骨架中,包名必须是 `com.soundgame.game`)
里把 stub 换成你的实现:
```kotlin
object GameFactory {
fun create(audio: AudioEngine): Game = MyGame(audio)
}
```
MainActivity 只认 `com.soundgame.game.GameFactory.create(audio)`,不要改它的签名。
**第 3 步**:放资产、构建、验证。
## Gesture 枚举
`com.soundgame.engine.Gesture`,共 6 种:
| 值 | 含义 | 判定 |
|---|---|---|
| `SWIPE_UP` / `SWIPE_DOWN` / `SWIPE_LEFT` / `SWIPE_RIGHT` | 四向滑动 | 位移 ≥ 64dp 且平均速度 ≥ 400px/s,方向取位移较大的轴(防误触) |
| `TAP` | 单击 | 标准 tap |
| `DOUBLE_TAP` | 双击 | 标准 double tap |
注意:因为注册了双击,**单击回调会延迟约 300ms**(等双击判定窗口)才触发。
设计玩法时把关键即时反馈尽量放在滑动或双击上,或接受该延迟。
阈值可在 `GestureConfig` 调整(骨架用默认值,如需改动在 `MainActivity` 的
`gestureDetector(config = ...)` 处传参——这影响所有 flavor,改动前先沟通)。
## AudioEngine 接口
```kotlin
interface AudioEngine {
fun play(id: String) // 按 id 前缀分发
fun playBgm(id: String, loop: Boolean = true, fadeMs: Long = 800)
fun stopBgm(fadeOutMs: Long = 800)
fun playVoice(id: String, duckBgm: Boolean = true, onComplete: (() -> Unit)? = null)
fun stopVoice()
fun playSfx(id: String, volume: Float = 1f)
fun setBgmVolume(volume: Float)
fun release() // Activity 已调用,你不用管
}
```
- **三层分离**:BGM 用 ExoPlayer(切换时自动交叉淡入淡出);对白用另一个
ExoPlayer,播放期间自动把 BGM 压到 20%(ducking),播完恢复;短音效用
SoundPool,低延迟可叠加。
- **`id` 约定**:相对 `assets/audio/` 的路径,如 `"sfx/door_open"`。
前缀 `bgm/`、`voice/`、`sfx/` 决定走哪一层;`play(id)` 就靠前缀分发。
**扩展名可省略**,引擎依次尝试 `.ogg` → `.mp3` → `.wav`。
- `playVoice` 的 `onComplete` 在对白自然播完时回调(被 `stopVoice` 打断不回调),
适合做"台词说完再开选项"的剧情驱动。
- 找不到资产时引擎打 `Log.e("AudioEngine", ...)` 并静默跳过,不会崩溃——
但请用 logcat 检查拼写:`adb logcat -s AudioEngine`。
## 资产放哪
```
app/src/<flavor>/assets/audio/
bgm/ 背景音乐(循环长音频,建议 .ogg)
voice/ 对白/旁白(建议 .ogg 或 .mp3)
sfx/ 短音效(建议 .wav 或短 .ogg,SoundPool 低延迟)
```
封面图:`app/src/<flavor>/res/drawable/cover.png`(1080x1920 竖图,骨架已生成
占位渐变图,换成正式图时保持文件名 `cover.png` 不变,MainActivity 按名引用)。
生成/处理音频用 ffmpeg(已配好):如
`ffmpeg -i in.wav -c:a libvorbis -q:a 5 app/src/storyline/assets/audio/bgm/theme.ogg`。
## 规则与注意事项
- **不要修改** `app/src/main/` 下的引擎代码和 `MainActivity`,除非你 flavor 的
需求所有游戏都需要——那是共享代码。确需扩展引擎能力时,加新方法而不要改
已有签名。
- 每个 flavor 的包结构固定为 `com.soundgame.game.GameFactory` + 任意其他类,
三个 flavor 的同名类互不冲突(源码集隔离)。
- 全屏沉浸式、屏幕常亮、竖屏锁定已在 MainActivity/Manifest 处理,不用自己弄。
- 封面图已带 TalkBack 无障碍描述(contentDescription),换图不影响。
- 音频游戏注意节奏:手势回调在主线程,音频 API 均非阻塞,可以直接在
`onGesture` 里调用。