ADX2+UE4を使用してRPGなどの戦闘開始・終了BGMをシームレスに繋げる


はじめに

アンリアルエンジン4とサウンドミドルウェア「ADX2 for UE4」を使って、RPGなどのゲーム内で戦闘が開始される・または終了するタイミングで専用のBGM演出をシームレスに演奏する実装をしてみます。
ファンファーレや効果音などの独立したサウンドではなく、非戦闘時~戦闘終了までがひとつの曲として成立するようなイメージです。

(後日サンプル用意して載せます・・・!)

当記事は基本的にADX2 + UE4でオープンワールド系ゲームを想定したBGMの実装記事の発展編です。
https://qiita.com/SigRem/items/45426e599b4b000874de
上記の記事に機能を加えるかたちで実装していますが、当記事の機能のみを組み込むことも可能です。

当記事ではUE4.26.1を使用します。基本的にブループリントのみでの実装を想定しています。
ADX2はインディー向けの「LE版」であれば、無料で使用できます。
https://game.criware.jp/products/adx2-le/

記事執筆時点のADX2 for UE4のSDKバージョンはv1_29です。

前提

ADX2 for UE4の導入や基本的な使い方は以下の記事にあります。必要に応じて参照してください。
ADX2 for UE4の導入で、一歩上のサウンド表現を(導入編)
https://qiita.com/SigRem/items/4250925f6d66a4fd287a
ADX2 for UE4の導入で、一歩上のサウンド表現を(実践編)
https://qiita.com/SigRem/items/c089b71c42e898980a46

実装

AtomCraftでBGMを構成する

マテリアルの用意

ベースとなる簡単なBGMの他に、
- 戦闘開始前の盛り上がり(フィルイン)
- 戦闘が始まる際のシンバルの音(クラッシュ)
- 戦闘終了後の余韻(アウトロ)
となる3つのサウンドを用意しました。

キューの設定

まずはタイムラインのタイムベースを設定します。
対象のキューを開き、時計の右の三角形を押して「タイムベースの編集」を選択します。

正しいBPMを入力します。

時計のボタンを押し、タイムラインの表示単位が秒数でなくテンポに合わせて表示されていることを確認します。

トラックリストの空欄で右クリックし、「ビート同期情報の作成」をしておきます。

「BeatSync」マーカーをクリックし、インスペクターにて「BPM」に正しいBPMを入力します。

コールバックマーカーの配置

フィルインやクラッシュが鳴るタイミングとなる「コールバック」マーカーを配置していきます。
トラックリストの空欄で右クリックし、「マーカーの作成」を選択。

マーカーの追加ウィンドウが開くので、タイプを「コールバック」に設定します。
名前とタグに分かりやすいものを入力します。まずはフィルインから。

フィルインのサウンドが再生されるタイミングにマーカーをドラッグして持っていきます。
このマーカーを通過した際に戦闘フラグがTrueになっていると、フィルイン用サウンドが再生されることになります。

次にクラッシュ用のコールバックマーカーを作ります。「ID」はフィルイン用と被らないものにしたほうがベターです。

こちらのマーカーはタイムラインの最初(ループの起点)に配置します。

アウトロのパートについてはコールバックマーカーとは別の処理を行うため、これでキューの編集は完了です。

各キューを作成

用意したマテリアル(クラッシュ、アウトロ、フィルイン)に対するキューをひとつずつ作成します。
キューシートを右クリックし、「新規オブジェクト」→「キュー『ポリフォニック』の作成」です。

キューに対してマテリアルをひとつずつ配置します。

キューシートのビルド

ここまでできたらキューシートをビルドします。

UE4での実装

キューシートのインポート、セットアップ

ビルドしたキューシートをインポートします。


プロジェクト設定を開き、CriWareタブの「Atom Config」に出力されたacfファイルが指定されていることを確認します。

レベル上にキューを配置し、ゲームを再生するとキューも再生されることを確認しておきます。

シーケンスコールバック用のイベントを作成する

戦闘開始時用のキューを再生します。
今回は簡単にレベルブループリントから処理を行ってみます。
予め配置したキューを選択しておきます。

キューを選択した状態で、Detailsパネルから「Auto Activate」のチェックを外しておきます。

Aisacコントロールの初期値を任意のもの(非戦闘BGMになるよう)に設定しておき、さらに「Loop Setting」を「One Shot」にしておきます。

レベルブループリントを開きます。

イベントグラフの空欄で右クリックし、キューのリファレンスノードを配置します。

もし検索欄にノードが出ない場合、レベル上でキューを選択しなおしてみてください。

リファレンスノードから線を伸ばし、Get Atom Componentノードをつなげます。

さらに線を伸ばし、「assign」と検索してAssign On Atom Sequence Callbackノードを配置します。

イベントディスパッチャからカスタムイベントが作られるので、適当に名前をつけます。

任意のタイミングでBGMの再生・シーケンスコールバックとの紐付けができるよう、BGMの再生もカスタムイベントから呼び出せるようにしておきます。

戦闘フラグを監視してフィルインを再生する

シーケンスコールバックイベントから処理を作っていきます。

イベントの「Sequence Info」ピンから線を伸ばし、「break」と検索して出てくるノードを選択します。

Breakノードで情報を分割し、必要なものが取り出せるようになりました。

Switch on IntノードでCallback IDによる分岐をします。

タグによる分岐も可能です。その場合はSwitch on Stringノードを使用し、Detailsパネルでピンの名前を使用しているタグ名に変更します。

Spawn Sound 2Dノードを配置します。ノードのカテゴリが「Atom」に属しているものを選んでください。

Spawn Sound 2Dノードにフィルインとなるキューを設定します。

デバッグ用にPrint Stringsでメッセージを表示します。

Event BeginPlayイベントからBGMの再生イベントを呼び出します。

この状態でゲームを実行すると、フィルインのタイミングでメッセージが表示され、フィルインとなるキューが再生されるはずです。

ループされずに再生が止まってしまいますが正常です。後ほど改良します。

戦闘状態かを示す変数を追加します。タイプはBoolです。

テスト用に初期値はTrueにしておきます。

Branchノードをはさみ、戦闘フラグがTrueであればフィルインが再生されるようにします。

キューのループ

現状ではキューがループされずに停止してしまうので、ループを行いましょう。
Event Tickでキューが再生終了されているかを監視して、再びBGMの再生イベントを呼び出します。

これでBGMがループ再生されるようになります。
単純にキューをループするだけだと2回目以降のシーケンスコールバックが発火されないため、再度イベントを紐付けています。

テスト用に、戦闘状態/非戦闘状態を切り替える機能を作ってみます。
Input KeySet Battleからなる簡単なものです。

変数「bBattle」の初期値はFalseにしておきます。

フィルイン用のキューは非戦闘→戦闘のタイミングで一度のみ再生することが望ましいので、DoOnceノードで制限をかけています。「bBattle」がFalseであればDoOnceの制限をリセットするので、非戦闘状態を経由すれば再びフィルイン処理を行うようになります。

戦闘フラグを監視してクラッシュを再生する(戦闘移行)

クラッシュの再生もフィルインの場合とほぼ同じです。
DoOnceで制限をかけ、戦闘開始時のみクラッシュ音を再生します。また、Set Aisac by Nameなどで戦闘状態のBGMバリエーションに差し替える場合はこのタイミングで行うとメリハリがきいて良いかと思います。

戦闘終了時にアウトロを再生する(BeatSync)

アウトロの再生タイミングには、キューのテンポに合わせて発火される「BeatSync」イベントを使います。

処理の準備として、BeatSyncイベントを監視するかどうかを示す変数を用意し、戦闘開始のクラッシュと共にTrueにします。

シーケンスコールバックのイベントと同じように、「Assign On Atom Beat Sync Callback」を選択してイベントを作成します。

イベントに名前をつけておきます。

BeatSyncイベントでは、まずBeatSync処理を監視する状態であれば、現在戦闘状態かを判別します。

その後、「bBattle」が「False」であれば監視状態を解除し、BGMの再生を停止すると同時にアウトロのキューを再生します。

これである程度BGMのテンポに合わせて戦闘終了フレーズが続けて再生されることになります。
ゲームを実行して、1,2キーを押して戦闘状態を切り替えてテストしてみましょう。

ビートだと唐突なので小節ごとに判定を行いたいという場合は、イベントの「Beat Sync Info」をBreakノードで分解することで何拍目かを取得することができるので、これを利用してタイミングを制限可能です。