Files
tankwar_proj/docs/AudioManager_说明文档.md
2026-04-10 22:59:39 +08:00

302 lines
8.9 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.
# AudioManager 音效系统说明文档
> **文件路径**: `js/managers/AudioManager.js`
> **运行环境**: 微信小游戏 (WeChat Mini Game)
> **依赖 API**: `wx.createWebAudioContext()` (Web Audio API)
> **外部资源**: 无 — 所有音效通过 PCM 程序化合成生成
---
## 1. 架构概述
`AudioManager` 是坦克大战微信小游戏的音效管理模块,采用 **程序化音频合成** 方案,通过 Web Audio API 在运行时生成所有游戏音效的 PCM 缓冲区,无需加载任何外部音频文件。
### 设计决策
| 方案 | 优点 | 缺点 |
|------|------|------|
| ~~外部音频文件~~ | 音质高、可定制 | 需要额外资源文件,增加包体积 |
| **程序化合成 ✅** | 零资源依赖、包体极小、即时可用 | 音效较简单,适合复古风格游戏 |
### 模块关系
```
game.js (初始化)
└── AudioManager.init() ← 创建 WebAudioContext + 预生成所有音效缓冲区
├── GameScene.js (游戏场景)
│ ├── _playerFire() → playSFX('shoot')
│ ├── _enemyFire() → playSFX('shoot')
│ ├── _spawnExplosion() → playSFX('explosion_big' | 'explosion_small')
│ ├── _checkPowerUpPickup() → playSFX('powerup')
│ ├── _handlePlayerDestroyed() → playSFX('gameover')
│ ├── _handleBaseDestroyed() → playSFX('gameover')
│ └── _checkVictory() → playSFX('victory')
└── CollisionManager.js (碰撞管理)
└── 子弹击中装甲坦克(未摧毁) → playSFX('hit')
```
---
## 2. 生命周期
```mermaid
sequenceDiagram
participant G as game.js
participant AM as AudioManager
participant GS as GameScene
G->>AM: new AudioManager()
G->>G: GameGlobal.audioManager = audioManager
G->>AM: audioManager.init()
AM->>AM: wx.createWebAudioContext()
AM->>AM: _generateSounds() 预生成9种音效
Note over AM: 初始化完成,_initialized = true
GS->>AM: playSFX('shoot')
AM->>AM: createBufferSource() → connect → start
Note over AM: 播放音效
G->>AM: pauseAll() / resumeAll()
Note over AM: 前后台切换时暂停/恢复
G->>AM: destroy()
AM->>AM: audioCtx.close() + buffers.clear()
```
---
## 3. 音效目录
### 3.1 完整音效列表
| 音效名 | 用途 | 时长 | 波形特征 | 触发位置 |
|--------|------|------|----------|----------|
| `shoot` | 坦克射击 | 80ms | 800→400Hz 下降正弦波 + 线性衰减 | `GameScene._playerFire()` / `_enemyFire()` |
| `explosion_small` | 小爆炸(子弹击中地形) | 200ms | 白噪声 + 120Hz 正弦波,二次衰减 | `GameScene._spawnExplosion(x, y, false)` |
| `explosion_big` | 大爆炸(坦克被摧毁) | 400ms | 白噪声 + 60Hz/90Hz 双正弦波,二次衰减 | `GameScene._spawnExplosion(x, y, true)` |
| `hit` | 子弹击中装甲(未摧毁) | 100ms | 1200Hz + 2400Hz 双正弦波,金属质感 | `CollisionManager` 子弹命中装甲坦克 |
| `hit_wall` | 子弹击中墙壁 | 60ms | 噪声 + 300Hz 正弦波混合 | 预留(当前未调用) |
| `powerup` | 拾取道具 | 250ms | 400→1200Hz 上升正弦波 + 泛音 | `GameScene._checkPowerUpPickup()` |
| `gameover` | 游戏结束 | 600ms | 400→150Hz 下降正弦波 | `GameScene._handlePlayerDestroyed()` / `_handleBaseDestroyed()` |
| `victory` | 通关胜利 | 500ms | C5→E5→G5 三音阶上升和弦 | `GameScene._checkVictory()` |
| `move` | 坦克移动 | 50ms | 80Hz 低频正弦波 | 预留(当前未调用) |
### 3.2 音效波形参数详解
#### shoot(射击)
```
时长: 0.08s
频率: 800Hz → 400Hz (线性下降)
包络: 线性衰减 (1 → 0)
振幅: 0.3
```
#### explosion_big(大爆炸)
```
时长: 0.4s
成分: 白噪声(50%) + 60Hz正弦(30%) + 90Hz正弦(20%)
包络: 二次衰减 (1-t)²
振幅: 0.4
```
#### victory(胜利)
```
时长: 0.5s
音符: C5(523Hz) → E5(659Hz) → G5(784Hz)
每段: 0.167s, 含 attack(10%) + decay(90%)
泛音: 基频 × 2, 振幅 0.15
```
---
## 4. API 参考
### 构造函数
```javascript
const audioManager = new AudioManager();
```
创建实例,默认 `soundEnabled = true`, `musicEnabled = true`
自动监听 `GameGlobal.eventBus``settings:changed` 事件。
### init()
```javascript
audioManager.init();
```
初始化 WebAudio 上下文并预生成所有音效缓冲区。
- 幂等调用:多次调用只执行一次
- 如果 `wx.createWebAudioContext` 不可用,静默降级(无音效)
### playSFX(name)
```javascript
GameGlobal.audioManager.playSFX('shoot');
```
播放指定名称的音效。
- **参数**: `name` — 音效名称,见上方音效目录
- 如果音效未找到或音效已禁用,静默忽略
- 每次调用创建新的 `BufferSource`,支持同一音效并发播放
### register(name, path)
```javascript
audioManager.register('custom', 'path/to/file.mp3');
```
向后兼容接口,当前为空操作(No-op)。
### playBGM(path) / stopBGM()
背景音乐接口,当前未实现(需要外部音频文件)。
### pauseAll() / resumeAll()
暂停/恢复所有音频,用于应用前后台切换。
### destroy()
销毁音频上下文,释放所有缓冲区资源。
### 属性
| 属性 | 类型 | 说明 |
|------|------|------|
| `soundEnabled` | `boolean` | 音效开关(getter/setter |
| `musicEnabled` | `boolean` | 音乐开关(getter/setter |
---
## 5. 集成指南
### 5.1 初始化(game.js
```javascript
// game.js 第39行
const audioManager = new AudioManager();
// 第48行 - 挂载到全局
GameGlobal.audioManager = audioManager;
// 第131行 - LoadingScene._startLoading() 中初始化
audioManager.init();
```
### 5.2 在游戏逻辑中播放音效
```javascript
// 射击时
GameGlobal.audioManager.playSFX('shoot');
// 爆炸时(根据大小选择音效)
GameGlobal.audioManager.playSFX(isBig ? 'explosion_big' : 'explosion_small');
// 拾取道具
GameGlobal.audioManager.playSFX('powerup');
// 游戏结束
GameGlobal.audioManager.playSFX('gameover');
// 胜利
GameGlobal.audioManager.playSFX('victory');
```
### 5.3 添加新音效
`_generateSounds()` 方法中添加新的缓冲区生成:
```javascript
// 示例:添加一个"警报"音效
this._buffers.set('alarm', this._generateBuffer(sampleRate, 0.3, (i, len) => {
const t = i / len;
const freq = 600 + Math.sin(t * 20) * 200; // 颤音效果
const envelope = 1 - t;
return Math.sin(2 * Math.PI * freq * i / sampleRate) * envelope * 0.3;
}));
```
然后在需要的地方调用:
```javascript
GameGlobal.audioManager.playSFX('alarm');
```
### 5.4 前后台切换处理
```javascript
// game.js 中已配置
wx.onHide(() => { audioManager.pauseAll(); });
wx.onShow(() => { audioManager.resumeAll(); });
```
---
## 6. 技术细节
### 6.1 PCM 缓冲区生成原理
每个音效通过 `_generateBuffer()` 方法生成:
1. 根据 `sampleRate`(通常 44100Hz)和 `duration` 计算总采样数
2. 创建单声道 `AudioBuffer`
3. 逐采样调用 `generator(sampleIndex, totalSamples)` 函数
4. 每个采样值范围 `[-1.0, 1.0]`
```
采样数 = sampleRate × duration
例: 44100 × 0.08 = 3528 个采样点 (shoot 音效)
```
### 6.2 播放机制
```javascript
playSFX(name) createBufferSource() connect(destination) start(0)
```
- 每次播放创建新的 `BufferSource` 节点(Web Audio API 要求,BufferSource 是一次性的)
- 支持同一音效的多实例并发播放(如连续射击)
- 播放完成后 `BufferSource` 自动被垃圾回收
### 6.3 性能考量
| 指标 | 数值 |
|------|------|
| 预生成缓冲区数量 | 9 个 |
| 最大单个缓冲区大小 | ~26KB (explosion_big, 0.4s × 44100 × 4bytes) |
| 总内存占用 | ~80KB |
| 初始化耗时 | < 10ms |
| 播放延迟 | < 1ms (预生成缓冲区,无需解码) |
### 6.4 降级策略
```
wx.createWebAudioContext 可用?
├── 是 → 正常初始化,生成所有音效
└── 否 → _initialized = false, 所有 playSFX() 静默返回
```
---
## 7. 已知限制与后续规划
### 当前限制
1. **无背景音乐**`playBGM()` 为空实现,需要外部音频文件支持
2. **音效较简单** — 程序化合成适合复古风格,无法达到高保真音质
3. **`hit_wall``move` 音效已生成但未集成** — 预留接口,可在后续版本中启用
4. **`pauseAll()` / `resumeAll()` 为空实现** — WebAudio 的 suspend/resume 可在后续补充
### 后续可优化方向
- [ ] 实现 `pauseAll()` / `resumeAll()` 使用 `audioCtx.suspend()` / `audioCtx.resume()`
- [ ] 集成 `hit_wall` 音效到 `CollisionManager` 的墙壁碰撞逻辑
- [ ] 集成 `move` 音效到坦克移动逻辑(需注意节流,避免频繁触发)
- [ ] 添加音量控制(通过 `GainNode`
- [ ] 支持外部音频文件加载,用于背景音乐
- [ ] 音效参数可配置化(从 JSON 配置文件读取波形参数)