ADX+UEで、デバイスの入力強度に応じてサウンドを変化させる

# はじめに
アンリアルエンジン4とサウンドミドルウェア「ADX2 for UE4」を使って、ゲームパッドなどのデバイスの入力した強さにより、サウンドを変化させる実装です。
「トリガーを引く強さによって銃の発射精度が変わり、弾の発射音も影響される」
「スティックを弾いた強さに応じて音の重さが変わる」
などといった、プレイヤーの入力に対するフィードバック手段として有効です。

実装にはADX for UEの機能「AISACコントロール」を使用します。

# 前提

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

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

記事執筆時点のADX 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で効果音を構成する

### マテリアルの用意

AtomCraftを起動し、プロジェクトを用意したら効果音となるマテリアルをインポートします。
![A01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/03fcbe3f-7083-55e8-a458-e07f4a4a77a1.png)
今回は
「トリガー入力の押し込みで音が高くなる機関銃」
「スティック入力の強さで音が二段階に変化するジャンプ音」
を想定し、機関銃のサウンド1種とジャンプのサウンド2種（弱ジャンプ、強ジャンプ）を用意しました。


### キューの作成

マテリアルを再生するためのキューを作成します。
ワークユニットツリーでキューシートを右クリックし、「新規オブジェクト」→「キュー『ポリフォニック』の作成」を選択。
![A02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a3063579-b2ed-74af-5732-fce15eddebf0.png)
マシンガン用のキューとジャンプ用のキューを作成し、それぞれに用意したマテリアルをドラッグアンドドロップして配置します。
![A03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/5549ee36-356d-5daf-c041-de279bf09893.png)
![A04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/ee18da2b-5f0f-1923-cd36-11dbcf80294e.png)
マテリアルがループ用の素材になっていれば問題ありませんが、今回用意したマシンガン用サウンドはループしてほしいにも関わらず、ワンショット用のものなのでループ用のマーカーを加えます。
タイムライン上の空欄を右クリックし、「新規オブジェクト」→「マーカーの作成」を選択します。
![A05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/04ca0b34-87dc-a5fb-5210-af18f73d0ec5.png)
タイプは「シーケンスループ」で「追加」をクリックします。
![A06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/6c140d83-e87c-9686-6c16-dec7579688c6.png)
テスト再生して確認しながら、ループマーカーを適切な位置に持っていきます。
![A07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/8d8c3f89-35a0-fc5b-22f7-6218415758ef.png)
これでふたつのキューが用意できました。


### AISACコントロールの設定（ピッチ）

トリガー入力などに応じて音を変化させるために、AISACコントロールを設定します。
マシンガン用のキューを選択し、トラックリストの空欄を右クリックして「新規オブジェクト」→「AISACの作成」を選択。
![A08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/4c15b23d-bce0-5290-feb7-4951e0c03d1f.png)
マシンガンのサウンドは入力によって音の高さを変えたいので、AISACグラフタイプは「ピッチ」とします。
![A09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/cce46491-8dd6-da7b-38f6-c498231e5d37.png)
AISACリストでピッチを選択し、グラフを編集します。
![A10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/9bc1a07f-58a2-dad8-d14e-c762c42eab24.png)
0に近づくほど少しだけ値が下がっている直線のグラフとしました。
テスト再生中に上部のスライダーを動かすと、音の変化がプレビュー可能です。
ボタン入力をイメージしながら、操作していて気持ちよさそうな塩梅を探してみましょう。
![A11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/51e02eb5-83fe-0b34-e275-9fb9d1fc456b.png)
編集しおえたら、タブを切り替えてタイムラインに戻ります。
![A12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3f99f318-6b03-13c0-7601-66a89e7dbfe5.png)


### AISACコントロールの設定（ボリューム）

今度はジャンプ用のキューにAISACを設定します。
ジャンプ用のキューを選択します。
トラックリストの弱ジャンプ用トラックを右クリックして「新規オブジェクト」→「AISACの作成」を選択。
![A13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a7f9cecd-cb44-3ca3-365e-79128ce5019a.png)
今度はグラフタイプを「ボリューム」とします。
![A14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a9fa8fdb-c0a9-5eca-1a64-14c227b451b1.png)
すぐにタイムラインに戻り、強ジャンプ用のトラックについてもAISACを作成します。
![A15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/fb3c2f5b-2c69-b0bb-431b-95277251d5f6.png)
まったく同じ設定で追加します。ここで同じAISACコントロールを使い回すことにより、ひとつの値でふたつのトラックのボリュームを相互に入れ替えることが可能です。
![A16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/7b7518b5-6cfe-868b-1591-4d93c4ed91b8.png)
ふたつのトラックの数値が真逆になるよう設定します。右側の「ポイントリスト」を使うと詳細な数値が入力できて、設定しやすいかもしれません。
![A17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/22b314bb-c08c-72af-2002-e621e6235edd.png)
画像のような形のグラフができたら、テスト再生して動作を確認します。
![A18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/2fb4842b-d2a2-b115-f070-4026c8610541.png)


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

AISACコントロールが完成したら、UEに持っていくためにキューシートをビルドします。
![A19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/e0405cd5-75f0-fae1-a131-40a7bb3c8db2.png)
![A20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/90732e64-9c19-210e-b354-b81cbb6e7ac8.png)


## UEでの実装

### キューシートのインポート

ここからUEに移ります。
ビルドしたacbファイル、acfファイルをコンテンツブラウザへとドラッグアンドドロップして配置します。
![B01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/48b2a9c9-d133-6f28-4d6c-85eb33805b57.png)
![B02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/8a8bf37c-4582-6847-756b-ea4262313514.png)
プロジェクト設定を開き、
![B03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/64dd7ac4-9bd5-d6f6-0385-4c19b37b784b.png)
CriWareタブの「Atom Config」に今回出力したacfファイルを設定します。
![B04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/ae990d3e-bf02-bf1e-0b92-578d4008b967.png)
ここで一度エディタを再起動しておくことをおすすめします。

### 入力の強度を取得する

入力を取得するため、キー（ボタン）を登録します。
ふたたびプロジェクト設定を開きます。
![B05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/c17d76ea-e7d8-3cc0-85f6-611c1d492392.png)
キー入力の設定は「Input」タブで行います。
![B06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/452b4dac-9f94-c1ba-c6e6-73d3f5bf688c.png)
ボタンを押した/離したといった判定は「Action Mappings」で行い、スティックやトリガーを入力した強度といった判定は「Axis Mappings」で行います。
「Axis Mappings」を追加します。
![B07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/9651e45e-faea-3d5c-c4cd-1d1ce3ad0a65.png)
「Fire」という名前で、ゲームパッドの右側トリガーの入力を登録しました。
![B08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/efe99132-0742-c54a-0a4d-e7978cbcda78.png)
ジャンプアクションに関しては、今回はスティックのX軸とY軸入力を合計する形で判定します。
両方の入力を登録しましょう。
![B09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/5776c3ba-c527-5ee8-3442-f5cce26be9a2.png)
**※既にキャラクターの移動などの動作にスティックを使用している場合、干渉することがあります。どちらかを別の入力にしてもいいでしょう。**


### 入力をテストする

登録したインプットが正常に測れるか、テストしてみましょう。
レベルブループリントを開きます。
![B10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/ad72e144-e5f3-a1dc-4611-30934ab16b24.png)
**Print String**ノードで毎フレームのトリガー入力強度を表示します。**Get Fire（登録した入力名）**で登録したトリガーを押した強さを受け取れます。
![B11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/29e6972d-4203-ce84-61f2-0d96fe38b351.png)
**Get Fire**ノードのプロパティには「Consume Input」という項目があります。これにチェックが入っていると、別の場所からの入力処理を受け付けなくなります。後にキャラクターに処理を追加した際に動かなくなることが想定されますので、**チェックは外しておきましょう**。
![B11b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/0d702b39-8683-776c-15f2-e923724b2a0f.png)
ゲームを再生しゲームパッドのトリガーを入力すると、押した強さが表示されるようになりました。
![B12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/6f900967-fe2d-6f60-39c6-41ed244c48df.png)

同じようにスティック入力も計測してみます。
「StepX」「StepY」の入力をとり、**abs**ノードを噛ませます。これは受け取った値を「絶対値」に変換するものです。スティック入力は逆方向に倒すことでマイナスの値になるため、入力強度を知りたい場合はこのノードで正の値にすると分かりやすいです。
![B13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d03eeb1d-f9a2-fc38-673e-bf6ed10238b8.png)
合計した値は入力方向によっては1を超えることがありますので、**clamp**ノードで最大値を設定して丸めます。
![B14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a22648bd-f871-ed1f-93b9-3636e603ff89.png)
これでトリガーとスティックの入力がそれぞれ表示されるようになりました。


### 強度に応じてサウンドを変化させる（機関銃）

キャラクターのブループリントに入力処理を追加します。

処理が長くなるためこの記事ではマクロ化していますが、そのまま記述しても動きます。
マクロはMy Blueprintパネルから追加できます。
![E01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/19f97be2-a968-c035-1de6-8d0d9e4a5f00.png)
作成したマクロを選択し、DetailsパネルでInputsとOutputsを追加します。
「Value」には入力強度の数値を渡します。
また、アクションが成功したかどうかで処理を分岐させたいため、Outputsには「true」「false」のふたつの出口を用意します。
![E02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/6b6c4657-4fc2-e992-6f0e-d53fdcd8805f.png)


発射処理用の変数を追加します。
Vector型の「FireLoc」、float型の「FireCount」「FireRate」です。
「FireRate」は弾の発射間隔の最速値です。今回は「0.1」としました。トリガーの最大押し込みにより、0.1秒間隔の発射となります。
![C01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/9cdb3287-d7a1-96b4-2a60-2101908249b1.png)
まずは入力強度の数値を受け取り、発射処理を開始すべきか判定します。
ここで分岐させないと、数値を受け取っただけで発射判定になってしまうため常に機関銃を撃ち続けることになってしまいます（数値が0でも処理は行われる）。
今回は入力強度が「0.1」以上で処理に移ることにしました。
![C02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/f8ae2677-af9e-e75e-c21d-dc4d534a328a.png)
もし機関銃のサウンドが停止されている場合、Atomコンポーネントの再生を行います。再生状態の取得は**Get Status**ノードで行えます。
![C03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/16f8d24e-dd7f-fba0-a4a2-94bb5d19d8de.png)
変数「FireCount」を減算し、0以下になれば弾をスポーンさせます。押し込みの強さをDelta Timeにかけ、押し込みが強いほど減算を速くし、弾の発射間隔も狭まるようにします。
![C04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/73868f21-ab17-fa4c-0663-620531bf64bf.png)
「FireCount」に「FireRate」を代入し、発射間隔をリセットします。**Spawn Actor**で弾アクターをスポーンさせます。
「FireLoc」は弾の発射位置（スポーン位置）となります。
**OutPuts**ノードへと線をつなぎ、マクロ外へと処理を続けます。
![C05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/10773344-3b2b-decd-0435-4379d48c3cca.png)
入力強度が0.1未満だった場合、機関銃のサウンドを停止します。
![C06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/8e48eda2-09e8-fdc1-8fce-ace5539ff769.png)
停止後、**Outputs**へと処理を続けます。
![C07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/719c033f-c217-1efa-3278-440c10c52af8.png)
マクロ内の処理の全体図です。
![C08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/72876c39-7bbb-1e63-07d1-cc3b764d0737.png)


入力処理に戻り、**Set Aisac by Name**ノードで「Axis Value」を渡してあげればサウンドが変化します。
![B15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/6849269a-adfe-94ca-002f-026a6cbeb89e.png)


### 強度に応じてサウンドを変化させる（ジャンプ）

ステップジャンプに関しては先ほどのテストと同様、合計して値を丸めておきます。
![B16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/e6b2af2f-36c0-eed0-3644-ce71ba34b68d.png)

マクロ内グラフを書いていきます。
入力強度が0.2以上であればステップジャンプ処理を開始します。
![D01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/0d314149-96a3-7b44-f4bc-e277d56b3348.png)
アクションの条件として、キャラクターが地上にいる状態かを判定します。
キャラクタークラスであれば、**Character Movement**コンポーネントから線を引き出し**Is Falling**ノードでキャラクターが落下中（空中にいる）か取得できます。地上にいる状態をTrueとしたいので、**NOT**ノードを噛ませて**Branch**ノードの条件式とします。
![D02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a143f474-1994-bd1c-341f-a77fb1f56188.png)
Trueであれば**Launch Character**ノードでキャラクターをジャンプさせます。
![D03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/f5aeb7ef-ec60-da3d-89fc-bbdcf5a6d1e2.png)
ジャンプするベクター（方向と強さ）を算出するには、次の画像のように計算します。
キャラクターの回転を取得し、**前方ベクター×ステップ基礎速度×入力強度+ジャンプする高さ**です。
実装例ではステップ基礎速は「4000」としてあります。
![D04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d2b0d4e5-7e7c-fbfd-9f3e-d6cb923790aa.png)
ステップ実行後、**Outputs**ノードの「True」へとつなぎます。
![D05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/084b06a3-9b10-a02f-53ac-81da94a84002.png)
入力強度不足、またはキャラクターが空中にいるなどでステップジャンプが成功しなかった場合は、Falseへとつなぎます。
![D06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/8d70457f-f930-54da-dbc4-d198ab829c66.png)

マクロから出て、イベントグラフへ戻ります。
**Spawn Atom Sound Location**でAtom Cueをスポーンさせて再生します。「Auto Destory」（再生終了時にコンポーネントをデストロイする）にはチェックを入れておきましょう。
![B17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/5dcaf1dc-ad81-2434-0acf-bbe3ad3175be.png)
最後にスポーンしたサウンドに対して、**Set Aisac by Name**でサウンドを変化させてあげましょう。
![B18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/12c731db-7955-cabc-6108-4cf08fb0eeb5.png)

プレイヤーの入力に対してサウンドで応えてあげることで、操作がしっかりと伝わっている実感やプレイングの気持ちよさにもつながります。
細かい演出ですが、プレイ中何度も使うアクションだからこそ凝ってみたいところですね。