Best for
- Use this skill when adding audio or sound to a Phaser 4 game.
phaserjs/phaser/skills/audio-and-sound/SKILL.md
Use this skill when adding audio or sound to a Phaser 4 game. Covers loading audio, playing sounds, music, volume, spatial audio, Web Audio API, and SoundManager. Triggers on: sound, audio, music, volume, mute.
Decision brief
Phaser provides a unified Sound system via this.sound (a SoundManager) that abstracts over Web Audio API and HTML5 Audio. It handles loading, playback, volume, panning, looping, markers, audio sprites, spatial audio, and browser autoplay-policy unlocking.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/phaserjs/phaser --skill "skills/audio-and-sound"Inspect the Agent Skill "audio-and-sound" from https://github.com/phaserjs/phaser/blob/41be1e462bc600064e498cba370bfa8c5c055a22/skills/audio-and-sound/SKILL.md at commit 41be1e462bc600064e498cba370bfa8c5c055a22. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
Assets loaded via this.load.audio() in preload() are ready by the time create() runs. Provide an array of URLs for cross-browser format fallback.
Phaser auto-selects the best backend via SoundManagerCreator.create():
Phaser auto-selects the best backend via SoundManagerCreator.create():
Accessed via this.sound in any Scene. It is a single shared instance across the entire game. Key responsibilities:
Created via this.sound.add(key, config). Each instance has its own playback state, volume, rate, detune, loop, pan, and seek properties. A sound must exist in the audio cache (loaded via the Loader) before it can be added.
Permission review
No configured static risk pattern was detected
This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.
Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 88/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 40,073 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Phaser provides a unified Sound system via
this.sound(a SoundManager) that abstracts over Web Audio API and HTML5 Audio. It handles loading, playback, volume, panning, looping, markers, audio sprites, spatial audio, and browser autoplay-policy unlocking.
Key source paths: src/sound/BaseSoundManager.js, src/sound/BaseSound.js, src/sound/webaudio/, src/sound/html5/, src/sound/SoundManagerCreator.js, src/sound/events/, src/sound/typedefs/
Related skills: ../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();
}
}
Assets loaded via this.load.audio() in preload() are ready by the time create() runs. Provide an array of URLs for cross-browser format fallback.
Phaser auto-selects the best backend via SoundManagerCreator.create():
config.audio.noAudio is true, or the device supports neither Web Audio nor HTML5 Audio, a NoAudioSoundManager is created (all calls are no-ops).config.audio.disableWebAudio is not true, a WebAudioSoundManager is created (preferred).WebAudio advantages: precise timing, gapless looping, stereo panning (StereoPannerNode), spatial audio (PannerNode), per-sound gain nodes, decodeAudio() for runtime decoding.
HTML5 Audio limitations: no spatial audio, no real stereo panning (pan fires events but no audible effect), less precise looping, requires instances count at load time for simultaneous playback.
Force HTML5 or disable audio via game config: audio: { disableWebAudio: true } or audio: { noAudio: true }. Pass audio: { context: existingAudioContext } to reuse a WebAudio context in SPAs.
this.sound)Accessed via this.sound in any Scene. It is a single shared instance across the entire game. Key responsibilities:
pauseOnBlur, default true)Created via this.sound.add(key, config). Each instance has its own playback state, volume, rate, detune, loop, pan, and seek properties. A sound must exist in the audio cache (loaded via the Loader) before it can be added.
State flags: isPlaying (boolean), isPaused (boolean).
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
Fire-and-forget -- this.sound.play(key, config?) adds, plays, and auto-destroys the sound on completion:
this.sound.play('explosion');
this.sound.play('powerup', { volume: 0.5, rate: 1.2 });
Retained reference -- this.sound.add(key, config?) then call play() on the instance:
const laser = this.sound.add('laser');
laser.play();
// Later: laser.stop(), laser.volume = 0.3, etc.
Each property can be set per-sound or globally on the manager. Global and per-sound values combine (for rate/detune, they multiply via 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);
The effective playback rate is: sound.rate * manager.rate * detuneRate where 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
The LOOPED event fires each time the sound loops back to the start. The LOOP event fires when the loop property changes.
sound.seek = 5.0; // jump to 5 seconds in
sound.setSeek(5.0); // chainable
console.log(sound.seek); // current playback position in seconds
Setting seek on a stopped sound has no effect.
sound.pan = -1; // full left
sound.pan = 0; // center
sound.pan = 1; // full right
sound.setPan(0.5); // chainable
Uses StereoPannerNode, if it exists, on WebAudio. On HTML5 Audio, the pan property fires events but has no audible effect.
Audio sprites combine multiple sounds into a single audio file with a JSON config (generated by the audiosprite tool). The JSON must be loaded separately.
// 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');
The JSON spritemap entries are automatically converted to markers with name, start, duration, and optional loop.
Manual markers -- you can also add markers to any sound:
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');
Marker API on BaseSound: 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();
The manager's pauseOnBlur (default true) automatically pauses all sounds when the tab loses focus.
Spatial audio uses the Web Audio PannerNode to position sounds in 2D/3D space relative to a listener.
// 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();
You can set sound.x and sound.y directly on a WebAudioSound to reposition it at any time. If follow is set to an object with x/y properties, the spatial position updates automatically each frame.
setListenerPosition() defaults to the center of the game canvas if called with no arguments.
// 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
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 */ });
| Property | Type | Default | Description |
|---|---|---|---|
mute | boolean | false | Whether the sound is muted |
volume | number | 1 | Volume, 0 (silence) to 1 (full) |
rate | number | 1 | Playback speed (0.5 = half, 2.0 = double) |
detune | number | 0 | Detuning in cents (-1200 to 1200) |
seek | number | 0 | Start playback position in seconds |
loop | boolean | false | Whether the sound should loop |
delay | number | 0 | Delay before playback starts, in seconds |
pan | number | 0 | Stereo pan, -1 (left) to 1 (right) |
source | SpatialSoundConfig | null | Spatial audio configuration (WebAudio only) |
Position: x (0), y (0), z (0) -- source position in world space.
Orientation: orientationX (0), orientationY (0), orientationZ (-1) -- source direction vector.
Models: panningModel ('equalpower' or 'HRTF'), distanceModel ('linear', 'inverse', 'exponential').
Distance: refDistance (1), maxDistance (10000), rolloffFactor (1).
Cone: coneInnerAngle (360), coneOuterAngle (0), coneOuterGain (0).
Tracking: follow (null) -- a Vector2Like object whose x/y is auto-tracked each frame.
| Property | Type | Default | Description |
|---|---|---|---|
name | string | (required) | Unique identifier for the marker |
start | number | 0 | Start position in seconds |
duration | number | (remaining) | Playback duration in seconds |
config | SoundConfig | {} | Default settings for this marker |
| Event Constant | String Value | Callback Args | When |
|---|---|---|---|
Events.PLAY | 'play' | (sound) | Sound starts playing |
Events.PAUSE | 'pause' | (sound) | Sound is paused |
Events.RESUME | 'resume' | (sound) | Sound resumes from pause |
Events.STOP | 'stop' | (sound) | Sound is stopped |
Events.COMPLETE | 'complete' | (sound) | Sound finishes (non-looping) |
Events.LOOPED | 'looped' | (sound) | Sound loops back to start |
Events.LOOP | 'loop' | (sound, value) | Loop property changes |
Events.MUTE | 'mute' | (sound, value) | Mute state changes |
Events.VOLUME | 'volume' | (sound, value) | Volume changes |
Events.RATE | 'rate' | (sound, value) | Rate changes |
Events.DETUNE | 'detune' | (sound, value) | Detune changes |
Events.SEEK | 'seek' | (sound, value) | Seek position changes |
Events.PAN | 'pan' | (sound, value) | Pan value changes |
Events.DESTROY | 'destroy' | (sound) | Sound is destroyed |
this.sound)| Event Constant | String Value | Callback Args | When |
|---|---|---|---|
Events.PAUSE_ALL | 'pauseall' | (manager) | pauseAll() called |
Events.RESUME_ALL | 'resumeall' | (manager) | resumeAll() called |
Events.STOP_ALL | 'stopall' | (manager) | stopAll() called |
Events.GLOBAL_MUTE | 'globalmute' | (manager, value) | Global mute changes |
Events.GLOBAL_VOLUME | 'globalvolume' | (manager, value) | Global volume changes |
Events.GLOBAL_RATE | 'globalrate' | (manager, value) | Global rate changes |
Events.GLOBAL_DETUNE | 'globaldetune' | (manager, value) | Global detune changes |
Events.UNLOCKED | 'unlocked' | (manager) | Audio system unlocked after user interaction |
Events.DECODED | 'decoded' | (key) | Single audio key decoded (WebAudio) |
Events.DECODED_ALL | 'decodedall' | () | All queued audio decoded (WebAudio) |
// Example: listen for completion
const sfx = this.sound.add('bang');
sfx.on('complete', (sound) => {
console.log(sound.key, 'finished');
});
sfx.play();
this.sound)Methods: 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).
Properties: volume (0-1), mute (boolean), rate (number), detune (-1200 to 1200), pauseOnBlur (boolean, default true), locked (boolean, read-only), listenerPosition (Vector2), sounds (array, private).
Methods: 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).
Properties: volume (0-1), mute (boolean), rate (number), detune (number), seek (seconds), loop (boolean), pan (-1 to 1), isPlaying (read-only), isPaused (read-only), duration (seconds), totalDuration (seconds), key (string), x / y (spatial position, WebAudio only).
All set* methods return this for chaining.
Browsers block audio until user interaction. Phaser handles this automatically:
AudioContext starts suspended. Phaser listens for touchstart/touchend/mousedown/mouseup/keydown on document.body to call context.resume(). The locked property is true until unlocked; UNLOCKED event fires once resolved.You do not need to handle unlocking manually. To know when ready, listen for UNLOCKED:
if (this.sound.locked) {
this.sound.once('unlocked', () => {
this.sound.play('bgm');
});
} else {
this.sound.play('bgm');
}
No single format works everywhere. Provide multiple formats: this.load.audio('bgm', ['assets/bgm.ogg', 'assets/bgm.mp3']). MP3 has broadest support. OGG Vorbis lacks Safari support. AAC/M4A works well on Safari/iOS. WebM/Opus has excellent quality but limited older browser support.
HTML5 Audio uses a pool of <audio> tags. Specify instances when loading for simultaneous playback: this.load.audio('shot', 'assets/shot.mp3', { instances: 4 }). Default is 1. If all tags are in use and manager.override is true (default), the sound with the most progress is hijacked. WebAudio has no such limitation.
StereoPannerNode not supported on iOS/Safari, so pan has no audible effect (events still fire).context.suspend()/context.resume() on the VISIBLE game event.setListenerPosition() and spatial audio are WebAudio-only.For SPAs that recreate the game without a full page reload, pass audio: { context: existingAudioContext } in the game config. You can also swap contexts at runtime via this.sound.setAudioContext(newContext) (WebAudio only).
There is one SoundManager per game, not per scene. this.sound in every scene references the same manager. Sounds are not automatically cleaned up on scene shutdown -- you must stop/remove them yourself if needed. Looping sounds will continue playing across scene changes unless explicitly stopped.
this.sound.play(key) creates a sound that auto-destroys on completion -- you cannot control it after calling play. Use this.sound.add(key) when you need a persistent reference to pause, stop, adjust volume, or listen for events.
The source config for spatial audio is silently ignored when using HTML5 Audio. If your game must support HTML5 Audio fallback, do not rely on spatial positioning for gameplay-critical audio cues.
Access the underlying AudioContext to create an AnalyserNode for frequency/waveform visualization:
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
This only works with the WebAudioSoundManager. Check this.sound.context exists before using.
| File | Purpose |
|---|---|
src/sound/SoundManagerCreator.js | Factory: picks WebAudio, HTML5, or NoAudio manager |
src/sound/BaseSoundManager.js | Base manager: add, play, get, stop/pause/resume all, global volume/rate/detune |
src/sound/BaseSound.js | Base sound: play/pause/resume/stop, markers, config, calculateRate |
src/sound/webaudio/WebAudioSoundManager.js | WebAudio manager: AudioContext, gain nodes, unlock, spatial listener, decodeAudio |
src/sound/webaudio/WebAudioSound.js | WebAudio sound: buffer sources, spatial/panner nodes, seek, all properties |
src/sound/html5/HTML5AudioSoundManager.js | HTML5 manager: audio tag pooling, locked queue, override |
src/sound/html5/HTML5AudioSound.js | HTML5 sound: tag-based playback, limited feature set |
src/sound/noaudio/NoAudioSoundManager.js | No-op manager for environments without audio |
src/sound/events/ | All sound event constants (PLAY, STOP, COMPLETE, etc.) |
src/sound/typedefs/SoundConfig.js | SoundConfig type definition |
src/sound/typedefs/SoundMarker.js | SoundMarker type definition |
src/sound/typedefs/SpatialSoundConfig.js | SpatialSoundConfig type definition |
src/sound/typedefs/AudioSpriteSound.js | AudioSpriteSound type definition |
Alternatives
coreyhaines31/marketingskills
When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program
alirezarezvani/claude-skills
App Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklist
dotnet/skills
Migrates .NET test projects from VSTest to Microsoft.Testing.Platform (MTP). Use when user asks to "migrate to MTP", "switch from VSTest", "enable Microsoft.Testing.Platform", "use MTP runner", set OutputType=Exe only for test projects in Directory.Build.props, or mentions EnableMSTestRunner, EnableNUnitRunner, or UseMicrosoftTestingPlatformRunner. USE FOR: MTP behavioral differences vs VSTest (exit code 8, zero tests discovered, --ignore-exit-code, TESTINGPLATFORM_EXITCODE_IGNORE); centralizing
JasonColapietro/suede-creator-skills
Suede-owned experimentation discipline for hypotheses, sample sizing, test duration, significance, and repeatable experiment programs. Use when comparing variants, deciding whether a result is reliable, or building an experiment backlog and cadence. NOT FOR: analytics instrumentation (use suede-analytics), post-click conversion diagnosis (use suede-site-alchemy), or writing the variant copy itself (use suede-copy).