关于 audio-and-sound
面向 Phaser 游戏开发者的音频系统参考,说明如何加载和控制声音,并介绍 Web Audio 与 HTML5 Audio 后端及其能力差异。
主要功能
- 01Web Audio 与 HTML5 Audio 后端
- 02声音播放、循环与音量控制
- 03标记、音频精灵与定位音频
- 04暂停恢复及浏览器音频解锁
适用场景
- 01播放背景音乐和游戏音效
- 02将多段音效配置为音频精灵
- 03根据游戏对象位置播放定位声音
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ phaserjs/ phaser --skill audio-and-sound安装后在 Claude Code 中输入/audio-and-sound确认已加载。
详细介绍
音频与声音
Phaser 通过
this.sound(一个 SoundManager)提供统一的 Sound 系统,对 Web Audio API 和 HTML5 Audio 进行了抽象。它处理加载、播放、音量、声像、循环、标记、音频精灵、空间音频以及浏览器自动播放策略解锁。
关键源码路径: src/sound/BaseSoundManager.js、src/sound/BaseSound.js、src/sound/webaudio/、src/sound/html5/、src/sound/SoundManagerCreator.js、src/sound/events/、src/sound/typedefs/
相关技能: ../loading-assets/SKILL.md、../game-setup-and-config/SKILL.md
快速开始
class GameScene extends Phaser.Scene {
preload() {
this.load.audio('bgm', 'assets/music.mp3');
this.load.audio('coin', ['assets/coin.ogg', 'assets/coin.mp3']);
}
create() {
// Fire-and-forget (auto-destroys when complete)
this.sound.play('coin');
// Retained reference for ongoing control
this.music = this.sound.add('bgm', { loop: true, volume: 0.5 });
this.music.play();
}
}
在 preload() 中通过 this.load.audio() 加载的资源,在 create() 运行时已经就绪。提供一个 URL 数组以实现跨浏览器格式回退。
核心概念
WebAudio 与 HTML5 Audio
Phaser 通过 SoundManagerCreator.create() 自动选择最佳后端:
- 如果
config.audio.noAudio为 true,或者设备既不支持 Web Audio 也不支持 HTML5 Audio,则会创建一个 NoAudioSoundManager(所有调用均为空操作)。 - 如果设备支持 Web Audio 且
config.audio.disableWebAudio不为 true,则会创建一个 WebAudioSoundManager(首选)。 - 否则,会创建一个 HTML5AudioSoundManager 作为回退。
WebAudio 的优势:精确的时序、无缝循环、立体声声像(StereoPannerNode)、空间音频(PannerNode)、每个声音独立的增益节点、decodeAudio() 用于运行时解码。
HTML5 Audio 的限制:没有空间音频,没有真正的立体声声像(pan 会触发事件但没有可听见的效果),循环不够精确,同时播放需要在加载时指定 instances 数量。
通过游戏配置强制使用 HTML5 或禁用音频:audio: { disableWebAudio: true } 或 audio: { noAudio: true }。传入 audio: { context: existingAudioContext } 可在 SPA 中复用 WebAudio 上下文。
SoundManager(this.sound)
可在任何 Scene 中通过 this.sound 访问。它是整个游戏中单一共享的实例。主要职责:
- 添加、播放和移除声音实例
- 全局音量、静音、速率和失谐
- 当浏览器标签页失去/获得焦点时自动暂停/恢复(
pauseOnBlur,默认为true) - 针对移动浏览器的音频解锁处理
- 空间音频监听器位置(仅限 WebAudio)
声音实例
通过 this.sound.add(key, config) 创建。每个实例都有自己的播放状态、音量、速率、失谐、循环、声像和定位属性。声音必须存在于音频缓存中(通过 Loader 加载)才能被添加。
状态标志:isPlaying(布尔值)、isPaused(布尔值)。
const sfx = this.sound.add('explosion', { volume: 0.8 });
sfx.play(); // returns boolean
sfx.pause(); // only works if isPlaying
sfx.resume(); // only works if isPaused
sfx.stop(); // resets to stopped state
sfx.destroy(); // marks for removal from manager
常见模式
播放声音
即发即忘 —— this.sound.play(key, config?) 会添加并播放声音,并在完成后自动销毁:
this.sound.play('explosion');
this.sound.play('powerup', { volume: 0.5, rate: 1.2 });
保留引用 —— this.sound.add(key, config?),然后在实例上调用 play():
const laser = this.sound.add('laser');
laser.play();
// Later: laser.stop(), laser.volume = 0.3, etc.
音量、速率和失谐
每个属性都可以按声音设置,也可以在管理器上全局设置。全局值和按声音的值会组合(对于速率/失谐,它们通过 calculateRate() 相乘)。
// Per-sound
sound.volume = 0.5; // 0 to 1
sound.setVolume(0.5); // chainable alternative
sound.rate = 1.5; // 0.5 = half speed, 2.0 = double speed
sound.setRate(1.5);
sound.detune = 200; // cents, -1200 to 1200
sound.setDetune(200);
// Global (affects all sounds)
this.sound.volume = 0.8;
this.sound.setVolume(0.8);
this.sound.rate = 1.0;
this.sound.setRate(1.0);
this.sound.detune = 0;
this.sound.setDetune(0);
有效播放速率为:sound.rate * manager.rate * detuneRate,其中 detuneRate = Math.pow(1.0005777895065548, sound.detune + manager.detune)。
循环
// Via config at creation
const bgm = this.sound.add('music', { loop: true });
bgm.play();
// Toggle during playback
bgm.loop = false;
bgm.setLoop(false); // chainable
每次声音循环回到开头时都会触发 LOOPED 事件。当循环属性发生变化时,会触发 LOOP 事件。
定位
sound.seek = 5.0; // jump to 5 seconds in
sound.setSeek(5.0); // chainable
console.log(sound.seek); // current playback position in seconds
对已停止的声音设置定位不会产生任何效果。
立体声声像
sound.pan = -1; // full left
sound.pan = 0; // center
sound.pan = 1; // full right
sound.setPan(0.5); // chainable
在 WebAudio 上使用 StereoPannerNode(如果存在)。在 HTML5 Audio 上,pan 属性会触发事件,但没有可听见的效果。
音频精灵和标记
音频精灵通过一个 JSON 配置(由 audiosprite 工具生成)将多个声音合并到一个音频文件中。该 JSON 必须单独加载。
// In preload
this.load.audioSprite('sfx', 'assets/sfx.json', ['assets/sfx.ogg', 'assets/sfx.mp3']);
// In create
this.sound.playAudioSprite('sfx', 'explosion');
this.sound.playAudioSprite('sfx', 'coin', { volume: 0.5 });
// Or add for retained control
const sprite = this.sound.addAudioSprite('sfx');
sprite.play('explosion');
JSON 中的 spritemap 条目会自动转换为带有 name、start、duration 以及可选 loop 的标记。
手动标记——你也可以向任何声音添加标记:
const sound = this.sound.add('longtrack');
sound.addMarker({ name: 'intro', start: 0, duration: 5 });
sound.addMarker({ name: 'loop', start: 5, duration: 20, config: { loop: true } });
sound.addMarker({ name: 'outro', start: 25, duration: 3 });
sound.play('intro');
// Later
sound.play('loop');
BaseSound 上的标记 API:addMarker(marker)、updateMarker(marker)、removeMarker(markerName)。
背景音乐模式
this.bgm = this.sound.add('theme', { loop: true, volume: 0.4 });
this.bgm.play();
// Stop on scene shutdown: this.bgm.stop();
管理器的 pauseOnBlur(默认为 true)会在标签页失去焦点时自动暂停所有声音。
空间音频(仅限 WebAudio)
空间音频使用 Web Audio 的 PannerNode,相对于听者在 2D/3D 空间中定位声音。
// Set the listener position (typically your camera or player)
this.sound.setListenerPosition(400, 300);
// Or update directly: this.sound.listenerPosition.set(x, y);
// Create a spatialized sound with a source config
const enemy = this.sound.add('roar', {
source: {
x: 800,
y: 300,
refDistance: 50,
maxDistance: 2000,
rolloffFactor: 1,
distanceModel: 'inverse',
panningModel: 'equalpower',
follow: enemySprite // auto-track a Game Object's x/y
}
});
enemy.play();
你可以直接在 WebAudioSound 上设置 sound.x 和 sound.y,随时重新定位它。如果 follow 被设置为一个带有 x/y 属性的对象,空间位置会每帧自动更新。
如果在调用 setListenerPosition() 时不传任何参数,它默认将听者位置设为游戏画布的中心。
静音
// Per-sound
sound.mute = true;
sound.setMute(true);
// Global
this.sound.mute = true;
this.sound.setMute(true);
查询声音
this.sound.get('coin'); // first sound with key, or null
this.sound.getAll('coin'); // all sounds with key
this.sound.getAll(); // every sound in the manager
this.sound.getAllPlaying(); // all currently playing sounds
this.sound.isPlaying('coin'); // true if any 'coin' sound is playing
this.sound.isPlaying(); // true if any sound is playing
移除和停止
this.sound.stopAll(); // stop all sounds, fires STOP_ALL
this.sound.stopByKey('coin'); // stop all sounds with key, returns count
this.sound.pauseAll(); // pause all, fires PAUSE_ALL
this.sound.resumeAll(); // resume all, fires RESUME_ALL
this.sound.remove(soundInstance); // destroy + remove specific sound
this.sound.removeByKey('coin'); // destroy + remove all with key, returns count
this.sound.removeAll(); // destroy + remove everything
在运行时解码音频(仅限 WebAudio)
this.sound.decodeAudio('key', base64StringOrArrayBuffer);
// Or batch: this.sound.decodeAudio([{ key: 'sfx1', data: buf1 }, { key: 'sfx2', data: buf2 }]);
this.sound.on('decoded', (key) => { /* one done */ });
this.sound.on('decodedall', () => { /* all done */ });
配置参考
SoundConfig
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
mute | boolean | false | 声音是否静音 |
volume | number | 1 | 音量,0(静音)到 1(最大) |
rate | number | 1 | 播放速度(0.5 = 一半,2.0 = 两倍) |
detune | number | 0 | 以音分表示的失谐(-1200 到 1200) |
seek | number | 0 | 开始播放位置,以秒为单位 |
loop | boolean | false | 声音是否应循环 |
delay | number | 0 | 播放开始前的延迟,以秒为单位 |
pan | number | 0 | 立体声平移,-1(左)到 1(右) |
source | SpatialSoundConfig | null | 空间音频配置(仅限 WebAudio) |
SpatialSoundConfig
位置:x (0)、y (0)、z (0) —— 声源在世界空间中的位置。
方向:orientationX (0)、orientationY (0)、orientationZ (-1) —— 声源方向向量。
模型:panningModel('equalpower' 或 'HRTF')、distanceModel('linear'、'inverse'、'exponential')。
距离:refDistance (1)、maxDistance (10000)、rolloffFactor (1)。
锥形:coneInnerAngle (360)、coneOuterAngle (0)、coneOuterGain (0)。
跟踪:follow (null) —— 一个 Vector2Like 对象,其 x/y 每帧会被自动跟踪。
SoundMarker
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
name | string | (必填) | 标记的唯一标识符 |
start | number | 0 | 开始位置,以秒为单位 |
duration | number | (剩余) | 播放时长,以秒为单位 |
config | SoundConfig | {} | 此标记的默认设置 |
事件
声音实例事件(在 Sound 对象上触发)
| 事件常量 | 字符串值 | 回调参数 | 触发时机 |
|---|---|---|---|
Events.PLAY | 'play' | (sound) | 声音开始播放 |
Events.PAUSE | 'pause' | (sound) | 声音暂停 |
Events.RESUME | 'resume' | (sound) | 声音从暂停中恢复 |
Events.STOP | 'stop' | (sound) | 声音停止 |
Events.COMPLETE | 'complete' | (sound) | 声音播放完成(非循环) |
Events.LOOPED | 'looped' | (sound) | 声音循环回到开头 |
Events.LOOP | 'loop' | (sound, value) | 循环属性变化 |
Events.MUTE | 'mute' | (sound, value) | 静音状态变化 |
Events.VOLUME | 'volume' | (sound, value) | 音量变化 |
Events.RATE | 'rate' | (sound, value) | 速率变化 |
Events.DETUNE | 'detune' | (sound, value) | 音高偏移变化 |
Events.SEEK | 'seek' | (sound, value) | 跳转位置变化 |
Events.PAN | 'pan' | (sound, value) | 声像值变化 |
Events.DESTROY | 'destroy' | (sound) | 声音被销毁 |
SoundManager 事件(在 this.sound 上触发)
| 事件常量 | 字符串值 | 回调参数 | 触发时机 |
|---|---|---|---|
Events.PAUSE_ALL | 'pauseall' | (manager) | 调用 pauseAll() |
Events.RESUME_ALL | 'resumeall' | (manager) | 调用 resumeAll() |
Events.STOP_ALL | 'stopall' | (manager) | 调用 stopAll() |
Events.GLOBAL_MUTE | 'globalmute' | (manager, value) | 全局静音变化 |
Events.GLOBAL_VOLUME | 'globalvolume' | (manager, value) | 全局音量变化 |
Events.GLOBAL_RATE | 'globalrate' | (manager, value) | 全局速率变化 |
Events.GLOBAL_DETUNE | 'globaldetune' | (manager, value) | 全局音高偏移变化 |
Events.UNLOCKED | 'unlocked' | (manager) | 用户交互后音频系统解锁 |
Events.DECODED | 'decoded' | (key) | 单个音频键解码完成(WebAudio) |
Events.DECODED_ALL | 'decodedall' | () | 所有排队音频解码完成(WebAudio) |
// Example: listen for completion
const sfx = this.sound.add('bang');
sfx.on('complete', (sound) => {
console.log(sound.key, 'finished');
});
sfx.play();
API 快速参考
BaseSoundManager(this.sound)
方法:add(key, config?)、addAudioSprite(key, config?)、play(key, extra?)、playAudioSprite(key, spriteName, config?)、get(key)、getAll(key?)、getAllPlaying()、isPlaying(key?)、remove(sound)、removeByKey(key)、removeAll()、stopAll()、stopByKey(key)、pauseAll()、resumeAll()、setListenerPosition(x?, y?)、setMute(value)、setVolume(value)、setRate(value)、setDetune(value)。
属性:volume(0-1)、mute(boolean)、rate(number)、detune(-1200 到 1200)、pauseOnBlur(boolean,默认 true)、locked(boolean,只读)、listenerPosition(Vector2)、sounds(数组,私有)。
BaseSound(声音实例)
方法:play(markerName?, config?)、pause()、resume()、stop()、destroy()、addMarker(marker)、updateMarker(marker)、removeMarker(name)、setMute(value)、setVolume(value)、setRate(value)、setDetune(value)、setSeek(value)、setLoop(value)、setPan(value)。
属性:volume(0-1)、mute(boolean)、rate(number)、detune(number)、seek(秒)、loop(boolean)、pan(-1 到 1)、isPlaying(只读)、isPaused(只读)、duration(秒)、totalDuration(秒)、key(string)、x / y(空间位置,仅 WebAudio)。
所有 set* 方法均返回 this 以支持链式调用。
注意事项
浏览器自动播放策略
浏览器会阻止音频,直到用户进行交互。Phaser 会自动处理这一点:
- WebAudio:
AudioContext启动时处于挂起状态。Phaser 会监听document.body上的 touchstart/touchend/mousedown/mouseup/keydown,以调用context.resume()。在解锁之前,locked属性为 true;解决后会触发UNLOCKED事件。 - HTML5 Audio:被锁定的音频标签会将所有操作排队,直到第一次触摸时重新执行它们。
你不需要手动处理解锁。要了解何时准备就绪,请监听 UNLOCKED:
if (this.sound.locked) {
this.sound.once('unlocked', () => {
this.sound.play('bgm');
});
} else {
this.sound.play('bgm');
}
音频格式支持
没有一种格式能在所有地方都正常工作。请提供多种格式:this.load.audio('bgm', ['assets/bgm.ogg', 'assets/bgm.mp3'])。MP3 的支持最广泛。OGG Vorbis 不支持 Safari。AAC/M4A 在 Safari/iOS 上工作良好。WebM/Opus 音质出色,但对较旧浏览器的支持有限。
HTML5 Audio 同时播放
HTML5 Audio 使用一个 <audio> 标签池。加载时指定 instances 以实现同时播放:this.load.audio('shot', 'assets/shot.mp3', { instances: 4 })。默认值为 1。如果所有标签都在使用中,且 manager.override 为 true(默认值),则进度最多的声音会被抢占。WebAudio 没有这种限制。
iOS/Safari 特定事项
- iOS/Safari 不支持
StereoPannerNode,因此pan没有可听效果(事件仍会触发)。 - iOS 17/18+ 可以在后台中断音频。Phaser 通过在
VISIBLE游戏事件上调用context.suspend()/context.resume()来处理这一点。 setListenerPosition()和空间音频仅适用于 WebAudio。
WebAudio 上下文复用
对于无需整页刷新即可重新创建游戏的 SPA,请在游戏配置中传入 audio: { context: existingAudioContext }。你还可以在运行时通过 this.sound.setAudioContext(newContext) 交换上下文(仅限 WebAudio)。
Sound Manager 是共享的(全局)
每个游戏只有一个 SoundManager,而不是每个场景一个。每个场景中的 this.sound 都引用同一个管理器。场景关闭时不会自动清理声音——如有需要,你必须自行停止/移除它们。除非显式停止,否则循环声音会在场景切换期间继续播放。
即发即弃与持久声音
this.sound.play(key) 会创建一个在完成后自动销毁的声音——调用 play 后你无法再控制它。当你需要持久引用来暂停、停止、调整音量或监听事件时,请使用 this.sound.add(key)。
空间音频仅适用于 WebAudio
使用 HTML5 Audio 时,空间音频的 source 配置会被静默忽略。如果你的游戏必须支持 HTML5 Audio 回退,请不要依赖空间定位来实现对游戏玩法至关重要的音频提示。
Web Audio Analyser(仅限 WebAudio)
访问底层的 AudioContext 以创建用于频率/波形可视化的 AnalyserNode:
const analyser = this.sound.context.createAnalyser();
analyser.fftSize = 256;
this.sound.masterVolumeNode.connect(analyser);
analyser.connect(this.sound.context.destination);
const dataArray = new Uint8Array(analyser.frequencyBinCount);
// In update loop
analyser.getByteFrequencyData(dataArray);
// Use dataArray values to drive visual effects
这仅适用于 WebAudioSoundManager。使用前请检查 this.sound.context 是否存在。
源文件映射
| 文件 | 用途 |
|---|---|
src/sound/SoundManagerCreator.js | 工厂:选择 WebAudio、HTML5 或 NoAudio 管理器 |
src/sound/BaseSoundManager.js | 基础管理器:添加、播放、获取、全部停止/暂停/恢复、全局音量/速率/失谐 |
src/sound/BaseSound.js | 基础声音:播放/暂停/恢复/停止、标记、配置、calculateRate |
src/sound/webaudio/WebAudioSoundManager.js | WebAudio 管理器:AudioContext、增益节点、解锁、空间监听器、decodeAudio |
src/sound/webaudio/WebAudioSound.js | WebAudio 声音:缓冲区源、空间/声像节点、跳转、全部属性 |
src/sound/html5/HTML5AudioSoundManager.js | HTML5 管理器:audio 标签池化、锁定队列、覆盖 |
src/sound/html5/HTML5AudioSound.js | HTML5 声音:基于标签的播放、有限的功能集 |
src/sound/noaudio/NoAudioSoundManager.js | 适用于无音频环境的空操作管理器 |
src/sound/events/ | 所有声音事件常量(PLAY、STOP、COMPLETE 等) |
src/sound/typedefs/SoundConfig.js | SoundConfig 类型定义 |
src/sound/typedefs/SoundMarker.js | SoundMarker 类型定义 |
src/sound/typedefs/SpatialSoundConfig.js | SpatialSoundConfig 类型定义 |
src/sound/typedefs/AudioSpriteSound.js | AudioSpriteSound 类型定义 |