VANNAN GAMES.

← 制作ノート一覧

ブラウザゲームの音が鳴らない問題、全部踏んだので地図を描く

サイト全体

このサイトのゲームで、「音が鳴らない」という報告を何度も受けました。そのたびに原因が違いました。ブラウザで音を鳴らすのは、思っていたより何倍も地雷原です。同じ道を歩く人のために、踏んだ罠の全体像を描いておきます。

罠1: 自動再生ブロック

ページを開いた直後にBGMのplay()を呼んでも、ユーザーが一度も操作していないとブラウザは再生を拒否します。厄介なのは拒否のされ方で、エラーは投げられずPromiseが静かにrejectされるだけ。catchで握りつぶしていると、何も起きずにただ無音です。

さらにうちのBGM関数は「同じ曲が指定されたら何もしない」最適化をしていたので、最初の再生が拒否されると、曲が切り替わるまでずっと無音のままでした。症状は「たまに最初だけBGMが鳴らない」。たまに、なのはブラウザがサイトの利用実績によって自動再生を許可したりしなかったりするからです。再現しないバグの見本のような挙動でした。対策は、最初のタップ時に「意図した曲が止まったままなら鳴らし直す」リトライを仕込むこと。

罠2: iOSのマナーモード

これが一番意地悪でした。iPhoneの消音スイッチがONのとき、HTMLのAudio要素で再生する音は鳴るのに、Web Audio APIで合成した音だけが消音されます。うちはBGMがファイル再生、効果音がWebAudio合成だったので、「BGMは聞こえるのにSEだけ鳴らない」という、コードを何度見ても原因の分からない状態になりました。audioSessionを再生用途として宣言すると回避できる端末もありますが、対応はSafari 16.4以降で、確実ではありません。

罠3: Audio要素ごとの「解錠」

ではSEもファイル再生にすれば解決かというと、もうひとつ罠があります。iOSはAudio要素ごとに「ユーザー操作の中で一度再生した実績」を要求します。BGMはタップ起点で再生されるので自然に解錠されますが、効果音は戦闘処理などの非同期な流れの中でplay()されるため、一度も解錠されないまま延々と拒否され続けます。対策は、最初のタップ時に全ての音要素を無音で一瞬再生してすぐ止めること。これで以後はプログラムからいつでも鳴らせます。

結論: 効果音もぜんぶファイルにした

WebAudio合成には「音源ファイル不要で軽い」「コードで音を作れる」という良さがあり、当初は全ゲームで採用していました。しかし罠2のような端末依存の消音は、こちらのコードでは制御しきれません。最終的に方針を転換し、効果音も全てWAVファイルにしてBGMと同じ経路で再生することにしました。合成はあきらめず、Pythonで波形を生成してファイルに焼くスクリプトを作ったので、「コードで音を作る」楽しさは残っています。ビープ音15種で合計400KB。この軽さなら割に合います。

いまは新しいゲームを作るとき、①ファイル再生に統一、②初回タップでのBGM再生リトライ、③audioSession宣言、④全要素の解錠、の4点セットを最初から入れています。

おまけ: 実機からの報告を切り分ける質問

「音が鳴らない」と言われたとき、どの罠かを切り分ける質問を用意しておくと早いです。①BGMは鳴っていますか——鳴っていなければ自動再生ブロックかミュート設定。②ボタンを押したときのピコピコ音は鳴りますか——BGMだけ鳴るならマナーモード(罠2)の可能性が高い。③特定の効果音だけ鳴りませんか——それは解錠漏れ(罠3)です。うちでは3つの罠を全部この順で踏んだので、質問リストの精度には自信があります。

もうひとつ、音を直すときに一緒に見直したいのがミックスです。効果音が「鳴っているのに聞こえない」ことがあり、これは罠ではなく音量バランスの問題です。BGMを基準音量より下げる、派手な演出の間だけBGMを一時的に絞る、といった引き算で効果音は立ちます。技術の問題と聞こえ方の問題を分けて考える、が音まわりの基本でした。

教訓

「PCのChromeで鳴っている」は、何の保証にもなりません。音の不具合は再生経路×端末×設定の組み合わせで起きるので、コードレビューでは見つからず、実機の報告だけが頼りです。「鳴らない」という短い報告の裏に3種類の別々の原因があった、というのが今回いちばんの学びでした。