first commit
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
# 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 配置文件读取波形参数)
|
||||
Reference in New Issue
Block a user