マガジン

ブラウザゲームの音が出ないとき:最初の操作と音の開始条件

遊びをつくる

執筆にClaudeを使用しています。

大きなボタンに指が触れ、スピーカーへ金色の音の波が広がる立体イラスト
記事のテーマをもとに制作したAI生成イラスト。

ゲームを公開したのに「音が鳴らない」という報告が届いた経験はないだろうか。コードに誤りがなくても、スマホやiframeで埋め込んだときに音が出ないことがある。原因はほぼ一つ――ブラウザが音を自動で鳴らすことを制限しているからだ。この記事では、その仕組みと対処の手順を整理する。なお本文はClaudeが執筆している。人間による取材・実機検証・独立した人的校閲は行っていない。

この記事の目次
  1. なぜ音が出ないのか:自動再生制限の仕組み
  2. AudioContextの状態を確認する
  3. 「最初の操作」でつなぐ:実装パターン
  4. iframeと埋め込み・モバイルブラウザの注意点
  5. <audio>要素を使っている場合
  6. 音が出ないときの確認手順
  7. 音が出る設計:ユーザー体験との両立

なぜ音が出ないのか:自動再生制限の仕組み

主要なブラウザ(Chrome、Safari、Firefox)はいずれも、ページが読み込まれた直後に音声を自動で再生しないよう制限している。この方針は「自動再生ポリシー(Autoplay Policy)」と呼ばれ、ユーザーが意図せず音を出されることを防ぐために設けられた。

Web Audio APIを使う場合、音の出力口となる AudioContext をJavaScriptで生成すると、その時点でブラウザはコンテキストを suspended(一時停止)状態 に置く。suspended状態のままでは音源を鳴らすことができない。再生を始めるには、ユーザーの操作(クリック・タップなど)をきっかけにして audioCtx.resume() を呼び出し、コンテキストをrunning状態へ移行させる必要がある。

HTMLの <audio> や <video> 要素でも同じ制限が働く。play() メソッドはPromiseを返し、自動再生が拒否されたときはそのPromiseがrejectされる。エラーを捕捉せずにいると、コンソールに未処理の拒否が積まれてゲームが意図通りに動かなくなる原因になり得る。

AudioContextの状態を確認する

まず手元の実装が今どの状態にあるかを確認しよう。audioCtx.state プロパティには次の4つの値が入る。

  • "suspended":ユーザー操作の外でAudioContextが生成されたか、明示的に一時停止された状態。
  • "running":音声を出力できる通常動作中の状態。
  • "interrupted":電話アプリが音声を占有するなど、ウェブアプリの外部から中断された状態。resume()を呼ぶことで復帰を試みられる。
  • "closed":close()で閉じられた状態。再利用はできない。

音が出ない場面でよく遭遇するのは "suspended" だが、モバイルで電話を受けたあとなど外部割り込みが入ると "interrupted" になることもある。どちらの場合も resume() で復帰を試みることができる。ブラウザの開発者ツールのコンソールに console.log(audioCtx.state) を仕込んでページを読み込むと、現在の状態を直接確認できる。

javascript
// 説明用コード例(動作保証なし)
const audioCtx = new AudioContext();
console.log(audioCtx.state); // 多くの場合 "suspended"

document.addEventListener('click', () => {
  if (audioCtx.state === 'suspended' || audioCtx.state === 'interrupted') {
    audioCtx.resume().then(() => {
      console.log('AudioContext:', audioCtx.state);
    });
  }
}, { once: true });

上のコードは仕組みを示す説明用の簡略例であり、実際のゲームに組み込む際はエラー処理や既存の音声管理の構造に合わせて調整が必要になる。

ユーザーのクリックイベントハンドラー内で resume() を呼ぶと、状態はrunningに変わり、以降の音源再生が可能になる。{ once: true } オプションを付けると、一度だけ発火するリスナーになる。ゲームに「タップしてスタート」などの画面を設けているなら、その操作のタイミングでresumeを呼ぶのが自然な設計だ。

「最初の操作」でつなぐ:実装パターン

音を使うゲームでよく採用されるパターンが、ゲーム開始前に操作を要求する画面(スタートスクリーン)を設けることだ。ユーザーが「はじめる」ボタンを押すその瞬間に、AudioContextのresumeと最初の音声バッファの読み込みをまとめて行う。

パターン1:スタートボタンでAudioContextをunlock

javascript
// 説明用コード例
let audioCtx;

startButton.addEventListener('click', async () => {
  audioCtx = new AudioContext();
  // ユーザー操作のコールバック内でnew AudioContext()を呼ぶと
  // すでにrunning状態で生成されることが多い
  await loadSounds(audioCtx); // 音源の読み込み処理
  startGame();
});

AudioContextをユーザー操作のコールバック内で生成すると、多くのブラウザではそのまま "running" 状態で起動する(Chrome Autoplay Policyドキュメントで言及)。ページ読み込み時に生成する場合は、最初のユーザー操作で resume() を呼ぶ方が確実だ。

パターン2:既存のAudioContextを後からresumeする

javascript
// 説明用コード例
// ゲームエンジンや音声ライブラリが内部でAudioContextを持つ場合
function unlockAudio(engine) {
  const ctx = engine.getAudioContext(); // エンジン依存のAPI
  if (ctx && (ctx.state === 'suspended' || ctx.state === 'interrupted')) {
    ctx.resume();
  }
}

Howler.jsやPixi Soundなどのライブラリは内部でAudioContextを管理しているため、ライブラリ固有のunlock APIや設定フラグがあることが多い。使用しているライブラリがある場合は、そのドキュメントで自動再生制限への対応方法を確認することを先に行うとよい。

iframeと埋め込み・モバイルブラウザの注意点

ゲームを別のページに埋め込む場合、iframeにはデフォルトで自動再生の権限が与えられないことがある。Chromeのautoplayドキュメントによれば、同一オリジンのiframeはデフォルトで自動再生が許可されるが、クロスオリジンのiframeには明示的な権限委譲が必要だ。属性として allow="autoplay" を指定することで、埋め込み先のページから権限を委譲できる。

html
<!-- iframeの埋め込み例(説明用)-->
<iframe
  src="https://example.com/game/"
  allow="autoplay"
  width="800"
  height="600">
</iframe>

この属性があっても、iframe内のゲーム自体がユーザー操作をトリガーとして音を開始する設計でなければ、音は鳴らないままだ。「allow属性で許可する」と「ユーザー操作をゲーム側で受け取る」は別の話である。

アソバースのような投稿プラットフォームでは、ゲームがiframeで表示されることが一般的だ。ゲーム内に「タップしてスタート」「クリックで開始」などの操作受付を設ける設計が、どの環境でも音を確実に出すための基本になる。

モバイルブラウザでの推奨事項

iOS Safariを含むモバイルブラウザは、ユーザーの直接操作なしに音の再生を拒否することが多い。PCブラウザで音が出ていてもスマホで出ないケースはこれにあたる。resume() は、ユーザー操作イベントのコールバック内で直接呼ぶことが推奨される。使用するライブラリやエンジンのドキュメントでモバイル向けの注意事項を確認し、PCとスマホの両方で動作を確認することが望ましい。

<audio>要素を使っている場合

Web Audio APIではなく <audio> 要素で音を鳴らしているゲームも同じ制限を受ける。play() はPromiseを返すので、拒否時の処理を書いておかないとエラーがサイレントに積まれてゲームの挙動がおかしくなることがある。

javascript
// 説明用コード例
const sound = document.getElementById('bgm');

startButton.addEventListener('click', () => {
  sound.play().catch((err) => {
    // 再生できなかった場合の処理
    console.warn('再生を開始できませんでした:', err);
  });
});

自動再生が目的でなく、単に「ボタンを押したら音楽が流れる」という設計であれば、play() はユーザー操作のコールバック内で呼べばよい。それだけで多くのケースは解決する。

音が出ないときの確認手順

症状を再現してから次の順で確認すると、原因を絞りやすい。

  1. AudioContextの状態を確認する:コンソールで audioCtx.state を出力する。"suspended" または "interrupted" なら resume の呼び出し場所を見直す。
  2. resume() の呼び出し位置を確認する:ユーザー操作イベントのコールバック内で直接呼んでいるかを確認する。
  3. play() のPromise拒否を確認する:<audio> 要素を使っている場合は .catch() でエラーを捕捉し、コンソールに内容を出す。
  4. iframeの allow 属性を確認する:クロスオリジンの埋め込みで動かしている場合は allow="autoplay" がiframeに付いているか確認する。
  5. スマホ実機で確認する:PCで動いてもモバイルブラウザで動かないことがある。タップイベントのコールバックでresumeを呼んでいるかを再確認する。

このチェックリストを一通り確認しても原因が特定できないときは、使用しているゲームエンジンやオーディオライブラリのドキュメントで「autoplay policy」または「user gesture」のセクションを探すと、ライブラリ固有の対処法が記載されていることが多い。

音が出る設計:ユーザー体験との両立

自動再生制限は制約ではなく、「ユーザーが操作してから音が出る」という自然な流れを設計に組み込む機会として捉えることができる。ゲームを開く→タイトル画面が出る→タップして始める、この流れに音の初期化を乗せれば、制限を意識させることなく音が機能する。

アソバースで公開されている「雨音のピアノ」(rain-piano)は、鍵盤を押す操作が音の開始条件と自然に一致している例だ。ゲームの内容と音の起動タイミングが一致しているため、ユーザーに「音を出すための別操作」を要求する必要がない。音を使うゲームを設計するときは、こうしたゲームプレイとの一体化を最初から意識すると、制限をほぼ感じさせない実装になる。

どうしてもBGMをゲーム開始直後から流したい場合も、「音量ゼロで起動して最初の操作後にフェードイン」「小さな♪ボタンを画面に置いて最初のタップを促す」といった方法で、自動再生制限と折り合いを付けることができる。大切なのは、ユーザーが意図せず音を出されないよう配慮しつつ、意図して操作した瞬間に音が機能していることだ。

出典・参考資料