# 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 配置文件读取波形参数）
