phaser-audio
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePhaser 4 Audio
Phaser 4 Audio
Phaser 4 wraps the Web Audio API for all audio playback. Know when each audio backend applies and follow the loading/playback patterns below to avoid the most common audio bugs.
Phaser 4 基于 Web Audio API 实现所有音频播放功能。请了解各音频后端的适用场景,并遵循以下加载/播放模式,以避免最常见的音频问题。
Web Audio vs HTML5 Audio
Web Audio vs HTML5 Audio
Phaser uses Web Audio by default on every browser that supports it (all modern browsers). Web Audio runs audio processing on a dedicated thread, supports spatial audio, effects chains, precise scheduling, and is never subject to the single-track limit that plagues HTML5 Audio.
HTML5 Audio is the automatic fallback when is unavailable — typically older Android WebViews and some edge-case browser configurations. You rarely target it intentionally. If you must force it:
AudioContexttypescript
const config: Phaser.Types.Core.GameConfig = {
audio: {
disableWebAudio: true, // force HTML5 Audio fallback
},
};Never force HTML5 Audio unless you have a specific compatibility requirement. Its limitations — single concurrent track, no detune/rate, no spatial positioning — will constrain your design.
Phaser 默认在所有支持 Web Audio 的浏览器(所有现代浏览器)中使用 Web Audio。Web Audio 在专用线程上运行音频处理,支持空间音频、效果链、精确调度,并且不受 HTML5 Audio 存在的单轨限制影响。
当 不可用时,会自动回退到 HTML5 Audio —— 通常出现在旧版 Android WebView 和一些特殊浏览器配置中。除非有特殊需求,否则无需刻意使用它。如果必须强制使用:
AudioContexttypescript
const config: Phaser.Types.Core.GameConfig = {
audio: {
disableWebAudio: true, // 强制回退到 HTML5 Audio
},
};除非有特定兼容性需求,否则不要强制使用 HTML5 Audio。 它的局限性——单并发轨道、无音调调整/速率控制、无空间定位——会限制你的设计。
Loading Audio: Always Provide mp3 AND ogg
加载音频:务必同时提供 mp3 和 ogg 格式
Browsers do not agree on a single audio codec. Safari and iOS require mp3. Firefox prefers ogg/vorbis. Chrome accepts both. Provide both formats and let Phaser pick the one the browser supports:
typescript
// preload()
preload(): void {
// ALWAYS provide both formats as an array — mp3 first, ogg second
this.load.audio('music-main', ['assets/audio/main.mp3', 'assets/audio/main.ogg']);
this.load.audio('sfx-jump', ['assets/audio/jump.mp3', 'assets/audio/jump.ogg']);
this.load.audio('sfx-coin', ['assets/audio/coin.mp3', 'assets/audio/coin.ogg']);
this.load.audio('sfx-hurt', ['assets/audio/hurt.mp3', 'assets/audio/hurt.ogg']);
}Never load a single format. A game that works perfectly on your Chrome dev machine will be completely silent on Safari/iOS if you only ship .
.ogg浏览器对音频编码格式没有统一标准。Safari 和 iOS 需要 mp3 格式,Firefox 偏好 ogg/vorbis 格式,Chrome 两者都支持。请同时提供两种格式,让 Phaser 选择浏览器支持的格式:
typescript
// preload()
preload(): void {
// 务必以数组形式提供两种格式 —— 先 mp3,后 ogg
this.load.audio('music-main', ['assets/audio/main.mp3', 'assets/audio/main.ogg']);
this.load.audio('sfx-jump', ['assets/audio/jump.mp3', 'assets/audio/jump.ogg']);
this.load.audio('sfx-coin', ['assets/audio/coin.mp3', 'assets/audio/coin.ogg']);
this.load.audio('sfx-hurt', ['assets/audio/hurt.mp3', 'assets/audio/hurt.ogg']);
}绝不要只加载单一格式。如果你只提供 格式,在你的 Chrome 开发环境中运行完美的游戏,在 Safari/iOS 上会完全没有声音。
.oggAdding and Playing Sounds
添加与播放声音
this.sound.add()
— Full Control
this.sound.add()this.sound.add()
—— 完全控制
this.sound.add()Use when you need a reference to control the sound (pause, resume, seek, adjust volume, listen for events):
add()typescript
create(): void {
const music = this.sound.add('music-main', {
loop: true,
volume: 0.6,
});
music.play();
this.music = music;
}当你需要引用声音以进行控制(暂停、恢复、跳转、调整音量、监听事件)时,使用 :
add()typescript
create(): void {
const music = this.sound.add('music-main', {
loop: true,
volume: 0.6,
});
music.play();
this.music = music;
}this.sound.play()
— One-Shot Fire-and-Forget
this.sound.play()this.sound.play()
—— 一次性触发即遗忘
this.sound.play()Use the manager's for sounds you fire once and never need to reference again. Phaser manages the lifetime automatically:
play()typescript
// In update() or an event handler:
this.sound.play('sfx-coin', { volume: 0.9 });
this.sound.play('sfx-jump', { volume: 1.0, rate: 1.1 });Rule of thumb: Background music → . One-shot SFX → .
add()sound.play()对于只需播放一次且无需后续引用的声音,使用管理器的 方法。Phaser 会自动管理其生命周期:
play()typescript
// 在 update() 或事件处理器中:
this.sound.play('sfx-coin', { volume: 0.9 });
this.sound.play('sfx-jump', { volume: 1.0, rate: 1.1 });经验法则: 背景音乐 → 使用 ;一次性音效 → 使用 。
add()sound.play()SoundConfig Options
SoundConfig 选项
Pass a object as the second argument to or as the second argument to :
SoundConfigadd()play()typescript
const config: Phaser.Types.Sound.SoundConfig = {
loop: false, // boolean — loop the sound (default: false)
volume: 1, // number 0–1 — per-sound volume (default: 1)
rate: 1, // number — playback speed multiplier (default: 1; 2 = double speed)
detune: 0, // number — cents offset from base pitch (default: 0; 100 = 1 semitone)
seek: 0, // number — start position in seconds (default: 0)
delay: 0, // number — seconds to wait before playing (default: 0)
};将 对象作为第二个参数传递给 或 :
SoundConfigadd()play()typescript
const config: Phaser.Types.Sound.SoundConfig = {
loop: false, // 布尔值 —— 是否循环播放(默认:false)
volume: 1, // 数字 0–1 —— 单声音量(默认:1)
rate: 1, // 数字 —— 播放速度倍数(默认:1;2 = 两倍速度)
detune: 0, // 数字 —— 与基准音高的音分偏移量(默认:0;100 = 1 个半音)
seek: 0, // 数字 —— 起始位置(秒,默认:0)
delay: 0, // 数字 —— 播放前等待时间(秒,默认:0)
};Background Music
背景音乐
Background music loops continuously, needs volume control, and must be stopped on scene shutdown.
typescript
// --- In PreloaderScene.preload() ---
this.load.audio('music-game', ['assets/audio/game.mp3', 'assets/audio/game.ogg']);
this.load.audio('music-menu', ['assets/audio/menu.mp3', 'assets/audio/menu.ogg']);
// --- In GameScene.create() ---
this.bgMusic = this.sound.add('music-game', { loop: true, volume: 0.5 });
this.bgMusic.play();
// Adjust volume at runtime
this.bgMusic.setVolume(0.3);
// Pause/resume (e.g. on pause screen)
this.bgMusic.pause();
this.bgMusic.resume();
// Stop (e.g. on scene shutdown)
this.bgMusic.stop();背景音乐持续循环播放,需要音量控制,并且必须在场景关闭时停止。
typescript
// --- 在 PreloaderScene.preload() 中 ---
this.load.audio('music-game', ['assets/audio/game.mp3', 'assets/audio/game.ogg']);
this.load.audio('music-menu', ['assets/audio/menu.mp3', 'assets/audio/menu.ogg']);
// --- 在 GameScene.create() 中 ---
this.bgMusic = this.sound.add('music-game', { loop: true, volume: 0.5 });
this.bgMusic.play();
// 运行时调整音量
this.bgMusic.setVolume(0.3);
// 暂停/恢复(例如在暂停界面)
this.bgMusic.pause();
this.bgMusic.resume();
// 停止(例如在场景关闭时)
this.bgMusic.stop();SFX Pattern: One-Shot Sounds
音效模式:一次性声音
For sound effects that fire and forget — coin pickups, gunshots, UI clicks — use directly. Do not store a reference:
this.sound.play()typescript
// In any scene method or event handler:
this.sound.play('sfx-coin', { volume: 0.9 });
this.sound.play('sfx-jump', { volume: 1.0, rate: 1.05 });
this.sound.play('sfx-death', { volume: 0.8, detune: -200 });Phaser creates an internal instance, plays it to completion, then destroys it. Zero cleanup required.
对于触发后无需后续操作的音效——如收集金币、枪声、UI 点击——直接使用 ,无需存储引用:
this.sound.play()typescript
// 在任意场景方法或事件处理器中:
this.sound.play('sfx-coin', { volume: 0.9 });
this.sound.play('sfx-jump', { volume: 1.0, rate: 1.05 });
this.sound.play('sfx-death', { volume: 0.8, detune: -200 });Phaser 会创建内部实例,播放完毕后自动销毁,无需清理。
Sound Pooling for Rapid SFX
快速音效的声音池
If a sound fires many times per second (gunshots, footsteps, rapid UI feedback), a single instance causes audible cutoff — each new call restarts the same sound from the beginning.
play()Pre-create a pool of instances and round-robin through them:
typescript
create(): void {
// Create a pool of 5 gunshot sounds
this.gunshotPool = [];
for (let i = 0; i < 5; i++) {
this.gunshotPool.push(this.sound.add('sfx-gunshot', { volume: 0.8 }));
}
this.poolIndex = 0;
}
private fireGunshot(): void {
const snd = this.gunshotPool[this.poolIndex];
// Stop any currently playing instance at this slot, then play fresh
if (snd.isPlaying) snd.stop();
snd.play();
this.poolIndex = (this.poolIndex + 1) % this.gunshotPool.length;
}Pool size guideline: match the maximum overlapping instances you expect. For footsteps, 3–4 is usually sufficient.
如果某个声音每秒触发多次(如枪声、脚步声、快速 UI 反馈),单个实例会导致声音中断——每次新的 调用都会重新启动同一个声音。
play()预先创建一组实例,循环使用:
typescript
create(): void {
// 创建 5 个枪声声音实例的池
this.gunshotPool = [];
for (let i = 0; i < 5; i++) {
this.gunshotPool.push(this.sound.add('sfx-gunshot', { volume: 0.8 }));
}
this.poolIndex = 0;
}
private fireGunshot(): void {
const snd = this.gunshotPool[this.poolIndex];
// 停止当前槽中正在播放的实例,然后重新播放
if (snd.isPlaying) snd.stop();
snd.play();
this.poolIndex = (this.poolIndex + 1) % this.gunshotPool.length;
}池大小指南:匹配你预期的最大重叠实例数。对于脚步声,3–4 个通常足够。
Audio Sprites
音频精灵
Audio sprites pack multiple short sounds into a single audio file with a JSON marker file. This reduces HTTP requests and is ideal for mobile where audio loading is slow.
音频精灵将多个短声音打包到单个音频文件中,并附带 JSON 标记文件。这减少了 HTTP 请求次数,非常适合音频加载较慢的移动端。
Loading
加载
typescript
// preload()
this.load.audioSprite(
'sfx-pack', // key
'assets/audio/sfx-pack.json', // JSON with marker definitions
['assets/audio/sfx-pack.mp3', 'assets/audio/sfx-pack.ogg'] // audio files
);typescript
// preload()
this.load.audioSprite(
'sfx-pack', // 键名
'assets/audio/sfx-pack.json', // 包含标记定义的 JSON 文件
['assets/audio/sfx-pack.mp3', 'assets/audio/sfx-pack.ogg'] // 音频文件
);Playing
播放
typescript
// this.sound.playAudioSprite(key, markerName, config?)
this.sound.playAudioSprite('sfx-pack', 'coin');
this.sound.playAudioSprite('sfx-pack', 'jump', { volume: 0.8 });
this.sound.playAudioSprite('sfx-pack', 'hurt', { rate: 1.2 });typescript
// this.sound.playAudioSprite(key, markerName, config?)
this.sound.playAudioSprite('sfx-pack', 'coin');
this.sound.playAudioSprite('sfx-pack', 'jump', { volume: 0.8 });
this.sound.playAudioSprite('sfx-pack', 'hurt', { rate: 1.2 });JSON Format
JSON 格式
json
{
"resources": ["sfx-pack.mp3", "sfx-pack.ogg"],
"spritemap": {
"coin": { "start": 0.0, "end": 0.4, "loop": false },
"jump": { "start": 0.5, "end": 0.85, "loop": false },
"hurt": { "start": 1.0, "end": 1.6, "loop": false },
"music": { "start": 2.0, "end": 34.0, "loop": true }
}
}See for the full AudioSprite JSON schema.
references/audio-api.mdjson
{
"resources": ["sfx-pack.mp3", "sfx-pack.ogg"],
"spritemap": {
"coin": { "start": 0.0, "end": 0.4, "loop": false },
"jump": { "start": 0.5, "end": 0.85, "loop": false },
"hurt": { "start": 1.0, "end": 1.6, "loop": false },
"music": { "start": 2.0, "end": 34.0, "loop": true }
}
}完整的 AudioSprite JSON 模式请参考 。
references/audio-api.mdVolume Management
音量管理
typescript
// Global volume (affects all sounds)
this.sound.volume = 0.5; // set
const vol = this.sound.volume; // get
// Per-sound volume
music.setVolume(0.4);
const soundVol = music.volume;
// Mute/unmute everything
this.sound.mute = true; // mute all
this.sound.mute = false; // unmute all
// Check if global mute is on
const isMuted = this.sound.mute;typescript
// 全局音量(影响所有声音)
this.sound.volume = 0.5; // 设置
const vol = this.sound.volume; // 获取
// 单声音量
music.setVolume(0.4);
const soundVol = music.volume;
// 静音/取消静音所有声音
this.sound.mute = true; // 全局静音
this.sound.mute = false; // 取消全局静音
// 检查是否全局静音
const isMuted = this.sound.mute;Mute Button Pattern
静音按钮模式
typescript
create(): void {
const muteBtn = this.add.image(750, 30, 'btn-mute').setInteractive();
muteBtn.on('pointerdown', () => {
this.sound.mute = !this.sound.mute;
muteBtn.setTexture(this.sound.mute ? 'btn-unmute' : 'btn-mute');
});
}typescript
create(): void {
const muteBtn = this.add.image(750, 30, 'btn-mute').setInteractive();
muteBtn.on('pointerdown', () => {
this.sound.mute = !this.sound.mute;
muteBtn.setTexture(this.sound.mute ? 'btn-unmute' : 'btn-mute');
});
}Mobile Audio Unlock
移动端音频解锁
Mobile browsers and some desktop browsers block audio playback until the user interacts with the page. This is enforced at the browser level — there is no workaround.
Phaser handles this automatically. It listens for the first or event and resumes the at that moment. All sounds queued before that point will begin playing immediately after unlock.
pointerdownkeydownAudioContextCheck if audio is locked:
typescript
if (this.sound.locked) {
// AudioContext has not yet been unlocked
// Show a "tap to start" overlay
}
// Listen for the unlock event
this.sound.on(Phaser.Sound.Events.UNLOCKED, () => {
// Now safe to play audio
this.bgMusic.play();
});Best practice for audio-critical games: Show a full-screen "Tap to Start" overlay. When the player taps it, dismiss it. Phaser's internal unlock fires at the same time, so audio starts on the next call.
play()typescript
create(): void {
this.bgMusic = this.sound.add('music-main', { loop: true, volume: 0.6 });
if (this.sound.locked) {
const overlay = this.add.rectangle(400, 300, 800, 600, 0x000000, 0.7)
.setInteractive();
const label = this.add.text(400, 300, 'TAP TO START', {
fontSize: '32px', color: '#ffffff',
}).setOrigin(0.5);
this.sound.once(Phaser.Sound.Events.UNLOCKED, () => {
overlay.destroy();
label.destroy();
this.bgMusic.play();
});
} else {
this.bgMusic.play();
}
}移动端浏览器和部分桌面浏览器会阻止音频自动播放,直到用户与页面进行交互。这是浏览器层面的强制限制,没有解决办法。
Phaser 会自动处理此问题。 它会监听第一次 或 事件,并在此时恢复 。在此之前排队的所有声音会在解锁后立即开始播放。
pointerdownkeydownAudioContext检查音频是否被锁定:
typescript
if (this.sound.locked) {
// AudioContext 尚未解锁
// 显示“点击开始”覆盖层
}
// 监听解锁事件
this.sound.on(Phaser.Sound.Events.UNLOCKED, () => {
// 现在可以安全播放音频
this.bgMusic.play();
});对音频要求较高的游戏的最佳实践: 显示全屏“点击开始”覆盖层。当玩家点击时,隐藏覆盖层。Phaser 的内部解锁会同时触发,因此音频会在下次 调用时开始播放。
play()typescript
create(): void {
this.bgMusic = this.sound.add('music-main', { loop: true, volume: 0.6 });
if (this.sound.locked) {
const overlay = this.add.rectangle(400, 300, 800, 600, 0x000000, 0.7)
.setInteractive();
const label = this.add.text(400, 300, 'TAP TO START', {
fontSize: '32px', color: '#ffffff',
}).setOrigin(0.5);
this.sound.once(Phaser.Sound.Events.UNLOCKED, () => {
overlay.destroy();
label.destroy();
this.bgMusic.play();
});
} else {
this.bgMusic.play();
}
}AudioContext Suspension Recovery
AudioContext 挂起恢复
Mobile browsers (especially iOS Safari) and some desktop browsers suspend the when the tab loses focus, the device sleeps, or the PWA is backgrounded. Unlike the initial autoplay lock, resumption is NOT automatic — Phaser does not restore a suspended context on tab re-focus.
AudioContextSymptoms: Music stops abruptly when the user switches tabs and returns. No error in console. is (context was unlocked previously) but audio is still silent.
this.sound.lockedfalseFix — resume the context on visibility change:
typescript
// In PreloaderScene.create() or main.ts, after game is initialized:
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
const mgr = this.sound as Phaser.Sound.WebAudioSoundManager;
if (mgr.context?.state === 'suspended') {
mgr.context.resume();
}
}
});Alternative — use Phaser's built-in focus event:
typescript
this.game.events.on(Phaser.Core.Events.FOCUS, () => {
const mgr = this.sound as Phaser.Sound.WebAudioSoundManager;
if (mgr.context?.state === 'suspended') {
mgr.context.resume();
}
});Check before calling — calling it when already is a no-op, but calling it when throws.
mgr.context?.stateresume()'running''closed'AudioContext states:
| State | Meaning |
|---|---|
| Audio playing normally |
| Paused (tab hidden, device sleep) — call |
| Permanently closed — create a new game instance |
移动端浏览器(尤其是 iOS Safari)和部分桌面浏览器会在标签页失去焦点、设备休眠或 PWA 进入后台时挂起 。与初始自动播放锁定不同,恢复不是自动的——Phaser 不会在标签页重新获得焦点时恢复挂起的上下文。
AudioContext症状: 用户切换标签页后返回时,音乐突然停止。控制台无错误。 为 (上下文之前已解锁)但音频仍然无声。
this.sound.lockedfalse修复方案——在可见性变化时恢复上下文:
typescript
// 在 PreloaderScene.create() 或 main.ts 中,游戏初始化后:
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
const mgr = this.sound as Phaser.Sound.WebAudioSoundManager;
if (mgr.context?.state === 'suspended') {
mgr.context.resume();
}
}
});替代方案——使用 Phaser 内置的焦点事件:
typescript
this.game.events.on(Phaser.Core.Events.FOCUS, () => {
const mgr = this.sound as Phaser.Sound.WebAudioSoundManager;
if (mgr.context?.state === 'suspended') {
mgr.context.resume();
}
});调用 前检查 —— 在状态为 时调用无影响,但在状态为 时调用会抛出错误。
resume()mgr.context?.state'running''closed'AudioContext 状态:
| 状态 | 含义 |
|---|---|
| 音频正常播放 |
| 已暂停(标签页隐藏、设备休眠)—— 调用 |
| 已永久关闭 —— 创建新的游戏实例 |
Crossfading Music Between Scenes
场景间音乐淡入淡出
Abrupt music cuts sound amateurish. Fade out the old track in the outgoing scene, fade in the new track in the incoming scene.
typescript
// --- In OutgoingScene.shutdown() ---
shutdown(): void {
if (this.bgMusic?.isPlaying) {
this.tweens.add({
targets: this.bgMusic,
volume: 0,
duration: 500,
onComplete: () => this.bgMusic.stop(),
});
}
}
// --- In IncomingScene.create() ---
create(): void {
this.bgMusic = this.sound.add('music-new', { loop: true, volume: 0 });
this.bgMusic.play();
this.tweens.add({
targets: this.bgMusic,
volume: 0.6,
duration: 800,
ease: 'Linear',
});
}Note: can tween any numeric property on any object, including . No special audio tween API is needed.
this.tweenssound.volume音乐突然切换听起来很不专业。在退出的场景中淡出旧曲目,在进入的场景中淡入新曲目。
typescript
// --- 在 OutgoingScene.shutdown() 中 ---
shutdown(): void {
if (this.bgMusic?.isPlaying) {
this.tweens.add({
targets: this.bgMusic,
volume: 0,
duration: 500,
onComplete: () => this.bgMusic.stop(),
});
}
}
// --- 在 IncomingScene.create() 中 ---
create(): void {
this.bgMusic = this.sound.add('music-new', { loop: true, volume: 0 });
this.bgMusic.play();
this.tweens.add({
targets: this.bgMusic,
volume: 0.6,
duration: 800,
ease: 'Linear',
});
}注意: 可以对任何对象的任何数值属性进行补间动画,包括 。无需使用特殊的音频补间 API。
this.tweenssound.volumeStopping Sounds on Scene Shutdown
场景关闭时停止声音
Always clean up audio when a scene shuts down. Otherwise sounds from a previous scene continue playing indefinitely.
typescript
// Stop all sounds owned by the SoundManager (global — affects all scenes)
this.sound.stopAll();
// Stop only a specific sound
this.bgMusic.stop();
// Preferred pattern in shutdown():
shutdown(): void {
// If music belongs to this scene only
this.bgMusic?.stop();
// If this is a top-level scene and you want to silence everything:
// this.sound.stopAll();
}Use only at the top-level game exit or between completely unrelated game states. For scene transitions, stop only the specific sounds the current scene owns.
stopAll()场景关闭时务必清理音频,否则之前场景的声音会无限播放。
typescript
// 停止 SoundManager 拥有的所有声音(全局——影响所有场景)
this.sound.stopAll();
// 仅停止特定声音
this.bgMusic.stop();
// shutdown() 中的推荐模式:
shutdown(): void {
// 如果音乐仅属于当前场景
this.bgMusic?.stop();
// 如果是顶级场景且需要静音所有声音:
// this.sound.stopAll();
}仅在顶级游戏退出或完全无关的游戏状态切换时使用 。对于场景过渡,仅停止当前场景拥有的特定声音即可。
stopAll()Additional Resources
额外资源
Reference Files
参考文件
- — Complete SoundManager, BaseSound, and WebAudioSound API reference, all events, SoundConfig fields, AudioSprite JSON schema
references/audio-api.md
- —— 完整的 SoundManager、BaseSound 和 WebAudioSound API 参考,所有事件、SoundConfig 字段、AudioSprite JSON 模式
references/audio-api.md