ADX+UE5で、プロシージャルなアニメーションでサウンドを再生する

# はじめに
アンリアルエンジンとサウンドミドルウェア「ADX for UE」を連携させ、キャラクターが動いた際のサウンドを**アニメーションに埋め込むのではなく、物理ベースで再生**してみる試みです。

キャラクターの足音や握っている武器を振るといった動作音はアニメーションに埋め込んで再生することが多いですが、昨今の3Dゲームではキャラクターの動きをプロシージャルに生成していることも少なくありません。
プロシージャルなアニメーションでは、事前にキャラクターの動きを決め打ちできないため、自ずと任意のタイミングでサウンドを再生することも難しくなります。



そこで、「キャラクターがどんな動きをしようとも、物理を元にサウンドを再生してしまう」という設計をし解決してみることにしましょう。
たとえば「**キャラクターの足と地面が衝突したら足音を再生する**」「**キャラクターの手が何かに触れると音が鳴る**」といったようなアプローチをとります。
これらはプロシージャルアニメーションだけでなく、プレイヤーの動きが予測できないVRゲームなどのモーションコントローラを使用したゲームにも対応可能です。


# 前提

当記事では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


# 実装

## 足音の実装

単純な足音の実装をしてみましょう。

### アニメーションブループリントを編集する

キャラクターのアニメーションブループリントを開きます。
サードパーソンテンプレートでは「ABP_Manny」が該当します。
![A01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/7c45a900-4aa0-ca4b-f32a-450b868dd1ef.png)
「EventGraph」をダブルクリックしてイベントグラフを開きます。
![A02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/36002aec-a119-c856-492d-5d55ace7c2e2.png)

### プレイヤーキャラを変数に格納する
まず（ゲーム開始時に）プレイヤーキャラとなるアクターを変数に記憶したいので、**Event Blueprint Begin Play**イベントノードを配置します。
![A05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1facccfe-b24f-7299-52fc-7eb5f9f1a5fe.png)
**Try Get Pawn Owner**でこのアニメーションブループリントを使用しているアクターを取得し、**Cast To （プレイヤーキャラのアクター名）**で正しいアクターであることを照合します。
![A06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d1f99403-1096-a31d-0ba8-81bf9d8c0a74.png)
**Cast To （プレイヤーキャラのアクター名）**の青いアウトプットピンを右クリックして「Promote to Variable」で変数に格納します。
![A07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/4adbc147-63ad-410f-1944-2c9397ebef8f.png)
変数に名前をつければプレイヤーキャラを変数に格納する工程は完了です。
![A08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/43a97e30-f6dc-88d9-a384-06894cd8defe.png)

### 地面をトレースし、足音を再生する

**Event Blueprint Update Animation**イベントから処理を書いていきます。これは通常のアクターにおける**Tick**イベントと同じ感覚で使えるものです。
既にイベントがある場合は**Sequence**ノードなどで整理した上で処理を追加するといいでしょう。テンプレートのアニメーションブループリントでは既に**Sequence**ノードが置かれていますので、ピンを追加して処理を書くことにします。
![A03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/4e00f135-0361-3749-8438-58b06876cda1.png)
右足、左足で処理を行いたいので、グラフを整理するためにさらに**Sequence**ノードを置きます。
![A04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/31f1b6e6-8802-0483-40f7-a42def03926c.png)
変数に格納したアクターのスケルタルメッシュを**Get**し、そこから**Get Socket Location**でボーンの現在座標が取得できます。「In Socket Name」にはボーン名（またはソケット名）を入力します。
![A09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/782ff670-2360-295b-d7a8-363d38fe6908.png)
地面と接地したかを判定するためには、今回は**Line Trace By Channel**ノードを使用します。
「Start」はボーンの現在位置、「End」はそこから少し下方を参照した位置にします。
テスト用にDraw Debug Typeを「For Duration」にしておきましょう。
![A10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/e748d5a0-2029-a9e7-52fa-f48edb3fe278.png)
この段階で一度コンパイルしてテストします。
足からトレースされたラインが伸びているはずです。もし判定が長すぎると感じたら、数値を下げてみてもいいかもしれません。
![A11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1ceafc0e-5749-523d-852a-f5fac9d71ebf.png)


地面を感知した際の処理を追加します。
接地したかどうかで分岐する**Branch**ノード、衝突情報を取得する**Break Hit Result**ノードをつなげます。
![A12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/5bb9095a-f2f3-2f4a-265d-14f8bab5df58.png)
衝突したら**Play Atom Sound at Location**でサウンドを再生します。
![A13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/552be0c7-255d-882a-1704-066421e791af.png)
これでは衝突する限りサウンドが再生されてしまい、つまり移動している状態でも凄まじい速度で足音が鳴ってしまいます。
これを解決するために**Do Once**ノードを噛ませます。
このノードは「Reset」ピンに処理が流れるまで以降の処理を繰り返しません。
次の画像のようにつなげば、接地したら一度だけ音を鳴らし、一度足が地面から離れる（トレースがヒットしなくなる）まで足音を鳴らす処理を行いません。
![A14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/086606c4-0b8b-8214-82f1-905ef061478c.png)

足が衝突した地面の材質によって足音を変化させることもできます。
これについては過去の記事で詳しく解説していますので、必要に応じて参照してみてください。

https://qiita.com/SigRem/items/9f63fdd54e0faaee14bc

![A15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/24b608a7-93ff-b298-1bc7-0e90827127c3.png)

これで左足の処理は完了です。同じものをコピペし、右足の処理も追加します。
![A16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a3d13f23-89bb-7fbe-cfc2-4462946d7527.png)
もちろん**Get Socket Location**の参照ボーン名は右足ボーンの名前を入力しておきます。
![A17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/0ca9bb44-7b85-8318-bb29-a7b4dd1ba317.png)
これで足音は完成です！両足が接地したときのみ足音が再生されているはずです。
![A18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/89939bed-32ea-c2b8-8c04-0e56b3a5e86f.png)
テストが済んだらDraw Debug Typeを「None」にしておき、デバッグ描画をオフにしておきます。
![A19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1146b128-c6e9-b45e-1270-34e353992b44.png)




## 手、武器などの衝突の実装

手や武器など、任意の場所につけられる衝突判定を作ります。

### 判定用アクターの作成

ブループリントクラスを作成します。
![B01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a044cb3d-d886-c244-34e2-53c48b438ed8.png)
親クラスは「Actor」とします。
![B02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a04a6843-e06d-a563-ec55-6d1956947532.png)
任意の名前をつけます。
![B03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/bd222202-6f67-7fd0-e10e-6b653fbadef4.png)
アクターを開き、「Sphere Collision」コンポーネントを追加します。
![B04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/873ba463-d4f7-d20c-f3a0-ad9393366cca.png)
これはアクターがキャラクターに追従しているかをテストするためのものです。
デバッグ用描画のために「Hidden in Game」のチェックを外します。
![B05b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d74cd7e7-ef94-5e03-f854-adb88365939f.png)

### Traceによる当たり判定

イベントグラフに移動し、変数を追加します。
- 「**IgnoreList**」: Actor型の**Array**です。衝突判定に含めたくないアクターを登録します。
- 「**Velocity**」: Float型です。速度を格納します。
- 「**PreFrameLocation**」: Vector型です。直前フレームの座標です。速度の算出に使用します。

![B07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/cdb5c669-3f95-926b-59dd-89e6c64f19ff.png)

「**IgnoreList**」がArrayになっていることを確認しておくと良いでしょう。
![C01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d30b8948-1d46-5096-8fb3-85249f7ca5c5.png)


**Event Tick**からのイベントで、毎Tick移動方向と移動量を取得します。

現在のフレームの座標と前フレームの座標を比較し、フレーム間の移動量を算出します。
その後、次フレームの計算のために現在座標を変数「PreFrameLocation」に格納しておきます。
![C02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/9ae75bbd-4dd0-f74b-ca12-59b73525fb56.png)
これらを順番づけて処理させるため、**Sequence**ノードを置きます。
![C03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/667559de-8554-04f9-0645-b2c7f8faa72e.png)
移動方向を算出するには**Normalize**ノードを使用します。
![C04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/84dad0da-bf48-7f43-9f4e-9945ce5bcf08.png)

当たり判定を取得するため、**Line Trace by Channel**ノードを置きます。
![C05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/991b9050-1d7b-64a6-039e-082ab94e92d9.png)
アクターの移動方向（＝手や武器などが移動した方向）の判定をとるため、「**現在座標＋移動方向に少し移動させた座標**」を取得します。
**Vector*Vector**ノードを配置します。
![C06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/184e9df6-62ea-50c4-5ef7-39626252c541.png)
**Vector*Vector**ノードの黄色いインプットピンの下の方を右クリックし、「Concer Pin」→「Float」を選択します。
![C07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/fd4062f9-b9ec-7cc6-ded7-3424bf422375.png)
下のピンがFloat型を示す黄緑色になりました。試しに「50.0」と入力します。
![C08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/dedbee21-1ad5-20a1-d8d0-f09dc9fafecb.png)
トレースのスタート座標を**GetActor Location**、目標座標を「**現在座標+移動方向*50**」とします。
![C09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/67c95086-3cea-d80b-f2bc-2837989c51fb.png)
デバッグ用にDraw Debug Typeを「For Duration」にします。
![C10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/c78f0ae3-91f7-1980-8eda-07b076e0319a.png)
ゲームを再生すると、当たり判定処理が毎フレーム行われていることがわかります。
![C11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/6e0a2771-3c93-99e9-fbf9-47a54e0e89d5.png)

**「IgnoreList」**変数を「**Actors to Ignore**」ピンにつなげます。これにより、「IgnoreList」に格納されたアクターはトレースの対象にならなくなります。
当たり判定の大きさも「10.0」程度にしておきます。
![C11b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/031af5c1-b9dd-e0b1-6479-2b79ccac1836.png)



### アクターのアタッチ

レベルブループリントに移動します。
![B14a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/f7644e1a-4d0c-d104-62a6-006edcaaab84.png)
プレイヤーキャラクターを取得し、**Cast**してクラスを照合して変数に格納します。
![B14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3659ef34-9a55-529e-0e61-6004472cb1cd.png)
衝突判定用アクターを**SpawnActor**でスポーンします。
![B15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/f42c1c90-1110-b598-952f-ee790a207ebf.png)
スポーンしたアクターをプレイヤーキャラの左手にアタッチ（追従）します。
Locaiton Rule、Rotation Ruleは「Snap to Target」にしておきます。
![B16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/b56ac357-ea01-6d22-54cb-ff8f9fc266aa.png)

ゲームを再生してみましょう。
キャラクターの手に球体が追従し、他のオブジェクトと重なると名前が出力されるでしょうか。
![B09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/84f82038-44bd-28be-b174-22369affe348.png)
もし動かない場合、衝突対象のオブジェクトのDetailsパネルから「Generate Overlap Events」にチェックを入れてみてください。
![B10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3d52d18f-8ef2-f4d1-14d1-74886468e021.png)


### 速度によるサウンドの分岐

**アクターのイベントグラフ**に戻ります。

トレースが接触した場合の処理を作るため、**Branch**ノード、**Break Hit Result**ノードを配置します。
![C13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a1dcd296-a2ab-dfc4-c95c-3f25f97cc87a.png)
トレースが接触したら**Play Atom Sound at Location**でサウンドを再生します。
![C14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/8844ec5e-a174-5532-9007-045d9787ccfb.png)
「Sound」のインプットピンをドラッグして線を伸ばし、**Select**ノードを配置します。
![C15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/4cd8039b-649f-41e2-dfe0-6013f21fd6d0.png)
条件を設定し、サウンドを分岐させます。しきい値となる速度についてはテストしながら調整してみてください。
![C16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/52c449c7-4eed-f944-b65b-ceaf60866d9f.png)
足音のときと同じように、**Do Once**ノードを挟んで、一度接触したら離れるまでサウンドが再生されないようにしておきます。
![C17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/b11a1518-65a9-4625-3aaf-1c981b08ae5f.png)





### アタッチ座標の改善

当たり判定を小さくしてみると、アタッチ場所が少しずれているのが分かります。
![B17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d42a71de-ea38-9aca-6a60-9c9f52e104ce.png)
アタッチ時に**Make Transform**ノード内で座標に補正をかけておき、**Attach Actor To Component**ノードのLocation Ruleを「Keep Relative」に変更するとアタッチする場所をずらすことができます。
![B18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/78c65022-976b-634f-ab28-22979a8e94c1.png)
手の大きさと判定が大体一致しました！キャラクターが持っている武器の先端に判定をつけたい場合などはこういった方法で補正をかける必要があるかもしれません。
![B19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/2e198117-6ad0-7d59-acf9-b00525819477.png)


## 補足

こういった手法で、キャラクターの要所（たとえば装備している武器など）からトレースを行い、サウンドを再生すれば「派手に動いているのにサウンドが鳴らずに違和感が出る」といった問題を解決できるでしょう。

また、VRゲームにおいては「自分の動きで衝突音が再生される」ことが圧倒的な没入感を生みます。
こちらも試してみると面白いかもしれません。

ただし、ジャンプなどの瞬間的な動作音はプロシージャル生成でうまく鳴らすのは難しいです。状況に応じて決め打ち・プロシージャルを使い分けていきましょう。