# 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//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 状态机基类 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`(状态机模式): ```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(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//assets/audio/ bgm/ 背景音乐(循环长音频,建议 .ogg) voice/ 对白/旁白(建议 .ogg 或 .mp3) sfx/ 短音效(建议 .wav 或短 .ogg,SoundPool 低延迟) ``` 封面图:`app/src//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` 里调用。