mcpField
P
phaser-audio

phaser-audio

github.com/yakoub-ai開発29194 回インストール9 週間前に更新
リポジトリ

phaser-audio について

phaser-audioは、Phaser 4でブラウザーゲームの音声を実装するためのスキルです。バックエンドの選択から、読み込み、再生、ライフサイクル管理までの実践パターンを解説します。

主な機能

  1. 01Web AudioとHTML5 Audioの指針
  2. 02MP3とOGGのアセット読み込み
  3. 03音楽と単発効果音のパターン
  4. 04サウンドプールとオーディオスプライト
  5. 05音量、ロック解除、復帰処理
  6. 06シーンの後処理と音楽の遷移

活用シーン

  1. 01音声バックエンドと対応形式の選択
  2. 02ループ音楽と単発効果音の追加
  3. 03頻繁に鳴る効果音への対応
  4. 04タブ復帰後の音声再開

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/yakoub-ai/phaser4-gamedev --skill phaser-audio

インストール後、Claude Code で/phaser-audioを入力して読み込まれたか確認します。

同梱ファイルreferences/audio-api.md

詳細

Phaser 4 オーディオ

Phaser 4 はすべてのオーディオ再生において Web Audio API をラップしています。各オーディオバックエンドがいつ適用されるかを把握し、以下に示す読み込み・再生のパターンに従うことで、最もよくあるオーディオのバグを回避できます。

Web Audio と HTML5 Audio

Phaser は、対応しているすべてのブラウザー(すべてのモダンブラウザー)でデフォルトで Web Audio を使用します。Web Audio は専用スレッドでオーディオ処理を実行し、空間オーディオ、エフェクトチェーン、精密なスケジューリングをサポートし、HTML5 Audio を悩ませる単一トラック制限の影響を一切受けません。

HTML5 Audio は、AudioContext が利用できない場合の自動フォールバックです。典型的には古い Android WebView や、一部のエッジケースのブラウザー構成が該当します。意図的にこれをターゲットにすることはほとんどありません。強制的に使用する必要がある場合は次のとおりです。

const config: Phaser.Types.Core.GameConfig = {
  audio: {
    disableWebAudio: true,   // force HTML5 Audio fallback
  },
};

特定の互換性要件がない限り、決して HTML5 Audio を強制しないでください。 単一の同時トラック、detune/rate 非対応、空間配置非対応といったその制限が、設計を縛ることになります。

オーディオの読み込み: 常に mp3 と ogg の両方を用意する

ブラウザーは単一のオーディオコーデックについて一致しません。Safari と iOS は mp3 を必要とします。Firefox は ogg/vorbis を好みます。Chrome はどちらも受け入れます。両方のフォーマットを用意し、ブラウザーが対応している方を Phaser に選ばせてください。

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

単一のフォーマットだけを読み込まないでください。Chrome の開発マシンでは完璧に動作するゲームも、.ogg しか出荷していなければ Safari/iOS では完全に無音になります。

サウンドの追加と再生

this.sound.add() — 完全な制御

サウンドを制御するための参照が必要な場合(一時停止、再開、シーク、音量調整、イベントの監視)は add() を使用します。

create(): void {
  const music = this.sound.add('music-main', {
    loop: true,
    volume: 0.6,
  });
  music.play();
  this.music = music;
}
this.sound.play() — 単発のファイア・アンド・フォーゲット

一度だけ再生し、その後参照する必要がないサウンドにはマネージャーの play() を使用します。Phaser がライフタイムを自動的に管理します。

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

経験則: BGM → add()。単発の SFX → sound.play()。

SoundConfig のオプション

SoundConfig オブジェクトを add() の第 2 引数として、または play() の第 2 引数として渡します。

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

BGM

BGM は連続的にループし、音量制御が必要で、シーン終了時に停止しなければなりません。

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

SFX のパターン: 単発サウンド

コインの取得、銃声、UI クリックなど、再生しっぱなしにするサウンドエフェクトには this.sound.play() を直接使用します。参照を保持しないでください。

// 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 は内部インスタンスを生成し、最後まで再生してから破棄します。クリーンアップは一切不要です。

高速な SFX のためのサウンドプーリング

1 秒間に何度も再生されるサウンド(銃声、足音、素早い UI フィードバック)では、単一のインスタンスだと途切れが聞こえます。新しい play() 呼び出しのたびに同じサウンドが最初から再スタートするためです。

インスタンスのプールを事前に生成し、ラウンドロビンで順に使用します。

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;
}

プールサイズの目安: 想定される最大の重複インスタンス数に合わせます。足音なら通常 3〜4 で十分です。

オーディオスプライト

オーディオスプライトは、複数の短いサウンドを 1 つのオーディオファイルと JSON マーカーファイルにまとめます。これにより HTTP リクエストが削減され、オーディオの読み込みが遅いモバイルに最適です。

読み込み
// 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
);
再生
// 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 フォーマット
{
  "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 を参照してください。

音量管理

// 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;
ミュートボタンのパターン
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');
  });
}

モバイルでのオーディオのアンロック

モバイルブラウザーと一部のデスクトップブラウザーは、ユーザーがページを操作するまでオーディオ再生をブロックします。これはブラウザーレベルで強制されるもので、回避策はありません。

Phaser はこれを自動的に処理します。 最初の pointerdown または keydown イベントを監視し、その時点で AudioContext を再開します。それより前にキューに入れられたすべてのサウンドは、アンロック直後に再生が始まります。

オーディオがロックされているかを確認します。

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

オーディオが重要なゲームでのベストプラクティス: 全画面の「タップしてスタート」オーバーレイを表示します。プレイヤーがそれをタップしたら、それを閉じます。Phaser の内部アンロックが同時に発火するため、次の play() 呼び出しでオーディオが開始します。

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 の一時停止からの復帰

モバイルブラウザ(特に iOS Safari)や一部のデスクトップブラウザでは、タブがフォーカスを失ったとき、デバイスがスリープしたとき、または PWA がバックグラウンドに移行したときに AudioContext を一時停止します。初回の自動再生ロックとは異なり、再開は自動では行われません — Phaser はタブが再フォーカスされても一時停止されたコンテキストを復元しません。

症状: ユーザーがタブを切り替えて戻ると、音楽が突然停止します。コンソールにエラーは出ません。this.sound.locked は false(コンテキストは以前にロック解除されている)ですが、音声は依然として無音です。

修正 — 可視状態の変更時にコンテキストを再開する:

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

代替案 — Phaser 組み込みのフォーカスイベントを使用する:

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'完全に閉じられている — 新しいゲームインスタンスを作成してください

シーン間での音楽のクロスフェード

突然の音楽の切断は素人っぽく聞こえます。退出するシーンで古いトラックをフェードアウトし、入ってくるシーンで新しいトラックをフェードインしてください。

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

注: this.tweens は sound.volume を含む任意のオブジェクトの任意の数値プロパティをトゥイーンできます。特別なオーディオトゥイーン API は必要ありません。

シーンシャットダウン時のサウンド停止

シーンがシャットダウンするときは、必ずオーディオをクリーンアップしてください。そうしないと、前のシーンのサウンドが無期限に再生され続けます。

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

stopAll() はトップレベルのゲーム終了時、または完全に無関係なゲーム状態間でのみ使用してください。シーン遷移では、現在のシーンが所有する特定のサウンドのみを停止してください。

追加リソース

リファレンスファイル
  • references/audio-api.md — SoundManager、BaseSound、WebAudioSound の完全な API リファレンス、すべてのイベント、SoundConfig フィールド、AudioSprite JSON スキーマ

コメント