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

7.5 KiB
Raw Permalink Blame History

SoundGame 引擎 API 文档

面向游戏开发 agent:本文档说明如何在共享引擎骨架上开发一款纯音频游戏。 骨架已提供:手势识别(GestureEngine)、分层音频(AudioEngine)、状态机基类 (GameEngine)、全屏封面 Activity(MainActivity)。你只需要实现 Game 接口并提供资产,不需要碰 Activity、Compose 或音频底层。

构建与环境

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>(状态机模式):

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 接口:

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 换成你的实现:

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 接口

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 里调用。