UE5+ミドルウェア「ADX」で、「補完（クッション）フレーズ」を使いシームレスなBGM演出

# はじめに
ADXアンバサダーとして記事を書いておりますSigと申します。
この記事ではアンリアルエンジン5とサウンドミドルウェア「ADX for UE」を連携させ、ゲーム中の演出として
「**BGMの切り替わりにクッションとなるフレーズを演奏することで切れ目のないBGMを演出する**」という実装を試してみるものです。

https://x.com/sleepyslowsheep/status/1737451092659810570?s=20

こちらは先日の勉強会「UE Tokyo .dev #2」内のライトニングトークとして紹介したインタラクティブミュージックの実装例「UE5+ミドルウェアでインタラクティブミュージック！」の内容を発展させ、応用してみた実装例です。

https://www.docswell.com/s/sig_rs/598D91-2023-12-11-130753

<iframe width="560" height="315" src="https://www.youtube.com/embed/-oOj5ClW1HY?si=TfNMyTc-ERcMrW6x" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

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

# 前提
ADX for UEの導入や基本的な使い方は以下の記事にあります。必要に応じて参照してください。
**ADX for UEの導入で、一歩上のサウンド表現を（導入編）**

https://qiita.com/SigRem/items/4250925f6d66a4fd287a

**ADX for UEの導入で、一歩上のサウンド表現を（実践編）**

https://qiita.com/SigRem/items/c089b71c42e898980a46

# 実装

## AtomCraftでサウンドを用意する

まずはサウンドオーサリングツール「AtomCraft」でBGMに使用するサウンドを組み立てます。
今回はサウンド素材として、
- 「ADX_BGM_A」: 最初の部屋のBGM
- 「ADX_BGM_AT」: 最初の部屋から移動した際の「補完フレーズ」
- 「ADX_BGM_B」: 2番目の部屋のBGM
- 「ADX_BGM_BT」: 2番目の部屋から移動した際の「補完フレーズ」
の4点を用意しました。

それぞれマテリアルツリーにインポートします。
「マテリアルルートフォルダー」内のサブフォルダに格納することで見やすくなります。
![A01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/c191ca4f-aac3-50a8-9e3f-f7014fd0fec4.png)

「ADX_BGM_A」「ADX_BGM_B」は部屋にいる間ループして再生し続けたいので、インスペクターにて「ループ情報の上書き」を「True」にしてループ再生を有効にします。
![A01B.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/59575a35-ef78-c4ff-b798-76d6940796eb.png)


マテリアルからキューを作成します。
通常通りにキューを作ってもいいのですが、今回は簡易なキューがそれぞれ欲しいのでマテリアルツリーからマテリアルをドラッグ&ドロップすることで「ポリフォニック」タイプのキューを作ります。
![A02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/8a82567b-366c-bbb9-5b15-5c47417de82f.png)

4つのキューが作成されました。
![A03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/41937883-9ab2-3d4b-59c9-418577b694a5.png)

キューのトラックリストで右クリックし、「新規オブジェクト」→「ビート同期トラックの作成」を選択します。
![A06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/4aa72a85-1ed9-f692-d5d1-57f8bb740bef.png)

ビート同期トラックを選択し、インスペクターにてBPMを正しく設定します。
![A07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/55174361-fbe2-3943-6cd2-1919767d4377.png)
ビート同期ラベルを作成しておきます。
![A08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/538094c4-6442-3a2b-1e66-20b3fa39a491.png)

ここまでできたらキューシートをビルドします。
![A04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/90f597f1-6bde-c9de-d620-6b1ae451193f.png)
![A05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/edd3fa6e-08d6-4e53-9ecb-70d85fd0afc6.png)


## UE5にサウンドを組み込む

UE5でビルドしたキューシートをインポートします。
![B01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/6971a0f3-abc0-2362-9eca-eaee6449e546.png)
acfファイルインポート時のダイアログでは両方とも「Yes」を選択します。
これにより、自動的にacfファイルがプロジェクトに設定されます。
![B02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/43fc1933-3cef-2816-f5ea-6aa380fe7031.png)
![B03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/2d6d6a6b-c3b4-b6a4-e6a7-ac479da07ed7.png)
acbファイル（キューシート）を開き、キューが正常に再生されることを確認します。
![B04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/45acc043-c831-5d70-2eea-04ecc02668f2.png)


## レベルブループリントからBGMを再生する

今回はふたつの部屋を作り、左の部屋と右の部屋でBGMが切り替わるようにします。
![C01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d84216bd-6e05-d170-6977-d42d887637ec.png)

「Open Level Blueprint」からレベルブループリントを開きます。
![C02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3ff7e481-9cee-f820-4d3c-8b6e6f96893e.png)

ゲーム開始時のイベントで**Spawn Sound 2D**ノードを使い、BGMを再生します。
（画像内の**Set View Target with Blend**ノードはカメラ用の処理なので、今回は必須ではありません）
![C03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3859c53f-7171-7010-44ca-6f43a7119c7d.png)

**Spawn Sound 2D**ノードのインプットピンを右クリックし、「Promote to Variable」から変数を作成します。
![C04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/6737bb2f-5053-7350-71e4-0b41002b632b.png)

「BGM」と名前をつけました。
![C05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a871b163-428f-00c6-cd13-85a14f325a2d.png)

グラフをコンパイルし、Detailsパネルで変数に既定のキューを設定します。
![C06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/6962505d-7623-5568-4270-d6e8f8071fbd.png)

**Spawn Sound 2D**ノードを展開します。
![C07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/81d6930c-3bec-5561-dca8-a37dbeefa619.png)

「Auto Destroy」のチェックを外します。
通常**Spawn Sound 2D**で再生したAtomコンポーネントは再生終了時に破棄されますが、今回は後で異なるキューを再生するために使いまわしたいので、自動的に破棄されないようにします。
![C08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/4cee39bf-af9d-46f6-ec40-c49b257bb733.png)
ゲームを再生し、BGMが流れることを確認します。
![C09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/86cf9f2c-e268-6a28-0bfc-a0e15795bfb3.png)


## テンポを監視し、イベントを発火する

新規に変数を追加します。プレイヤーが現在いる部屋を記録しておく、Int型の「CurrentRoom」です。
![D01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/7bbdc50c-de74-899f-8c12-9c6e0ef050b7.png)

レベルにふたつのBox Triggerボリュームを追加します。
それぞれが部屋を覆うようにサイズを調整しました。
![D02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/13e9db3a-0d06-2c89-6981-fec0f7dbfbe6.png)

**ボリュームを選択した状態で**レベルブループリントに戻り、**On Actor Begin Overlap**イベントを配置します。
![D03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/72fc2e39-1525-ed93-f15c-84bf45d8fa25.png)

このイベントの後に**Branch**ノードで**Get Player Character**との同一性を確認しておけば、プレイヤーが部屋を移動したかが判定できます。
この処理をふたつのボリュームについて作っておきます。
![D04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/c4556b8c-92cb-b813-c807-c1036827e315.png)

プレイヤーが部屋を移動したら、変数「CurrentRoom」を変更します。
![D04B.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1d54813e-61e2-2744-ed54-ce264342469f.png)

**Event BeginPlay**からの処理に戻り、**Spawn Sound 2D**の青いアウトプットピンを右クリックして「Promote to Variable」で変数化します。
![D05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/bc047344-6e3b-8c19-eba4-4822839efafc.png)

変数に「Atom BGM」と名前をつけ、さらにそのアウトプットピンから線を伸ばし「**Assign On Atom Sound Cue Beat Sync**」を選択します。
![D06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/43212789-a243-fb60-510d-4671560e8f14.png)
**Bind Event to On Atom Sound Cue Beat Sync**ノードと、イベントノードが配置されます（「OnBeat」と名前をつけました）。
これはBGMのビートに合わせて発火するイベントです。
![D07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/6c441675-1bcc-b0bf-c764-b8348fc7dc86.png)
**OnBeat**イベントから**Print String**ノードを置き、テストとしてビートごとに文字を表示してみます。
![D08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/91c5f563-4129-4556-f468-c31ae492241c.png)

ゲームを再生すると、画面左上に小節内のビート数が表示されます。
![D09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/16ab01d1-b15f-6b7e-a973-51f5815ea2b0.png)



## 補完フレーズを再生し、BGMを切り替える

Int型の変数を追加します。現在再生中のBGMインデックスを記録するもので、名前を「CurrentBGM」とします。
![E01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1109fecd-3a85-a8f1-8d3c-0dae54080de7.png)
「補完フレーズ」はBGMの小節の始めの「ちょうどいいところ」で再生を開始したいので、「Beat Count」を監視し「0」の場合にBGMの切り替わり判定を開始します。
現在のBGMとプレイヤーが現在いる部屋が一致しているかを**Branch**で分岐させます。
![E02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a63ed0c1-06ae-853a-cfd1-a54dc6b95c41.png)

BGMのキューを一括で記憶しておくため、新しく変数を作ります。型は「Atom Sound Cue」で、「Variable Type」の横のマークを選択し配列（array)化します。
![E03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a326ca1c-683e-a852-a7bc-0d8476e9082f.png)
**コンパイルし**、「Default Value」にキューを格納していきます。
![E04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d3f5d541-6f9a-b97c-cba9-b18381ca52ea.png)

現在のBGMとプレイヤーが現在いる部屋が一致していない場合、BGMのキューを変更します。
ここで指定するのは補完フレーズとなる「ADX_BGM_AT」か「ADX_BGM_BT」です。
**Select**ノードを使い、「CurrentBGM」変数の値によって「BGMList」のインデックスを指定します。
（画像を見てもノードのはたらきが分かりにくい場合でも、ひとまず同じように組んでみるとイメージできるかもしれません）
また、その後で「CurrentBGM」に「CurrentRoom」の値を代入して一致させます。
![E05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/776ca60c-e317-fe89-b72f-c2b4b9a5f763.png)

補完フレーズの再生が停止するのを待って次の部屋のBGMを再生開始したいので、**Bind Event to On Atom Sound Play State Changed**を配置し、再生状態の変化をイベントに通知します。
![E06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/96d5a2be-c969-a21d-7dad-12f3de2ccc82.png)

**Bind Event to On Atom Sound Play State Changed**が発火させるイベントでは再生状態が取得できます。
これはイベントノードのアウトプットピンから**Switch**ノードを引き出すと状態別に処理を分岐させられます。
![E07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/48c333a1-d4b9-a16d-1f6e-fa77318a2f58.png)
「Stopped」（再生停止時）のみ次の部屋のBGMを再生する処理を続けます。
![E08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3b1f035d-deb4-35c6-727e-a5badb8d6009.png)

そのままでは再生状態が切り替わるたびにイベントが走ってしまうので、処理が完了したら一旦イベントの発火を停止させます。
![E09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/26973d40-c3fa-954b-1543-24aa895298d8.png)


## 完成！
これで部屋を移動した際に補完フレーズを挟んだ上で、次の部屋（マップ）のBGMを再生することで繋がっていくBGMを作ることができました。

遷移先のBGMがある程度特定できる場合は「ブロック再生」や「REACT」機能を使うという方法もあります。
参考となる記事がありますので、必要に応じて参照してみてください。

https://qiita.com/SigRem/items/696ab6f1b4e533eebdbf

https://qiita.com/SigRem/items/1f27f0460eb0338dac66