mcpField
A
audio-and-sound

audio-and-sound

github.com/phaserjs开发40.6k965 次安装10 周前更新
代码仓库

关于 audio-and-sound

面向 Phaser 游戏开发者的音频系统参考,说明如何加载和控制声音,并介绍 Web Audio 与 HTML5 Audio 后端及其能力差异。

主要功能

  1. 01Web Audio 与 HTML5 Audio 后端
  2. 02声音播放、循环与音量控制
  3. 03标记、音频精灵与定位音频
  4. 04暂停恢复及浏览器音频解锁

适用场景

  1. 01播放背景音乐和游戏音效
  2. 02将多段音效配置为音频精灵
  3. 03根据游戏对象位置播放定位声音

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/phaserjs/phaser --skill audio-and-sound

安装后在 Claude Code 中输入/audio-and-sound确认已加载。

详细介绍

音频与声音

Phaser 通过 this.sound(一个 SoundManager)提供统一的 Sound 系统,对 Web Audio API 和 HTML5 Audio 进行了抽象。它处理加载、播放、音量、声像、循环、标记、音频精灵、空间音频以及浏览器自动播放策略解锁。

关键源码路径: src/sound/BaseSoundManager.js、src/sound/BaseSound.js、src/sound/webaudio/、src/sound/html5/、src/sound/SoundManagerCreator.js、src/sound/events/、src/sound/typedefs/ 相关技能: ../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();
    }
}

在 preload() 中通过 this.load.audio() 加载的资源,在 create() 运行时已经就绪。提供一个 URL 数组以实现跨浏览器格式回退。

核心概念

WebAudio 与 HTML5 Audio

Phaser 通过 SoundManagerCreator.create() 自动选择最佳后端:

  1. 如果 config.audio.noAudio 为 true,或者设备既不支持 Web Audio 也不支持 HTML5 Audio,则会创建一个 NoAudioSoundManager(所有调用均为空操作)。
  2. 如果设备支持 Web Audio 且 config.audio.disableWebAudio 不为 true,则会创建一个 WebAudioSoundManager(首选)。
  3. 否则,会创建一个 HTML5AudioSoundManager 作为回退。

WebAudio 的优势:精确的时序、无缝循环、立体声声像(StereoPannerNode)、空间音频(PannerNode)、每个声音独立的增益节点、decodeAudio() 用于运行时解码。

HTML5 Audio 的限制:没有空间音频,没有真正的立体声声像(pan 会触发事件但没有可听见的效果),循环不够精确,同时播放需要在加载时指定 instances 数量。

通过游戏配置强制使用 HTML5 或禁用音频:audio: { disableWebAudio: true } 或 audio: { noAudio: true }。传入 audio: { context: existingAudioContext } 可在 SPA 中复用 WebAudio 上下文。

SoundManager(this.sound)

可在任何 Scene 中通过 this.sound 访问。它是整个游戏中单一共享的实例。主要职责:

  • 添加、播放和移除声音实例
  • 全局音量、静音、速率和失谐
  • 当浏览器标签页失去/获得焦点时自动暂停/恢复(pauseOnBlur,默认为 true)
  • 针对移动浏览器的音频解锁处理
  • 空间音频监听器位置(仅限 WebAudio)
声音实例

通过 this.sound.add(key, config) 创建。每个实例都有自己的播放状态、音量、速率、失谐、循环、声像和定位属性。声音必须存在于音频缓存中(通过 Loader 加载)才能被添加。

状态标志:isPlaying(布尔值)、isPaused(布尔值)。

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

常见模式

播放声音

即发即忘 —— this.sound.play(key, config?) 会添加并播放声音,并在完成后自动销毁:

this.sound.play('explosion');
this.sound.play('powerup', { volume: 0.5, rate: 1.2 });

保留引用 —— this.sound.add(key, config?),然后在实例上调用 play():

const laser = this.sound.add('laser');
laser.play();
// Later: laser.stop(), laser.volume = 0.3, etc.
音量、速率和失谐

每个属性都可以按声音设置,也可以在管理器上全局设置。全局值和按声音的值会组合(对于速率/失谐,它们通过 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);

有效播放速率为:sound.rate * manager.rate * detuneRate,其中 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

每次声音循环回到开头时都会触发 LOOPED 事件。当循环属性发生变化时,会触发 LOOP 事件。

定位
sound.seek = 5.0;        // jump to 5 seconds in
sound.setSeek(5.0);      // chainable
console.log(sound.seek);  // current playback position in seconds

对已停止的声音设置定位不会产生任何效果。

立体声声像
sound.pan = -1;   // full left
sound.pan = 0;    // center
sound.pan = 1;    // full right
sound.setPan(0.5); // chainable

在 WebAudio 上使用 StereoPannerNode(如果存在)。在 HTML5 Audio 上,pan 属性会触发事件,但没有可听见的效果。

音频精灵和标记

音频精灵通过一个 JSON 配置(由 audiosprite 工具生成)将多个声音合并到一个音频文件中。该 JSON 必须单独加载。

// 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');

JSON 中的 spritemap 条目会自动转换为带有 name、start、duration 以及可选 loop 的标记。

手动标记——你也可以向任何声音添加标记:

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');

BaseSound 上的标记 API: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();

管理器的 pauseOnBlur(默认为 true)会在标签页失去焦点时自动暂停所有声音。

空间音频(仅限 WebAudio)

空间音频使用 Web Audio 的 PannerNode,相对于听者在 2D/3D 空间中定位声音。

// 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();

你可以直接在 WebAudioSound 上设置 sound.x 和 sound.y,随时重新定位它。如果 follow 被设置为一个带有 x/y 属性的对象,空间位置会每帧自动更新。

如果在调用 setListenerPosition() 时不传任何参数,它默认将听者位置设为游戏画布的中心。

静音
// 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
在运行时解码音频(仅限 WebAudio)
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 */ });

配置参考

SoundConfig
属性类型默认值描述
mutebooleanfalse声音是否静音
volumenumber1音量,0(静音)到 1(最大)
ratenumber1播放速度(0.5 = 一半,2.0 = 两倍)
detunenumber0以音分表示的失谐(-1200 到 1200)
seeknumber0开始播放位置,以秒为单位
loopbooleanfalse声音是否应循环
delaynumber0播放开始前的延迟,以秒为单位
pannumber0立体声平移,-1(左)到 1(右)
sourceSpatialSoundConfignull空间音频配置(仅限 WebAudio)
SpatialSoundConfig

位置:x (0)、y (0)、z (0) —— 声源在世界空间中的位置。 方向:orientationX (0)、orientationY (0)、orientationZ (-1) —— 声源方向向量。 模型:panningModel('equalpower' 或 'HRTF')、distanceModel('linear'、'inverse'、'exponential')。 距离:refDistance (1)、maxDistance (10000)、rolloffFactor (1)。 锥形:coneInnerAngle (360)、coneOuterAngle (0)、coneOuterGain (0)。 跟踪:follow (null) —— 一个 Vector2Like 对象,其 x/y 每帧会被自动跟踪。

SoundMarker
属性类型默认值描述
namestring(必填)标记的唯一标识符
startnumber0开始位置,以秒为单位
durationnumber(剩余)播放时长,以秒为单位
configSoundConfig{}此标记的默认设置

事件

声音实例事件(在 Sound 对象上触发)
事件常量字符串值回调参数触发时机
Events.PLAY'play'(sound)声音开始播放
Events.PAUSE'pause'(sound)声音暂停
Events.RESUME'resume'(sound)声音从暂停中恢复
Events.STOP'stop'(sound)声音停止
Events.COMPLETE'complete'(sound)声音播放完成(非循环)
Events.LOOPED'looped'(sound)声音循环回到开头
Events.LOOP'loop'(sound, value)循环属性变化
Events.MUTE'mute'(sound, value)静音状态变化
Events.VOLUME'volume'(sound, value)音量变化
Events.RATE'rate'(sound, value)速率变化
Events.DETUNE'detune'(sound, value)音高偏移变化
Events.SEEK'seek'(sound, value)跳转位置变化
Events.PAN'pan'(sound, value)声像值变化
Events.DESTROY'destroy'(sound)声音被销毁
SoundManager 事件(在 this.sound 上触发)
事件常量字符串值回调参数触发时机
Events.PAUSE_ALL'pauseall'(manager)调用 pauseAll()
Events.RESUME_ALL'resumeall'(manager)调用 resumeAll()
Events.STOP_ALL'stopall'(manager)调用 stopAll()
Events.GLOBAL_MUTE'globalmute'(manager, value)全局静音变化
Events.GLOBAL_VOLUME'globalvolume'(manager, value)全局音量变化
Events.GLOBAL_RATE'globalrate'(manager, value)全局速率变化
Events.GLOBAL_DETUNE'globaldetune'(manager, value)全局音高偏移变化
Events.UNLOCKED'unlocked'(manager)用户交互后音频系统解锁
Events.DECODED'decoded'(key)单个音频键解码完成(WebAudio)
Events.DECODED_ALL'decodedall'()所有排队音频解码完成(WebAudio)
// Example: listen for completion
const sfx = this.sound.add('bang');
sfx.on('complete', (sound) => {
    console.log(sound.key, 'finished');
});
sfx.play();

API 快速参考

BaseSoundManager(this.sound)

方法: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)。

属性:volume(0-1)、mute(boolean)、rate(number)、detune(-1200 到 1200)、pauseOnBlur(boolean,默认 true)、locked(boolean,只读)、listenerPosition(Vector2)、sounds(数组,私有)。

BaseSound(声音实例)

方法: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)。

属性:volume(0-1)、mute(boolean)、rate(number)、detune(number)、seek(秒)、loop(boolean)、pan(-1 到 1)、isPlaying(只读)、isPaused(只读)、duration(秒)、totalDuration(秒)、key(string)、x / y(空间位置,仅 WebAudio)。

所有 set* 方法均返回 this 以支持链式调用。

注意事项

浏览器自动播放策略

浏览器会阻止音频,直到用户进行交互。Phaser 会自动处理这一点:

  • WebAudio:AudioContext 启动时处于挂起状态。Phaser 会监听 document.body 上的 touchstart/touchend/mousedown/mouseup/keydown,以调用 context.resume()。在解锁之前,locked 属性为 true;解决后会触发 UNLOCKED 事件。
  • HTML5 Audio:被锁定的音频标签会将所有操作排队,直到第一次触摸时重新执行它们。

你不需要手动处理解锁。要了解何时准备就绪,请监听 UNLOCKED:

if (this.sound.locked) {
    this.sound.once('unlocked', () => {
        this.sound.play('bgm');
    });
} else {
    this.sound.play('bgm');
}
音频格式支持

没有一种格式能在所有地方都正常工作。请提供多种格式:this.load.audio('bgm', ['assets/bgm.ogg', 'assets/bgm.mp3'])。MP3 的支持最广泛。OGG Vorbis 不支持 Safari。AAC/M4A 在 Safari/iOS 上工作良好。WebM/Opus 音质出色,但对较旧浏览器的支持有限。

HTML5 Audio 同时播放

HTML5 Audio 使用一个 <audio> 标签池。加载时指定 instances 以实现同时播放:this.load.audio('shot', 'assets/shot.mp3', { instances: 4 })。默认值为 1。如果所有标签都在使用中,且 manager.override 为 true(默认值),则进度最多的声音会被抢占。WebAudio 没有这种限制。

iOS/Safari 特定事项
  • iOS/Safari 不支持 StereoPannerNode,因此 pan 没有可听效果(事件仍会触发)。
  • iOS 17/18+ 可以在后台中断音频。Phaser 通过在 VISIBLE 游戏事件上调用 context.suspend()/context.resume() 来处理这一点。
  • setListenerPosition() 和空间音频仅适用于 WebAudio。
WebAudio 上下文复用

对于无需整页刷新即可重新创建游戏的 SPA,请在游戏配置中传入 audio: { context: existingAudioContext }。你还可以在运行时通过 this.sound.setAudioContext(newContext) 交换上下文(仅限 WebAudio)。

Sound Manager 是共享的(全局)

每个游戏只有一个 SoundManager,而不是每个场景一个。每个场景中的 this.sound 都引用同一个管理器。场景关闭时不会自动清理声音——如有需要,你必须自行停止/移除它们。除非显式停止,否则循环声音会在场景切换期间继续播放。

即发即弃与持久声音

this.sound.play(key) 会创建一个在完成后自动销毁的声音——调用 play 后你无法再控制它。当你需要持久引用来暂停、停止、调整音量或监听事件时,请使用 this.sound.add(key)。

空间音频仅适用于 WebAudio

使用 HTML5 Audio 时,空间音频的 source 配置会被静默忽略。如果你的游戏必须支持 HTML5 Audio 回退,请不要依赖空间定位来实现对游戏玩法至关重要的音频提示。

Web Audio Analyser(仅限 WebAudio)

访问底层的 AudioContext 以创建用于频率/波形可视化的 AnalyserNode:

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

这仅适用于 WebAudioSoundManager。使用前请检查 this.sound.context 是否存在。

源文件映射

文件用途
src/sound/SoundManagerCreator.js工厂:选择 WebAudio、HTML5 或 NoAudio 管理器
src/sound/BaseSoundManager.js基础管理器:添加、播放、获取、全部停止/暂停/恢复、全局音量/速率/失谐
src/sound/BaseSound.js基础声音:播放/暂停/恢复/停止、标记、配置、calculateRate
src/sound/webaudio/WebAudioSoundManager.jsWebAudio 管理器:AudioContext、增益节点、解锁、空间监听器、decodeAudio
src/sound/webaudio/WebAudioSound.jsWebAudio 声音:缓冲区源、空间/声像节点、跳转、全部属性
src/sound/html5/HTML5AudioSoundManager.jsHTML5 管理器:audio 标签池化、锁定队列、覆盖
src/sound/html5/HTML5AudioSound.jsHTML5 声音:基于标签的播放、有限的功能集
src/sound/noaudio/NoAudioSoundManager.js适用于无音频环境的空操作管理器
src/sound/events/所有声音事件常量(PLAY、STOP、COMPLETE 等)
src/sound/typedefs/SoundConfig.jsSoundConfig 类型定义
src/sound/typedefs/SoundMarker.jsSoundMarker 类型定义
src/sound/typedefs/SpatialSoundConfig.jsSpatialSoundConfig 类型定义
src/sound/typedefs/AudioSpriteSound.jsAudioSpriteSound 类型定义

评论

支持 Markdown