見出し画像

BGMManagerで意外と必要になる機能

◆ この記事はゲームサウンド制作 Advent Calendar 2018の15日目です


@geekdrums と申します。音楽プログラマーを名乗っています。ゲーム会社でゲームエンジニア、サウンドプログラマーなどやってきました。


ところで、BGMManagerクラス、作ったことありますか?

地味だし、簡単そうに思えますよね。地味だし簡単そうだから誰も語ってないし、なんとかなると思われてそうですが、意外とめんどくさい、特にRPGなど、戦闘システムとイベントなどの演出がごちゃごちゃ混ざってくるようなものは非常に面倒くさくなります。

そこで今回は、RPG作ってる個人のゲーム制作者とか、あるいは商業でゲームエンジニアやってて、ついでにこれも、みたいな勢いでBGM管理を任されてしまったエンジニアのために、どんな事が必要になってくるのか、どう設計しておくと良いのか、簡単そうなのになんでこうなるのか、という問題を共有しておこうと思いました。


CurrentBGMの保持

BGMManagerっていうのは単にBGM管理クラスのことで、まぁ名前は何でも良いので好きにつけていただければよいのですが、要は

・現在のBGMを保持

・前のBGMを止める

・フェードイン・アウトとか一時停止・再開などを管理する

クラスのことです。

最低限の機能としては、CurrentBGM変数を1つ持って、PlayBGM関数が来たら前のBGMをフェードアウトさせれば事足ります。

青字は他システムから呼ばれる関数で、黄色字はBGMManagerが自動的に呼ぶ関数というイメージ


BGMレイヤーの定義

ある程度画面数が多くなってくると、CurrentBGMだけ保持すればいいや、みたいな甘い実装を少しだけ後悔することになります。

BGMの一時停止と再開(SuspendとResume)が欲しくなるからです。

例えばMenuBGMを鳴らしている間はFieldBGMを一時停止して、Menuを閉じたら復帰するなど、「せっかく曲のいいところまで聴いてたのに最初から鳴り直し」みたいな事を避けるために絶対必要になってきます。

このため、用途に応じてBGMのレイヤーみたいな概念を導入して、

EventBGMレイヤー
BattleBGMレイヤー
MenuBGMレイヤー
FieldBGMレイヤー

なんかを用意して、それぞれにCurrentBGMを持ち、レイヤーごとにPriorityとかを定義して、現在再生中のレイヤーが1つになるように優先度に応じて解決する、みたいな処理を書きます。

BGMManagerはBGMそのものではなくBGMレイヤーを管理するクラスになって、例えばFieldレイヤー再生中にBattleレイヤーがリクエストされたら、優先度判定してFieldレイヤーを一時停止して、BattleレイヤーのStopが呼ばれると残ったレイヤーの中で一時停止中のレイヤーの中から優先度の高いものを自動で再開、とかするわけです。

また、バトルが長ければバトル終了後のフィールド曲は最初から鳴ってもいいかな、って気もするので、再開じゃなくて頭から再生し直すオプションを用意、みたいな事も必要になってきます。

まぁ、レイヤー定義もプライオリティ制御もそんな手間ではないので、ここまではササッと終わります。


めんどくさいレイヤー制御

昨今のオープンワールドと言われるタイプのゲームだと、もうちょっと考えないといけない場合があります。フィールドの中でできることが多すぎるからです。

想像するだけで、
・クエストを受注して、クエスト進行中の専用曲が鳴る
・天候が変わったり時間が変化すると、専用のBGMが流れる
・ストリートミュージシャンが配置されており、ゲーム内での音楽が鳴る
・乗り物に乗ると、乗った時の音楽が鳴る
・ジュークボックス的な要素で、自由に曲を鳴らせる
・ミニゲームを開始すると、ミニゲームの曲が鳴る

とか色々あって、特に、これらが同時に発生する可能性がある場合、ちゃんと制御しないとえらいことになります。

簡単な例として、

1,フィールド曲が鳴っています
2,バトルに入ったのでフィールドを一時停止にしてバトル曲を鳴らします
3,乗り物に乗ってバトルから離脱しました。乗り物の曲はバトルより優先度が低いので、バトルから離れるまではバトル曲のままです。
4,バトルが終了します。

この時、乗り物に乗ったままだと乗り物の曲になってほしいわけですが、乗り物曲は優先度判定でキャンセルされています。

なので、単に再生をキャンセルするだけではなく、「再生しようとしたけど優先度判定でキャンセルされた」という状態を保存しておく必要があります。

つまり、BGMレイヤーの状態として

None(BGMがセットされてない)
Ready(BGMがセットされたが再生命令がきてない)
Playing(再生中)
Stopped(停止された)
Finished(曲が最後まで鳴って終わった)
Suspend(一時停止中だが、命令があるまで再開しなくて良い)
AutoResume(一時停止中で、自動で再開してほしい)
AutoPlay(Playがキャンセルされたので、自動で再生してほしい)

くらいのバリエーションが考えられます。


めんどくさいバグ

BGMの自動復帰をやると、「なんか一瞬変なBGMが鳴って消えます」みたいなバグがすぐ出てきます。

なぜかというと、各BGMレイヤーの管理者が好き勝手にPlay、Stopを呼んでいる状態では、「フィールドBGMが鳴り始めた0.5秒後にイベントが開始して、イベント前に一瞬変なBGMが挟まる」なんて事になり、「この後イベントってわかってるんだったら0.5秒前に教えてくれないとどうしようもない」という、単純にBGMManagerの利用者側で統率が取れてないために適切な順番で呼び出されない問題が起こるためです。

なのでとりあえずStopAll的な関数を用意したりします。ただ、そのバグを怖がってどこもかしこもStopAllを呼ばれてしまうと、せっかく制御していたレイヤーの関係性が無意味になり、今度は別の所から「BGMが復帰しなくて無音になるんだけど!?」みたいなバグが上がってくるのは目に見えています。

例えばこんな感じで、「イベント中に最初無音から途中でBGM入れたいんだよね」という事で、全部のレイヤーを消してイベント演出をしたとします。

案の定、イベント終了後にField BGMが(一時停止ではなくStopされてしまったので)鳴りません。

ここでやっと、なにか演出的な意図があって「無音にしたい」時っていうのが意外と多いな、ということに気がつくわけです。

そうすると、レイヤーに設定するBGMとして、「無音のBGM」を用意して、「無を鳴らす」みたいな事をしてレイヤー制御を活かしつつ無音を作ったりします。

何なら、無音専用のBGMレイヤーを用意してしまって、Eventレイヤー以下はEventSilentレイヤーで一括で無音にしておける、みたいな実装もありかもしれません。

……正直、ここらへんは前職でいろいろ試したものの、あっちを潰せばこっちで意図しないことが起こる、という感じで、何を自動にして何を人間が管理すればいいのか、いまいち最適解が掴めていないところです。もっと経験のある方がおりましたら知見を共有していただきたく。。


フェード時間を設定する

フェード時間は大切です。フェード時間は大切です。大事なんで何度でも言います。フェード時間に気を使ってください。一律で1秒とかでいっか♪とか言って最初にStopBGM(fadeTime = 1.0f)などとデフォルト引数を設定しても何ら解決はしません。

ひとまず、レイヤーごとにデフォルトのフェード時間を設定したりします。フィールド曲はゆっくりフェード、とかしたいですからね。

でも、やっぱりというか当然というか状況によってフェード時間を変えたい時が来ます。例えばポーズした時。ポーズは一瞬で止まって一瞬で復帰してほしいので、どのレイヤーでも関係なく0.1秒くらいで消えてほしいです。

また、同じバトルBGMでも、次に発生するイベントによってフェード時間を変えたい事もままあります。いつもは1秒フェードで問題なくても、バトル中に神妙なイベントが始まって、ボスさんが「実は俺~」とか謎の語りを始める時は3,4秒でもったいつけてフェードしてほしかったりします。

こういう時のために、全体を指定したフェード時間で停止したりとか、レイヤーのフェード時間を一時的に上書きする、みたいな機能が必要になってきます。上書きする場合、デフォルトに戻す処理も必要になったりして、呼び忘れバグの温床にもなるので気をつけなければいけません。


同じ曲を繋ぐ

BGM演出で一番ダサいのが、「同じ曲が繰り返しイントロから鳴り直す」事です。商業ゲームでもたまーーーにありますが、例外なくダサいです。なぜでしょう、理由を言語化するのが難しいんですが、どんなに良い曲も、すぐにイントロから鳴り直すのは本当にダサく聞こえます。やめましょう。

実装は簡単で、前の曲と新しい曲が同じなら止めずに流しっぱなしにする、レイヤーが違ったらBGMインスタンスを別レイヤーに移す、みたいな処理を作ります。

何かやんごとなき理由(BGMと同期した演出組んでるから最初から鳴ってほしい)とかはあるかもしれませんが、そもそも、同じBGMが連続してしまう事自体がBGM設定上の不具合と言って差し支えないので、そういう要望は無視して、同じBGMは自動的に引き継がれる実装で良い気がしています。


ディレイを作る

フェード時間と同じかそれ以上に重要なのがディレイです。曲をクロスフェードする、と言っても、本当に前の曲のフェード開始に次の曲のど頭がかぶってしまっていたら、せっかくのカッコいいイントロが前のBGMに潰されて勿体無いことになります。連続した変化ならディレイ0で繋げても良いのですが、シーンの切り替わりの時とか、ちゃんとイントロを聞かせたいボス曲とかで、ちゃんとディレイを入れて無音になってから曲を開始させると、その曲の印象がとっても良くなります。

フェードタイムの調整と合わせて利用すると、ここぞという場面で大きな差が出ます。


ストリーミング管理

可能ならここらへんはミドルウェアなり何なりにお任せしたいところなんですが、降り掛かってくる場合があります。環境によって頑張って対応するだけなので、頑張れとしか言えません。頑張ってください。


インタラクティブミュージックとの併用

これもまたミドルウェアに任せてしまいたい部分なのですが、A曲からB曲に変えるのと、A曲の中で縦の遷移、横の遷移を行うのは呼び出す関数からして違うので、そこらへんがミドルウェア内で1レイヤー抽象化されてない限りは、ゲーム側で呼び分ける必要があります。

例えば、いつもはフィールドとバトルでA,Bと違う曲を流していたけど、ここだけ縦の遷移でやりたい!となった時に、フィールドで鳴っているAのインスタンスをBに渡してそこで遷移を呼ぶ、みたいな処理です。


音楽演出のプログラムが”地味に”めんどくさい理由

ここまでフル機能作ったらあとはプランナーとかサウンドデザイナーがいい感じに演出してくれるでしょう、と思いきや、「意図しない曲が一瞬挟まる」とか「意図しない曲が鳴りっぱなし」とか「意図せず無音になる」とかいうバグが頻出し、原因究明に追われたりします。

音楽演出のプログラムは、3つの理由でめんどくさい事が起こりがちです。

1,時系列データが必要

サウンド系のデバッグあるあるですが、音というのは何かが起こったフレームで止めても何にもわからないので、その状況に至るまでの履歴を時系列で出力するデバッグ機能が絶対必要です。これは用意しておきましょう。BGMManagerの呼び出し関数にログを仕込むだけでも十分です。

2,いろんな人が関わる

バグの原因として最も多いのが、設定の人為的ミスだったりします。
イベントの演出担当者が他のレイヤーを勝手に全部消してしまっていたとか、最優先のレイヤーが勝手に使われすぎてバッティングしたりとか、ある担当者が設定してうまく行っていた制御が、別のレベルデザイナーが親切心でリトライポイントを増やしたせいで想定していなかった進行が発生し、リトライ時に適切に再開されなかった……などなど。

こういうのを、ちゃんと責任持って全部見る人がいれば良いのだと思いますが、BGM演出はレベルごと、システムごとに担当者が別れがちで、それゆえに適切なレイヤー管理とか関数呼び出しのルールが徹底されない事で、デバッグ時にサウンド班が地獄を見る、なんてことになります。

BGM管理のために群がる様々な担当者たちの図……っぽい何か。いらすとや様より。

ここはちゃんと責任を持てる担当者を入れるかルールをドキュメント化して、どんな時には何のレイヤーを使うのか、いつ再生、停止すべきかなどを共有しておくのが良いでしょう。

3,未来の情報が必要

あるタイミングで何を鳴らし始めるのが良いか、というのは、その次のシーンが何なのか(あるいは、そこに至るまでのシーンがどのようなものだったのか)という情報が必要になってきます。

例えば、暗転が始まると全レイヤーを停止する、という処理を入れていたとしても、暗転の後でさらにイベントが続く、という場合には敢えてBGMを止めずにそのままにしておきたかったり、あるいは、フィールド曲はいつも0.5秒でフェードアウト、という処理にしていたとしても、次のシーンが非常にシリアスな雰囲気の場合、敢えてフェード時間を長めに取っておきたかったり、その「次のシーン」に入ってからではもう遅いので、「次はこんなシーンだから今のBGMはこうしてください」という情報が事前に必要になってきます。そして、そういう汎用化された処理に例外を入れたり関数の根本的な呼び出し元の方からその理由となる情報を受け渡したりするのは、プログラム的には地味~なコストになってきます。


まとめ

BGM管理は、小規模なら何も困ることがないのでナメてかかりがちですが、大規模になるにつれ、大人数になるにつれて面倒くささとか、担当者全員に制御を理解してもらうコストとかがかかってきます。誰がどのレイヤーを責任もって管理するのか?などを事前にルール付けておくだけでも、混乱を避けやすいので、あとで大変なことになる前に準備しておくことが最終的なコスト削減につながると思います。

また、今回は技術的な話を中心にしましたが、そもそも「ここでどんな曲を鳴らすべきなのか?」という、「選曲」についても考えられる方法論はたくさんあります。「BGMをつける」という作業はレアな分考える機会も少なく、いざ「ここにどんな曲が欲しいですか?」と言われても、すぐに答えられる企画者は多くないのではないでしょうか。

素直に悲しいシーンに悲しい曲を当てるだけではなく、人物の感情にフォーカスするのかシーンの雰囲気を表現するのか?あえて雰囲気の違う曲をぶつけて演出するのか?モチーフで何かを暗示させるのか?無音や余韻を使った細かな演出も武器になります。

この機会に、自分の好きなアニメや映画の音楽演出を聴いて研究してみると面白いと思いますので、是非一度、気に留めてみてください。



より没入感の高い演出のための「インタラクティブミュージック」の技法については、こちらの連載でいろいろまとめています。ご参考にどうぞ。

著者のTwitterはこちら。ゲームの音楽演出について研究・解説したりゲームを作ったりしています。


サポートいただけたら、連載への励みになります!