Phaser
25 minutes
linux
macos
windows

Audio

Add sound effects, background music, and audio sprites to your Phaser games.

What you'll accomplish

Implement sound effects and background music in your game.

Prerequisites

  • Working Phaser game with sprites and physics

Audio in Web Games

Web browsers have strict audio policies:

  • Audio can't autoplay until user interaction
  • Audio context must be unlocked by a click/touch

Phaser handles most of this automatically, but you should:

  1. Start music after a user click (like "Play" button)
  2. Have a mute button

Loading Audio

Load audio files in preload():

typescript
preload(): void {
  // Single sound
  this.load.audio('jump', 'assets/sounds/jump.wav');
  this.load.audio('coin', 'assets/sounds/coin.mp3');

  // Background music
  this.load.audio('bgMusic', 'assets/music/background.mp3');

  // Multiple formats for browser compatibility
  this.load.audio('explosion', [
    'assets/sounds/explosion.ogg',
    'assets/sounds/explosion.mp3',
  ]);
}
Note

Use .mp3 for broad compatibility. .ogg is smaller but not supported in Safari. Provide both for best results.

Playing Sounds

typescript
create(): void {
  // Play once
  this.sound.play('jump');

  // With config
  this.sound.play('coin', {
    volume: 0.5,    // 0 to 1
    rate: 1,        // Playback speed (1 = normal)
    detune: 0,      // Pitch adjustment in cents
    loop: false,
    delay: 0,       // Delay before playing (seconds)
  });
}

// Play on collision
collectCoin(): void {
  this.sound.play('coin', { volume: 0.7 });
  this.score += 10;
}

// Play on jump
update(): void {
  if (this.cursors.up.isDown && this.player.body.blocked.down) {
    this.player.body.setVelocityY(-400);
    this.sound.play('jump');
  }
}

Sound Objects

For more control, create sound objects:

typescript
private jumpSound!: Phaser.Sound.BaseSound;
private coinSound!: Phaser.Sound.BaseSound;

create(): void {
  // Create sound objects
  this.jumpSound = this.sound.add('jump');
  this.coinSound = this.sound.add('coin', {
    volume: 0.5,
    rate: 1,
  });
}

update(): void {
  if (this.cursors.up.isDown && this.player.body.blocked.down) {
    this.jumpSound.play();
  }
}

collectCoin(): void {
  // Slightly randomize pitch each time
  this.coinSound.play({
    detune: Phaser.Math.Between(-100, 100),
  });
}

Background Music

typescript
private music!: Phaser.Sound.BaseSound;

create(): void {
  // Create and configure music
  this.music = this.sound.add('bgMusic', {
    volume: 0.3,
    loop: true,
  });

  // Start after user interaction (e.g., in menu scene)
  this.input.once('pointerdown', () => {
    this.music.play();
  });
}

shutdown(): void {
  // Stop music when leaving scene
  this.music.stop();
}

Cross-Scene Music

For music that persists across scenes:

typescript
// In your main menu scene
create(): void {
  // Only start if not already playing
  if (!this.sound.get('bgMusic')) {
    this.music = this.sound.add('bgMusic', { loop: true, volume: 0.3 });
    this.music.play();
  }
}

// In game over scene - stop the music
create(): void {
  const music = this.sound.get('bgMusic');
  if (music) {
    music.stop();
  }
}

Mute Control

Provide audio controls for players:

typescript
create(): void {
  // Mute button
  const muteBtn = this.add.text(750, 20, '🔊', { fontSize: '32px' })
    .setInteractive({ useHandCursor: true });

  muteBtn.on('pointerdown', () => {
    this.sound.mute = !this.sound.mute;
    muteBtn.setText(this.sound.mute ? '🔇' : '🔊');
  });
}

// Or use keyboard
create(): void {
  this.input.keyboard?.on('keydown-M', () => {
    this.sound.mute = !this.sound.mute;
  });
}

Volume Control

typescript
create(): void {
  // Global volume (affects all sounds)
  this.sound.volume = 0.5;

  // Separate music and SFX volumes
  this.musicVolume = 0.3;
  this.sfxVolume = 0.7;

  // Apply to individual sounds
  this.music = this.sound.add('bgMusic', { volume: this.musicVolume });
  this.jumpSound = this.sound.add('jump', { volume: this.sfxVolume });
}

setMusicVolume(value: number): void {
  this.musicVolume = value;
  if (this.music) {
    (this.music as Phaser.Sound.WebAudioSound).setVolume(value);
  }
}

Audio Sprites

Pack multiple sounds into one file with timing data:

json
// assets/sounds/sfx.json
{
  "spritemap": {
    "jump": { "start": 0, "end": 0.5 },
    "coin": { "start": 0.5, "end": 1.0 },
    "explosion": { "start": 1.0, "end": 2.5 },
    "powerup": { "start": 2.5, "end": 3.2 }
  }
}
typescript
preload(): void {
  this.load.audioSprite('sfx', 'assets/sounds/sfx.json', [
    'assets/sounds/sfx.ogg',
    'assets/sounds/sfx.mp3',
  ]);
}

create(): void {
  // Play a sprite sound
  this.sound.playAudioSprite('sfx', 'jump');
  this.sound.playAudioSprite('sfx', 'coin', { volume: 0.5 });
}

Spatial Audio

For games with positional sound:

typescript
create(): void {
  // Create sound with spatial properties
  const enemySound = this.sound.add('enemy', {
    volume: 1,
    loop: true,
  });

  enemySound.play();
}

update(): void {
  // Adjust volume based on distance
  const distance = Phaser.Math.Distance.Between(
    this.player.x, this.player.y,
    this.enemy.x, this.enemy.y
  );

  const maxDistance = 500;
  const volume = Math.max(0, 1 - distance / maxDistance);
  (this.enemySound as Phaser.Sound.WebAudioSound).setVolume(volume);

  // Pan based on position
  const pan = (this.enemy.x - this.player.x) / 400;
  (this.enemySound as Phaser.Sound.WebAudioSound).setPan(
    Phaser.Math.Clamp(pan, -1, 1)
  );
}

Audio Events

React to audio events:

typescript
create(): void {
  const music = this.sound.add('bgMusic');

  music.on('play', () => console.log('Started playing'));
  music.on('pause', () => console.log('Paused'));
  music.on('resume', () => console.log('Resumed'));
  music.on('stop', () => console.log('Stopped'));
  music.on('complete', () => console.log('Finished'));
  music.on('looped', () => console.log('Loop iteration'));

  music.play();
}

Sound Manager Properties

typescript
// Check if audio is available
if (this.sound.locked) {
  // Audio context is locked, waiting for user interaction
}

// Check if muted
const isMuted = this.sound.mute;

// Get all playing sounds
const playingSounds = this.sound.getAllPlaying();

// Stop all sounds
this.sound.stopAll();

// Pause all sounds
this.sound.pauseAll();

// Resume all sounds
this.sound.resumeAll();

Free Sound Resources

SourceTypeLicense
FreesoundSound effectsVarious CC
OpenGameArtMusic & SFXVarious
KenneyComplete packsCC0
JSFXRRetro sound generator-

Common Issues

Frequently Asked Questions

  1. Check browser console for errors
  2. Ensure audio starts after user interaction
  3. Check file path and format
  4. Try wrapping in a click handler:
typescript
this.input.once('pointerdown', () => {
  this.sound.play('music');
});

Use Web Audio (Phaser's default) and preload all sounds. Consider shorter sound files. Use audio sprites to reduce HTTP requests.

Track if sound is already playing:

typescript
if (!this.jumpSound.isPlaying) {
  this.jumpSound.play();
}

The scene stops all its sounds on shutdown. Either:

  1. Play music from a persistent scene
  2. Use this.sound.get('music') to check if already playing
  3. Mark the sound as not scene-owned

Verify Your Understanding

You've completed this lesson if you can:

0/5

Next: Build and deploy your Phaser game.