phaser-audio

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Phaser 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
AudioContext
is unavailable — typically older Android WebViews and some edge-case browser configurations. You rarely target it intentionally. If you must force it:
typescript
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 存在的单轨限制影响。
AudioContext
不可用时,会自动回退到 HTML5 Audio —— 通常出现在旧版 Android WebView 和一些特殊浏览器配置中。除非有特殊需求,否则无需刻意使用它。如果必须强制使用:
typescript
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']);
}
绝不要只加载单一格式。如果你只提供
.ogg
格式,在你的 Chrome 开发环境中运行完美的游戏,在 Safari/iOS 上会完全没有声音。

Adding and Playing Sounds

添加与播放声音

this.sound.add()
— Full Control

this.sound.add()
—— 完全控制

Use
add()
when you need a reference to control the sound (pause, resume, seek, adjust volume, listen for events):
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()
—— 一次性触发即遗忘

Use the manager's
play()
for sounds you fire once and never need to reference again. Phaser manages the lifetime automatically:
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 →
add()
. One-shot SFX →
sound.play()
.
对于只需播放一次且无需后续引用的声音,使用管理器的
play()
方法。Phaser 会自动管理其生命周期:
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
SoundConfig
object as the second argument to
add()
or as the second argument to
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)
};
SoundConfig
对象作为第二个参数传递给
add()
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
this.sound.play()
directly. Do not store a reference:
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
play()
call restarts the same sound from the beginning.
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
references/audio-api.md
for the full AudioSprite JSON schema.
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  }
  }
}
完整的 AudioSprite JSON 模式请参考
references/audio-api.md

Volume 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
pointerdown
or
keydown
event and resumes the
AudioContext
at that moment. All sounds queued before that point will begin playing immediately after unlock.
Check 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
play()
call.
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 会自动处理此问题。 它会监听第一次
pointerdown
keydown
事件,并在此时恢复
AudioContext
。在此之前排队的所有声音会在解锁后立即开始播放。
检查音频是否被锁定:
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
AudioContext
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.
Symptoms: Music stops abruptly when the user switches tabs and returns. No error in console.
this.sound.locked
is
false
(context was unlocked previously) but audio is still silent.
Fix — 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
mgr.context?.state
before calling
resume()
— calling it when already
'running'
is a no-op, but calling it when
'closed'
throws.
AudioContext states:
StateMeaning
'running'
Audio playing normally
'suspended'
Paused (tab hidden, device sleep) — call
resume()
'closed'
Permanently closed — create a new game instance
移动端浏览器(尤其是 iOS Safari)和部分桌面浏览器会在标签页失去焦点、设备休眠或 PWA 进入后台时挂起
AudioContext
。与初始自动播放锁定不同,恢复不是自动的——Phaser 不会在标签页重新获得焦点时恢复挂起的上下文。
症状: 用户切换标签页后返回时,音乐突然停止。控制台无错误。
this.sound.locked
false
(上下文之前已解锁)但音频仍然无声。
修复方案——在可见性变化时恢复上下文:
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 状态:
状态含义
'running'
音频正常播放
'suspended'
已暂停(标签页隐藏、设备休眠)—— 调用
resume()
'closed'
已永久关闭 —— 创建新的游戏实例

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:
this.tweens
can tween any numeric property on any object, including
sound.volume
. No special audio tween API is needed.
音乐突然切换听起来很不专业。在退出的场景中淡出旧曲目,在进入的场景中淡入新曲目。
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',
  });
}
注意:
this.tweens
可以对任何对象的任何数值属性进行补间动画,包括
sound.volume
。无需使用特殊的音频补间 API。

Stopping 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
stopAll()
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.
场景关闭时务必清理音频,否则之前场景的声音会无限播放。
typescript
// 停止 SoundManager 拥有的所有声音(全局——影响所有场景)
this.sound.stopAll();

// 仅停止特定声音
this.bgMusic.stop();

// shutdown() 中的推荐模式:
shutdown(): void {
  // 如果音乐仅属于当前场景
  this.bgMusic?.stop();
  // 如果是顶级场景且需要静音所有声音:
  // this.sound.stopAll();
}
仅在顶级游戏退出或完全无关的游戏状态切换时使用
stopAll()
。对于场景过渡,仅停止当前场景拥有的特定声音即可。

Additional Resources

额外资源

Reference Files

参考文件

  • references/audio-api.md
    — 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 模式