Unityでインディゲームを作る!

Unityでのゲーム制作を目指し、それに関わる話題についてのブログ

Unityでのオープン・ワールドにおけるBGMの自然な切り替えとFMODについて

 オープンワールド・ゲームにおいて、BGMを自然に切り替える、クロスフェードする方法をUnity上で再現してみたので、その経緯と結果をまとめます。そして、自分なりにやってみた後で、FMODという画期的なオーディオ・ソフトウェアに出会ったので、それについても書いてみます。

 

 オープン・ワールドという概念の定義については様々な議論がありますが、ここでは『境目のないフィールドが広がり、ロードを挟むことなく各地のロケーション、村やダンジョンなどの建造物へとシームレスに出入りできるマップデザイン』、とします。なので、厳密にはオープン・フィールドという言葉の方が適しているのかもしれません。

 

自然な移り変わり

 ゲームにおける音楽の重要性について、今更ながら語ることはないでしょう。しかし、昨今のゲームにおいては、そのオープンな構造や自由な進行、状況変化に対応できる、柔軟なオーディオ・コントロールが求められます。

 例えば、オープンフィールドにおいては、全体のフィールドと各地のロケーションに隔たりがないので、もし各エリアごとに専用BGMがあるなら、二つの音楽を自然に切り替えなければいけません。反対にステージ制、エリア制なら、単純にそのレベルをロードし終えた後で、BGMを再生さえすればいいことになります。

 

 実際、最近のゲームのほとんどがこれに対応しています。村に入ったら、村の人々の声が聞こえるようになり、音楽が鳴りだします。不気味なエリアに入ると不気味な音楽に自然に切り替わり、水の中に入ると、フィールド音楽とシンクロする水中版アレンジに瞬時に切り替わる、というのもあります。

 そうした状況や変化に柔軟に対応する、インタラクティヴな音楽表現には前々から興味がありました。そして、そのような機能を持った多くのゲームを自分でもやってきて、これはどういう風にやっているんだろうと感じていたので、チャレンジしたというわけです。

 

エリアをコライダーで囲む 

 四方に広がるメインフィールドにポツンと村や城、あるいは敵のアジトがあるかもしれません。そのロケーションを円形のコライダーで囲みます。今回はカプセル・コライダー(Capsule Collider)をトリガーモードで使用しています。円形であることで、どの方向から入っても同質の処理が出来るし、ある程度高さのある建物にも対応したかったからです。

 まず、プレイヤーがそのエリア内にいるということを検出します。 下図は、その状態をフィールドの真上から見た図です。

f:id:miur-us:20190204234321p:plain

真上からの図

 単純な音楽の切り替えということなら、 このコライダーがプレイヤーを検出した瞬間に、メインの音楽を止めてエリア音楽をスタートさせればいいことになります。しかし、それだと不自然なブツ切りになってしまいます。理想としては、二つの音楽が鳴り続けながら、プレイヤーが進むにつれて音量がスムーズに切り替わっていく、という動作を目指します。 

 

プレイヤーの位置(割合)を計算する

 自分が考えた方法は、そのコライダー内に音楽を切り替える区域を設定し、プレイヤーがその区間どこにいるかで、メイン音楽とエリア音楽の音量バランスを変える、というものです。

f:id:miur-us:20190204234538p:plain

プレイヤーの位置から、音量バランスを決める

 上図の赤い帯部分がそうです。ここを抜けて、完全にエリア内(図では白い円の中)に入ってしまえばエリア内の音楽に完全に切り替わることになります。

 

プレイヤーが赤い帯内にいる時に、どの位置にいるのかの割合を計算します。  

f:id:miur-us:20190204234452p:plain

円の中心に対する距離から計算する

 二次元上の円の中心に対する距離なので、3Dゲームの場合はエリアを真上から見下ろした二次元で考える必要があります。つまり、Y軸の高さを除外した、X軸とZ軸による二次元平面におけるプレイヤーの座標を計算するということです。

 なので、Vector3からVector2に変換します。

Vector2 playerPos2D = new Vector2(player.pos.x, player.pos.z );

 という感じで、2DだとY軸は縦ですが、そこに3Dにおける奥行きであるZ軸の値を代入します。すると図のような真上から見たときの二次元平面でのプレイヤーの座標が得られます。

f:id:miur-us:20190204235448p:plain

3D座標を2Dに変換し、相対的な位置を割り出す

  まずは赤帯部分の長さです。コライダーの円の半径と内円の半径の差で出せます。図だと黄色線ピンク線の差ですね。そして、プレイヤーの赤帯 部分内における相対的な位置は、プレイヤーと円の中心までの長さ(水色線)からピンク線の長さを引いて、赤帯の長さで割ればいいです。

 そうすれば内円から測って、赤帯内の何%の位置に立っているかを計算できます。

f:id:miur-us:20190204234538p:plain

外円からの割合をエリアBGM、内円からの割合をメイン音楽の音量に

 エリア外のどの位置から入ってきても、同様な処理をするのには、このように中心からの距離で計算するのがいいはずです。というわけで、赤帯内にいる時はこのように計算した値から、図のように音量バランスを設定しています。

 

スクリプトについて

 今回、二つのAudio Sourceの音量をクロスフェードする、ということでAreaMusicFadeCrossというスクリプトを作成しました。

f:id:miur-us:20190205000356p:plain

AreaMusicFadeCrossのメンバー変数

 これをカプセルコライダーをアタッチしたゲームオブジェクトに追加して、メインBGMエリアBGMのAudio Sourceをセットするだけで使えます。

f:id:miur-us:20190205000624j:plain

トリガーモードをアクティブにするのを忘れずに!!

f:id:miur-us:20190205000827p:plain

Start関数

 Start()内にて、プレイヤーやコライダーの参照を得たり、そこからコライダーの半径をgetしたりします。

 ここでは半径の70%を内円としているので、areaThreshold = colliderRadius * 0.7fとなっています。半径からそれを引けば、赤帯区域の長さが計算できるので、faderCrossDist赤帯の長さです。

 

Unityのオーディオソース(Audio Source)

f:id:miur-us:20190204234756j:plain

メイン音楽のAudio Source

 Unityにはオーディオソースというコンポーネントが用意されています。これにオーディオクリップ(wavやMP3などの音声ファイル)を読み込み、音を鳴らすわけですが、ボリューム・プロパティがありますので、これを使います。

AudioSource mainBGM = GetComponent<AudioSource>();

mainBGM.volume = 0.0f;

って感じでボリュームを変更できます。

f:id:miur-us:20190204235448p:plain

 volumeの値は0.0f~1.0fまでの値を取るので、これは0%~100%と考えていいです。なので、フレーム毎にプレイヤーの位置を計算して、その割合をそのままボリュームの値に設定してもいいのですが、それだと変化が急すぎてしまいます。

 なので、Math.Lerpを使って緩やかに推移させていく、という処理にします。この関数を上手く使えば、線形補完によって、緩やかに値を目標値にまで変化させることができます。

 

余韻を持たせる

f:id:miur-us:20190205001543p:plain

プレイヤーはコライダーから出たら・・・

 位置によって、バランスを変化させるということが出来ました。しかし、ボリュームの変更にMath.Lerpを使っていることもあって、このままだとエリアのコライダーを出た瞬間にBGMがぶつ切りになってします。(あるいはクロスフェード が途中で止まる)

 ある程度の余韻を持たせて、コライダーを出てからも緩やかにフェードクロスが続き、しばらく経った後で完全にメイン音楽に切り替わる、という風にしたいです。そのためには単にエリアのコライダーにいるというフラグだけでは不十分です。エリアから出た後でも、しばらくの間クロスフェードさせたいからです。 

f:id:miur-us:20190205000158p:plain

Coroutineで処理を遅らせる

 なので、IsFadeCrossingという変数を作っています。そして、コルーチンによる遅延処理を行います。エリアから出た5秒後にIsFadeCrossingをfalseにするという処理です。これで、エリアを出た後でも、IsFadeCrossingがtrueの間は、Update関数内でクロスフェード処理が続行することになります。

 

コルーチンのキャンセル

f:id:miur-us:20190204235304p:plain

StopCoroutine()

 ただし、一つ問題があります。出た後にまた直ぐエリア内に入ると、コルーチン処理がエリア内にいた状態で実行されてしまいます。

 なので、コルーチンが実行されている間にまたエリア内に入った場合は、StopCoroutineを使ってコルーチンを停止させます。

 

挑戦を振りかえって

 今回のやり方だと、Mixerを挟んでいないのでシンプルで分かりやすいですが、より高度なことをやろうとするとミキサーを挟まないと駄目なんでしょうね。

 ミキサーの場合は、パラメータ操作にややこしい部分もありますが、ダッキング機能(他の音が鳴ると音量を下げる機能)スナップショット機能もあるので、上手く使いこなせれば、今回チャレンジした以上のことも出来そうです。ここらへんはUnityのミキサー周りをよくわかっていないので、なんとも言えません。

 

FMODとの出会い

f:id:miur-us:20190205003529j:plain

FMODのトップ画面(公式デモ・プロジェクト)

 というわけで、とりあえず目標となる動作を自分なりに実現できたのですが、その後、FMODというUnityやアンリアル・エンジンでも使えるオーディオ・ソフトと出会い、そのあまりの性能の良さに感動し、この記事を書くきっかけにもなりました。

 

 FMODというのは、インタラクティヴなメディアにおける柔軟なオーディオ管理を実現するためのソフトで、3Dオーディオやゲーム内の状況、プレイヤーの操作に対応したオーディオ操作をより高度にスムーズに行うことのできるソフトです。

https://www.fmod.com/

 自分もまだ触り始めたばかりなのですが、DAWや動画作成ソフトなどを触ったことのある人なら、すぐに動かせるようになると思います。直感的で使いやすい!!しかも、ホビイストや個人製作者(予算制限あり)ならタダ!で使うコトができます!はっきり言って、タダで使っていいクオリティではないので、ゲーム・オーディオなどに興味ある人はぜひ触ってみるべきです!

 

FMODで出来ること

f:id:miur-us:20190205002132p:plain

トラックをレイヤーで分けてコントロールする

 今回のエリア内に入ると音が鳴り始める、はもちろんですが、それ以上に高度なオーディオ処理が、簡単に実現できるようです。例えば、楽曲内のセクションごとのループだとか、進行具合をパラメータとして、曲の進行をコントロールしたりすることも可能です。上写真ではAreaというパラメータで、各レイヤーの音量を変化させています。

 サウンドや音楽は全てFMOD内では全てイベントとして扱えますが、様々な音声を組み合わせることが出来るし、それらをソフト上のエフェクタで加工することも出来ます。

f:id:miur-us:20190205004211j:plain

いくつかのサンプルから、ひとつをランダムに再生できる

 また、いくつかのサウンドをまとめて、その内のどれかをランダムに再生するということも簡単に出来ますし、再生の度にピッチや音量を微妙にランダマイズさせることもできます。

 Unityで自分でスクリプトを書いて、同様のことを出来なくはないですが、それよりはるかに手軽で素早く出来ます。

 

 また、デモプロジェクトとして、なんと!あの有名インディゲームCelestaのFMODプロジェクトが配布されています。元々はMOD開発者向けのためのようですが、FMOD初心者にとっては格好の教材なので、これも研究したいと思います!

 

 今回の記事でチャレンジしたこと、あるいはその他の様々なことが、このソフトがあれば実現可能となるのでかなり興奮しており、ガンガン触っていきたいな、という感じです。