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

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

https://twitter.com/sleepyslowsheep/status/1481539926814384128?s=20

当記事は基本的に**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つのサウンドを用意しました。
![A01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/f79dac13-ab0f-c708-16e7-12188d0d9d6a.png)


### キューの設定

まずはタイムラインのタイムベースを設定します。
対象のキューを開き、時計の右の三角形を押して「タイムベースの編集」を選択します。
![A02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/06ad6bce-33a6-5bf1-5c81-99dfce9150fc.png)
正しいBPMを入力します。
![A03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/0cd9645c-0f3a-31f4-f837-b8d87b765be0.png)
時計のボタンを押し、タイムラインの表示単位が秒数でなくテンポに合わせて表示されていることを確認します。
![A04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/0d632431-0f0c-2d48-d724-b8ddaa2a64c3.png)
![A05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/77750b57-b191-6f24-c977-1d5718f47c64.png)

トラックリストの空欄で右クリックし、「ビート同期情報の作成」をしておきます。
![A06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3ef5e1c0-fdfa-2f7e-6426-52c9eff63bb8.png)
「BeatSync」マーカーをクリックし、インスペクターにて「BPM」に正しいBPMを入力します。
![A07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/23893855-ee66-9097-7ab7-7c385697e002.png)


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

フィルインやクラッシュが鳴るタイミングとなる「コールバック」マーカーを配置していきます。
トラックリストの空欄で右クリックし、「マーカーの作成」を選択。
![A08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/5bfea9bb-bcc9-e51a-e6b5-d31fa8cfb4aa.png)
マーカーの追加ウィンドウが開くので、タイプを「コールバック」に設定します。
名前とタグに分かりやすいものを入力します。まずはフィルインから。
![A09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/9b978f21-e85e-be69-6ec4-58d498aaaf08.png)
フィルインのサウンドが再生されるタイミングにマーカーをドラッグして持っていきます。
このマーカーを通過した際に戦闘フラグがTrueになっていると、フィルイン用サウンドが再生されることになります。
![A10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/61fa063c-4ff7-c024-0dca-ad843af4a0e3.png)
次にクラッシュ用のコールバックマーカーを作ります。「ID」はフィルイン用と被らないものにしたほうがベターです。
![A11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1b2c2a93-e468-8b74-a5c7-023f39c2debd.png)
こちらのマーカーはタイムラインの最初（ループの起点）に配置します。
![A12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1db63300-e9f9-91fb-acdb-3b15b4242686.png)

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


### 各キューを作成

用意したマテリアル（クラッシュ、アウトロ、フィルイン）に対するキューをひとつずつ作成します。
キューシートを右クリックし、「新規オブジェクト」→「キュー『ポリフォニック』の作成」です。
![A15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/93275c81-08b9-68ec-2758-70c50b88b3a4.png)
キューに対してマテリアルをひとつずつ配置します。
![A16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/f22a2f2c-5cd4-effc-74ea-9c8c9221df9e.png)
![A17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/524c8102-043b-50d3-e33d-7b87f93ffbb6.png)


### キューシートのビルド

ここまでできたらキューシートをビルドします。
![A13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/f30ff323-c0e8-df0d-a0b0-892f4f76c6a1.png)
![A14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3c543381-6db3-4bcf-3caa-1f1ad71c0f84.png)




## UE4での実装

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

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

![B01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/c758c397-9f18-e6c3-939b-641fa13d5b4d.png)
![B02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/aec8c828-755a-2798-07e4-91852721b14b.png)

プロジェクト設定を開き、CriWareタブの「Atom Config」に出力されたacfファイルが指定されていることを確認します。
![B03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/9efadafc-5d04-08f0-1518-17c14c723d60.png)
![B04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d98f53af-fb7b-130b-0659-10736d88b8cc.png)

レベル上にキューを配置し、ゲームを再生するとキューも再生されることを確認しておきます。
![B05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/fdd50278-ebfd-0452-b891-697d0b1ca70e.png)


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

戦闘開始時用のキューを再生します。
今回は簡単にレベルブループリントから処理を行ってみます。
予め配置したキューを選択しておきます。
![B07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/86f136f8-e0d4-2040-c358-2fe626d5273c.png)
キューを選択した状態で、Detailsパネルから「Auto Activate」のチェックを外しておきます。
![B07b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/893514a9-8cbd-0fe2-c5d2-be9b6c7dc02d.png)

Aisacコントロールの初期値を任意のもの（非戦闘BGMになるよう）に設定しておき、さらに「Loop Setting」を「One Shot」にしておきます。
![B07c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/44f41e2d-3b39-ea19-0e8c-bdb411f3434f.png)



レベルブループリントを開きます。
![B06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/9de3f6f1-dac1-1070-7a99-b187681459cb.png)

イベントグラフの空欄で右クリックし、キューのリファレンスノードを配置します。
![B08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d7fdaf59-480a-0f14-146e-f5fedb41cabc.png)
もし検索欄にノードが出ない場合、レベル上でキューを選択しなおしてみてください。
![B09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3507a78e-a234-7080-5fb5-ecc1bc8d610f.png)

リファレンスノードから線を伸ばし、**Get Atom Component**ノードをつなげます。
![B10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/8f5f9aef-70c2-2000-1be2-26d64418ccdb.png)
さらに線を伸ばし、「assign」と検索して**Assign On Atom Sequence Callback**ノードを配置します。
![B11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/8400f17d-2bc8-ff22-cdca-29edad0ea1de.png)
イベントディスパッチャからカスタムイベントが作られるので、適当に名前をつけます。
![B12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/87c969f2-7503-26b5-8f78-f5a67cddcb6f.png)

任意のタイミングでBGMの再生・シーケンスコールバックとの紐付けができるよう、BGMの再生もカスタムイベントから呼び出せるようにしておきます。
![B13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/9810211d-55af-4fac-c8a3-1f1ac999a84c.png)


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

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

イベントの「Sequence Info」ピンから線を伸ばし、「break」と検索して出てくるノードを選択します。
![B14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/0ad714d2-f39e-d885-0849-e1012799f5b8.png)
**Break**ノードで情報を分割し、必要なものが取り出せるようになりました。
![B15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/df31a7fc-7387-d951-3eae-ae727631afaf.png)
**Switch on Int**ノードでCallback IDによる分岐をします。
![B16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/5148b751-4fe5-49f9-0813-0429f232e6cd.png)
タグによる分岐も可能です。その場合は**Switch on String**ノードを使用し、Detailsパネルでピンの名前を使用しているタグ名に変更します。
![B17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/65d2f1fb-f6a7-90ef-be0d-e7a4aa8f0eb1.png)
**Spawn Sound 2D**ノードを配置します。**ノードのカテゴリが「Atom」に属しているものを選んで**ください。
![B18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/34141a49-fe0d-c11f-3e44-ed9d3a4d2978.png)
**Spawn Sound 2D**ノードにフィルインとなるキューを設定します。
![B19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/db6a5b01-5b1c-1579-f9dc-c94b0004fc86.png)
デバッグ用に**Print Strings**でメッセージを表示します。
![B20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/2609d5b6-b82f-f87e-fe01-c67a7a389df0.png)

**Event BeginPlay**イベントからBGMの再生イベントを呼び出します。
![B24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/7dbe8336-4a2a-9321-7ef0-ffb358bf256f.png)


この状態でゲームを実行すると、フィルインのタイミングでメッセージが表示され、フィルインとなるキューが再生されるはずです。
![B21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/fc81b35f-3285-be81-686b-0c69a215a7e9.png)

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

戦闘状態かを示す変数を追加します。タイプはBoolです。
![B22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/872d59ef-897a-893b-4b45-9742c9c65775.png)
テスト用に初期値はTrueにしておきます。
![B22b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1610fb33-6d7e-6233-8ffd-b60c7d5223d4.png)

**Branch**ノードをはさみ、戦闘フラグがTrueであればフィルインが再生されるようにします。
![B23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3a5fd1ca-16bb-4c73-93d4-093ec25f5174.png)



### キューのループ

現状ではキューがループされずに停止してしまうので、ループを行いましょう。
**Event Tick**でキューが再生終了されているかを監視して、再びBGMの再生イベントを呼び出します。
![B25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a6e4feea-f087-813e-dd9d-2d1147b9892c.png)
これでBGMがループ再生されるようになります。
単純にキューをループするだけだと2回目以降のシーケンスコールバックが発火されないため、再度イベントを紐付けています。

テスト用に、戦闘状態/非戦闘状態を切り替える機能を作ってみます。
**Input Key**と**Set Battle**からなる簡単なものです。
![B26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/cd45581c-0662-1cef-d7e1-7af3a142b5b4.png)
変数「bBattle」の初期値はFalseにしておきます。
![B27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/8bd4dd37-1110-84cd-f61a-4616e5bf309b.png)
フィルイン用のキューは非戦闘→戦闘のタイミングで一度のみ再生することが望ましいので、**DoOnce**ノードで制限をかけています。「bBattle」がFalseであれば**DoOnce**の制限をリセットするので、非戦闘状態を経由すれば再びフィルイン処理を行うようになります。
![B28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/e04861a7-a2f2-68e4-ee62-2b6b35360dd0.png)


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

クラッシュの再生もフィルインの場合とほぼ同じです。
**DoOnce**で制限をかけ、戦闘開始時のみクラッシュ音を再生します。また、**Set Aisac by Name**などで戦闘状態のBGMバリエーションに差し替える場合はこのタイミングで行うとメリハリがきいて良いかと思います。
![B29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d2672a55-cbda-26f9-09df-6d4c9039bd7b.png)


### 戦闘終了時にアウトロを再生する（BeatSync）

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

処理の準備として、BeatSyncイベントを監視するかどうかを示す変数を用意し、戦闘開始のクラッシュと共にTrueにします。
![B31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/c6cc9cbb-8f4e-d2f2-d327-da3a801cf967.png)



シーケンスコールバックのイベントと同じように、**「Assign On Atom Beat Sync Callback」**を選択してイベントを作成します。
![B30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/593df386-5910-a844-f9fd-29bd0d663380.png)
イベントに名前をつけておきます。
![B32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/dde008f1-bf3a-a516-1ad2-de86e04d8e01.png)

BeatSyncイベントでは、まずBeatSync処理を監視する状態であれば、現在戦闘状態かを判別します。
![B33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3bad1e8d-4bfb-73db-f687-62b215445072.png)
その後、「bBattle」が**「False」であれば**監視状態を解除し、BGMの再生を停止すると同時にアウトロのキューを再生します。
![B34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/ebb83999-5a0d-b6c3-4649-025e310429d7.png)

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

ビートだと唐突なので小節ごとに判定を行いたいという場合は、イベントの「Beat Sync Info」を**Break**ノードで分解することで何拍目かを取得することができるので、これを利用してタイミングを制限可能です。
![B35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a178df2f-2441-3d09-1719-9554f011f450.png)