UE5+ADXで、キャラクターの「溜め行動」に応じてサウンドを変化させる

# はじめに
アンリアルエンジンとサウンドミドルウェア「ADX for UE」を連携させ、ゲーム内でキャラクターが「溜める」（チャージする）行動をとった際にチャージ用効果音が段々と変化し、その後のアクションのサウンドにも影響を与える実装方法です。

アクションゲームによくある演出ですね。
チャージしている間の無防備な瞬間の緊張感と、発射した瞬間のカタルシスを表現するのに役立つ表現です。

実装の概要はこんな感じになります。
- 特定のボタンを長押しすると、キャラクターがチャージを開始する
- チャージ音は最初は低い音で、溜め続ける（ボタンを押し続ける）ことで段々高く音が変化する
- ボタンを離すとチャージが終了し、キャラクターがチャージショットを発射する
- アクションの開始と同時にサウンドが再生され、さらにチャージ段階によって変化する

チャージ中のサウンドはAISACコントロールで操作し、チャージショットに関してはゲーム変数による「スイッチ」機能で実装します。


# 前提

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

なお、ADX2は「ADX」へ名称が変更になりましたが、ツール構成は変更ありません（2がないから古いほう、というわけではありません）。

記事執筆時点のADX for UEのSDKバージョンはADX LE UE SDK(1.31.00.01)です。

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

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

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

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


# 実装

## AtomCraftでチャージ音、アクション音を構成する

### マテリアルのインポート

必要になるサウンドを用意し、インポートします。
今回用意したのはチャージ中に再生され続ける「SE_Charge」、そしてチャージ段階に応じた3種類のチャージショット音「SE_ShotA,B,C」です。「SE_Charge」はループできるものがいいでしょう。
![A01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/379e1910-2a57-b491-9f1e-c1cf9873ae57.png)
マテリアルツリーで「SE_Charge」を選択し、ループ情報の上書きを「True」、ループタイプを「ループ」にします。
これで自動的にループ再生され続けるようになります。
![A02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a13abf5e-d767-c3ef-715e-12fa6c509bf6.png)


### チャージ用キューの構成

新規にキューを作成します。
ワークユニットツリーでキューシートを右クリックし、「新規オブジェクト」→「キュー『ポリフォニック』の作成」を選択します。
![A03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/46a02533-21b8-8278-ae63-dc59d768e218.png)
マテリアルツリーから「SE_Charge」をドラッグアンドドロップして配置します。
テスト再生し、ループ再生されることを確認します。
![A04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/2b01258b-80e1-0623-847e-e4754b8d9cf8.png)
トラックリストの空欄で右クリックし、「新規オブジェクト」→「AISACの作成」を選択します。
![A05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/274846e2-5afd-3ced-52bd-1de84fd8acbe.png)
名前をつけ、AISACグラフタイプを「ピッチ」にします。チャージが長くなるにつれどんどん音が高く、早くなるイメージです。
![A06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/67345d6f-1ffd-5d55-d029-37c2c80c7bb6.png)
「ピッチ」のグラフを選択し、グラフを右肩上がりになるよう編集します。
![A07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/8f44de94-6a01-b1b5-39d7-644eb46328e0.png)
テスト再生しながらスライダーを動かし、サウンドがどのように変化するのか試してみましょう。
チャージを開始した瞬間は左端の部分が流れ、チャージ段階が最大になると右端のように再生されます。
![A08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/86999de5-c0c0-752f-3a98-bd222e3a73d5.png)





### チャージショット用キューの構成

チャージショット用のキューを作成します。
ワークユニットツリーでキューシートを右クリックし、「新規オブジェクト」→「キュー『スイッチ』の作成」を選択します。
![A09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d669ed5f-5df2-f705-9a55-9aeff0dff3f4.png)
キュー内に3つのサウンドをトラックを分けて入れておきます。
![A09b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/72e41ff0-8e0d-a27d-295d-65b58a19af60.png)

再生し分けるために「ゲーム変数」を追加します。
プロジェクトツリーで右クリックし、「新規オブジェクト」→「ゲーム変数の作成」を選択します。
![A10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/abab917d-c475-ffce-78dc-18e61f7aed32.png)
新しいゲーム変数に名前をつけます。今回は「ChargeLv」としました。
![A11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/5a8454bb-040f-180f-3f22-dc4e9053cfce.png)
チャージショットのキューを選択し、インスペクターにて「スイッチ」を開きます。
![A12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/e63528f8-e486-8e65-d99e-2a2be12219de.png)
スイッチ変数に新しく作成した「ChargeLv」を設定します。
![A13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/be60171a-1a41-265f-9eb8-a9f87f852e49.png)
「等分化」ボタンを押し、各スイッチ幅が均等になるようにします。
![A14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/ddd04414-8b6e-1aaf-d0af-3e67efb27f8f.png)
「セッションウィンドウ」でどう再生されるか確認してみましょう。
![A15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/bc930f7d-b2a2-ef4c-6d2a-5a9a56a12a52.png)
「ゲーム変数」ボタンを押すと、テスト用のゲーム変数が編集できるようになります。
![A16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/186b91ea-c867-a6f1-a466-52c6c27dcea9.png)
ゲーム変数「ChargeLv」を動かし、数値によってサウンドが変化することを確認してください。
![A17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/89a10ea0-3b8f-3ff8-0a1c-f49e502a2a1f.png)


### キューシートのビルド
ここまでできたら、キューシートをビルドしてUEに持っていきます。
![A18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/94199f14-c2b1-ae7e-501e-0d0a0a19d158.png)
![A19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/2a755412-76dc-8f0f-ee5d-aacfbb3b9e0b.png)



## UEでサウンドを実装する

### キューシートのインポート
ビルドしたキューシートをコンテンツブラウザにドラッグアンドドロップしてインポートします。
![B01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/9953bf80-7c06-5a8f-edeb-502760db289a.png)
![B02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/e413cefe-4e47-7a8b-98da-28e10b5422cf.png)
プロジェクト設定を開き、
![B03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/c57c81cd-972a-bb17-b7af-60c93142aa2c.png)
「CriWare」タブにて「Atom Config」にインポートしたacfファイルを指定します。
![B04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/707e4b94-e5a9-4bc2-e18c-911c05e5013e.png)


### 入力の登録

チャージショットアクションの入力用ボタンを登録します。
プロジェクト設定の「**Input**」タブに移動します。
![B05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/88dd809d-adc3-e0a6-0c9e-92d2d12a2ccb.png)
「**Axis Mappings**」の+ボタンを押して新しい入力を追加します。
![B06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/65dce470-fca0-3111-461d-ddcefa4597ce.png)
今回はテスト用として、マウスの左クリックを登録しました。
![B07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/b8a74708-16ff-b49f-9a6e-0de949bd41d5.png)


### サウンドの再生

キャラクターのブループリントアクターを開きます。
![B08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/4102f80f-9a91-a7ba-c217-648fb732a83d.png)
登録した入力を感知するイベント**Input Axis Charge**を追加します。
![C01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/c956d92e-823c-4e91-136e-eef4ad469b5a.png)
チャージ中かどうかを判別する変数、「bCharge」を追加します。型はBooleanです。
![C02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/40ab1c00-8234-6542-cf10-e2248902ba9a.png)
ボタンが押されている（入力強度が0.1以上）の場合、まずキャラクターがチャージ中かどうか判別します。
![C03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/e6e30acf-d340-9598-4636-f009838cf85c.png)
非チャージ状態であれば、「bCharge」をTrueにしてチャージ状態とします。
また、**Spawn Atom Sound at Location**ノードを使ってチャージ用のサウンドを再生し始めます。
![C04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/264c28e7-04aa-cca8-09ad-ce1f99a301f8.png)
**Spawn Atom Sound at Location**ノードの青いアウトプットピンを右クリックし、「Promote to Variable」で変数化します。
これにより、変数化しておいたサウンドに後から用意にアクセス可能になります。
![C05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/565a0b48-ac70-e178-7311-480a1c9b1f7f.png)
変数化したサウンドは「ChargeSound」と名前をつけました。
![C06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/4228243c-746b-3ad0-84aa-b16649068a86.png)
入力強度が0.1以上でない（つまりボタンを離している）側では、チャージ中であればチャージ状態を解除（False)にし、チャージサウンドを**Destroy Component**して再生を停止します。
![C07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/11174732-9f26-50fc-bc6a-c952c9809e06.png)
また、その後また**Spawn Atom Sound at Location**ノードでチャージショットのサウンドを再生します。
![C08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/f89e5303-abf0-b091-1ade-075ee4ad8a35.png)
現時点でのノードの全体図はこのようになります。
![C09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/52939368-9dac-e6e1-fa80-7414d828c41a.png)

この状態で最低限の機能が実装されているので、テストしてみましょう。ボタンを押している間チャージサウンドがループし、離すと再生が停止し、その代わりにチャージショットのサウンドが再生されます。


### チャージに応じたチャージ音のサウンド変化

チャージ段階によりサウンドが変化するよう実装していきます。
まずはチャージ段階を記憶しておくための変数「ChargeLv」を追加します。型はFloat型です。
![D01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/868898f6-fbab-71fe-f514-a1562847570a.png)
ボタンを押している間かつチャージ中である場合、チャージ段階を加算していきます。今回は**Get World Delta Seconds**ノードを加算してるため、リアルタイムの1秒でチャージ段階が「1.0」上昇します。
![D02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a69d0b7f-3647-f32c-4dcd-6d78787bf8a3.png)
ボタンを押し続けている間チャージ段階が無限に上昇してしまうので、**Clamp**ノードを噛ませて最大値を制限します。
![D03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/b2859316-e09c-ff30-9065-4ddd7611b264.png)
**Set Aisac by Name**ノードを使い、キューに対してAisacコントロールを行います。値は「ChargeLv」をそのまま代入します。
![D04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/07838323-d7e0-acc0-ae94-ccb9b8aae76c.png)
しかし、チャージ段階が「0～1.0」の間だけとは限りませんよね。チャージ段階がどの値であってもAisacコントロールには「0～1.0」の値を渡したいため、**Map Range Clamped**ノードを使用します。
「In Range」に入力（チャージ段階の最小値と最大値）を入れ、「Out Range」にはAisacコントロールの段階の最小値と最大値を入力します。
これにより、「Return Value」からチャージ段階に応じたAisacコントロール値用の「0～1.0」の数値が出力されます。
![D05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/2e2e3d00-bcd3-3b82-5bf5-08638e8408cc.png)

 

### チャージに応じたチャージショットのサウンド変化

チャージショットのサウンドも段階的に変化させていきます。
ゲーム変数に応じたサウンドの変化はAtomCraft側で設定したるので、ここでやるべきことは**Set Atom Game Variable**でゲーム変数を代入してあげることだけです。
Aisacコントロールと同じように**Map Range Clamped**ノードで数値を丸め、ゲーム変数に渡してあげます。
![D06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/9e3ad492-4737-9303-0acb-44aae0eea1bc.png)
最後に、チャージショットを撃つと同時にチャージ段階の数値をリセットします。
![D07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/7eb2ce3a-7453-83d9-b6ec-fb035aecbe7f.png)
ノードの全体図はこんな感じです。
![D08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3535fb3e-ac22-669f-5613-1b81efacecd7.png)

これで基礎的なチャージショットのサウンドが実装できました。
実際に操作してみると、それなりにゲーム的な気持ちよさが再現できているのが分かるかと思います。


## 補足
チャージ中のサウンドはその場にスポーンしているため、キャラクターが移動しながらチャージ行動をとるとサウンドが置いていかれてしまいます。
スポーンしたサウンドを**Attach Component To Component**ノードなどでキャラクターのメッシュに追従させてあげれば、違和感なくチャージ中のサウンドも同時に移動してくれます。
![E01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/32686bc0-67c5-2611-0503-c02080d83893.png)