$npx -y skills add Yakoub-ai/phaser4-gamedev --skill phaser-audioThis skill should be used when the user asks to "add sound", "play music", "audio not working", "add background music", "sound effects", "mute button", "audio sprite", "game audio", "play sound effect", or "music won't play".
| 1 | # Phaser 4 Audio |
| 2 | |
| 3 | 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. |
| 4 | |
| 5 | ## Web Audio vs HTML5 Audio |
| 6 | |
| 7 | 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. |
| 8 | |
| 9 | 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: |
| 10 | |
| 11 | ```typescript |
| 12 | const config: Phaser.Types.Core.GameConfig = { |
| 13 | audio: { |
| 14 | disableWebAudio: true, // force HTML5 Audio fallback |
| 15 | }, |
| 16 | }; |
| 17 | ``` |
| 18 | |
| 19 | **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. |
| 20 | |
| 21 | ## Loading Audio: Always Provide mp3 AND ogg |
| 22 | |
| 23 | 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: |
| 24 | |
| 25 | ```typescript |
| 26 | // preload() |
| 27 | preload(): void { |
| 28 | // ALWAYS provide both formats as an array — mp3 first, ogg second |
| 29 | this.load.audio('music-main', ['assets/audio/main.mp3', 'assets/audio/main.ogg']); |
| 30 | this.load.audio('sfx-jump', ['assets/audio/jump.mp3', 'assets/audio/jump.ogg']); |
| 31 | this.load.audio('sfx-coin', ['assets/audio/coin.mp3', 'assets/audio/coin.ogg']); |
| 32 | this.load.audio('sfx-hurt', ['assets/audio/hurt.mp3', 'assets/audio/hurt.ogg']); |
| 33 | } |
| 34 | ``` |
| 35 | |
| 36 | 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`. |
| 37 | |
| 38 | ## Adding and Playing Sounds |
| 39 | |
| 40 | ### `this.sound.add()` — Full Control |
| 41 | |
| 42 | Use `add()` when you need a reference to control the sound (pause, resume, seek, adjust volume, listen for events): |
| 43 | |
| 44 | ```typescript |
| 45 | create(): void { |
| 46 | const music = this.sound.add('music-main', { |
| 47 | loop: true, |
| 48 | volume: 0.6, |
| 49 | }); |
| 50 | music.play(); |
| 51 | this.music = music; |
| 52 | } |
| 53 | ``` |
| 54 | |
| 55 | ### `this.sound.play()` — One-Shot Fire-and-Forget |
| 56 | |
| 57 | Use the manager's `play()` for sounds you fire once and never need to reference again. Phaser manages the lifetime automatically: |
| 58 | |
| 59 | ```typescript |
| 60 | // In update() or an event handler: |
| 61 | this.sound.play('sfx-coin', { volume: 0.9 }); |
| 62 | this.sound.play('sfx-jump', { volume: 1.0, rate: 1.1 }); |
| 63 | ``` |
| 64 | |
| 65 | **Rule of thumb:** Background music → `add()`. One-shot SFX → `sound.play()`. |
| 66 | |
| 67 | ## SoundConfig Options |
| 68 | |
| 69 | Pass a `SoundConfig` object as the second argument to `add()` or as the second argument to `play()`: |
| 70 | |
| 71 | ```typescript |
| 72 | const config: Phaser.Types.Sound.SoundConfig = { |
| 73 | loop: false, // boolean — loop the sound (default: false) |
| 74 | volume: 1, // number 0–1 — per-sound volume (default: 1) |
| 75 | rate: 1, // number — playback speed multiplier (default: 1; 2 = double speed) |
| 76 | detune: 0, // number — cents offset from base pitch (default: 0; 100 = 1 semitone) |
| 77 | seek: 0, // number — start position in seconds (default: 0) |
| 78 | delay: 0, // number — seconds to wait before playing (default: 0) |
| 79 | }; |
| 80 | ``` |
| 81 | |
| 82 | ## Background Music |
| 83 | |
| 84 | Background music loops continuously, needs volume control, and must be stopped on scene shutdown. |
| 85 | |
| 86 | ```typescript |
| 87 | // --- In PreloaderScene.preload() --- |
| 88 | this.load.audio('music-game', ['assets/audio/game.mp3', 'assets/audio/game.ogg']); |
| 89 | this.load.audio('music-menu', ['assets/audio/menu.mp3', 'assets/audio/menu.ogg']); |
| 90 | |
| 91 | // --- In GameScene.create() --- |
| 92 | this.bgMusic = this.sound.add('music-game', { loop: true, volume: 0.5 }); |
| 93 | this.bgMusic.play(); |
| 94 | |
| 95 | // Adjust volume at runtime |
| 96 | this.bgMusic.setVolume(0.3); |
| 97 | |
| 98 | // Pause/resume (e.g. on pause screen) |
| 99 | this.bgMusic.pause(); |
| 100 | this.bgMusic.resume(); |
| 101 | |
| 102 | // Stop (e.g. on scene shutdown) |
| 103 | this.bgMusic.stop(); |
| 104 | ``` |
| 105 | |
| 106 | ## SFX Pattern: One-Shot Sounds |
| 107 | |
| 108 | For sound effects that fire and forget — coin pickups, gunshots, UI clicks — use `this.sound.play()` directly. Do not store a reference: |
| 109 | |
| 110 | ```typescript |
| 111 | // In any scene method or event handler: |
| 112 | this.sound.play('sfx-coin', { volume: 0.9 }); |
| 113 | this.sound.play('sfx-jump', { volume: 1.0, rate: 1.05 }); |
| 114 | this.sound.play('sfx-death', { volume: 0.8, detune: -200 }); |
| 115 | ``` |
| 116 | |
| 117 | Phaser creates an internal instance, plays it to completion, then destroys it. Zero cleanup required. |
| 118 | |
| 119 | ## Sound Pooling for Rapid SFX |
| 120 | |
| 121 | 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 |