UE5 + ADXで作る、ノベルゲームのサウンドテクニック（基本編）


ADXアンバサダーとして記事を書いておりますSigと申します。
この記事ではアンリアルエンジン5とサウンドミドルウェア「ADX for UE」を連携させ、ノベルゲームのサウンド演出を強化するテクニックを紹介します。
今回は前編として、実装頻度が高く、演出効果の高い基本テクニックに絞って紹介します。

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

# 前提
「ADX for UE LE」を使用します。導入や簡単な使い方は以下の記事にあります。
ADX for UEの導入で、一歩上のサウンド表現を（導入編）

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

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

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


# 実装

## 2Dキャラ × 3D背景でも違和感のない音空間をつくる

UE5ではFabにて高品質なアセットが提供されており、背景を作るのに有用です。

Fab https://www.fab.com/ja

「キャラは2Dだが背景は3D」といったシーン作りもUE5ではじゅうぶんに可能です。
ただし、この場合ではキャラクターの配置と音の定位に違和感を抱かせないような工夫・調整が必要です。
ここでは、
- すべてのサウンドを2Dで再生する
- キャラクターのボイスに定位を持たせる
2つの方法について見ていきましょう。

### すべてのサウンドを2Dで再生する

通常のノベルゲームのように、すべてのサウンドを方向や距離による減衰なく再生する方法です。どのような位置関係でも聞かせたい音声を届けることができるため、比較的オーソドックスな形式といえます。

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

減衰のないサウンド（空間の状況を加味しないサウンド）は、**Play Sound 2D**ノードで再生することができます。
![A01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/aee287e0-d24b-4928-8a6f-61c3c25fc940.png)
声とは別に背景の音や、空間を加味する効果音の再生には通常どおりの**Play Sound at Location**などを使用すると局所的に立体的なサウンド演出が得られます
![A02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/52663de9-0696-4038-b442-d14314f2e02e.png)


### キャラクターのボイスに定位を持たせる
こちらは、キャラクターのボイスを「キャラクターの話している位置」から聞こえるようにする方法です。
カメラを動かすことができたり、画面内でキャラクターが移動したりといったシーンに臨場感を持たせられます。

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

![A03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/7207762d-597d-448c-b61b-5a13cf594390.png)

![A04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/eff7c49b-6de2-4c4e-9450-5476226f9939.png)

リッチにも聞こえるこちらの表現ですが、ノベルゲームで重要な「キャラクターの台詞」をプレイヤーが聞き逃さないよう減衰には気を使う必要があるかもしれません。後述のAttenuation（減衰）の設定などを利用し、物語体験を担保しましょう。


### リスナーの位置を設定する

「リスナー」を設定することで、見せかけの空間とは別に聞こえ方を設定し、よりデザインされたサウンド体験を実現することもできます。
リスナーはサウンドの聴取位置のことであり、通常はカメラ位置にあります。
これを登場人物全員の声が聞き取りやすい位置などに移動させることで、柔軟に聞こえ方を設定できます。

通常は一体化しているカメラとリスナーを……
![Listener_01_A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/c7a75596-04f6-f2fb-754d-ae020320751b.png)
分離して独自のカスタマイズに！
![Listener_01_B.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/03c7077f-8232-ec3c-6a06-913f4d918fc5.png)

リスナー位置を分かりやすくするためのアクターを作成します。
コンテンツドロワー（コンテンツブラウザ）で右クリックし、「Blueprint Class」を選択。
![A05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/5e4b4b95-f429-4a90-9236-33a31d5b6432.png)
親クラスは「Actor」とします。
![A06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1a465a8b-049e-4681-9ea8-53ad5a99cf5f.png)
適当に名前をつけます。今回は「BP_ListenerPoint」としました。
![A07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/52941813-4117-40fe-bd69-55653dcef544.png)
レベル上に置いたときに視認性が向上するよう、「PaperSprite」コンポーネントを置き任意の画像を設定します。
![A08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/848747d2-0658-415f-bf82-412bc992ef29.png)
レベル上では設定された画像が表示されるため、多少分かりやすくなります。
![A09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/aac72211-a8db-49c0-9c22-21e8b50358bd.png)
レベルブループリントなどから**Get Player Controller**ノードを置き、そこから線を伸ばし**Set Audio Listener Override**でリスナー位置を上書きします。対象は「BP_ListenerPoint」のRoot Componentがいいでしょう。
![A10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/05ea96f2-1b97-4494-9762-707f606249dd.png)


リスナーについて詳しく解説した記事はこちらになります。
動画つきで違いを見ていますので、流し読みでもイメージが湧くかもしれません。

https://qiita.com/SigRem/items/43e72bf15b4bb9813b14

### Attenuation設定

ピンポイントで減衰を設定したいけどAtomCraftで微調整するほどでもないな……という場合には、Attenuation設定をUE側で上書きする方法もあります。

配置したキューごとに手動で設定をしたり、減衰設定をまとめたアセットを作り使い回すことも可能です。
![A11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/08f024e7-0342-4753-a03c-4f211ba54f9d.png)


減衰設定の詳しい実装法やパラメータ設定については、こちらの記事をご覧ください。

https://qiita.com/SigRem/items/61202791968bfd168bd2


## ボイス終了を待って進行制御（キー入力待ち／自動送り）

キャラクターの台詞の再生が終わるとページ送りを促すアイコンを表示したり、自動で次の台詞の表示/再生を行うテクニックです。

こちらは単体の記事で詳しく解説していますので、参照してみてください。

https://qiita.com/SigRem/private/e00e652a0b28d0c0762b

## 台詞を一文字ずつ表示する方法と、その効果音の再生

また、ボイスがない文章において、文字が一文字ずつ表示されるたびにSEを鳴らしたり、タイプ音を再生する演出もよく見られます。

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


再生タイミングが密になるため、SEが重ならないよう極々短いSEが望ましいです。
タイピング音などは多少のピッチ幅を持たせたり、キュー側でランダムな音を再生すると、同じ音が機械的に連続している感が抑えられて自然になります。

UIでのメッセージ表示時に、テキスト更新処理の代わりに表示予定のメッセージを変数として格納します。
メッセージを一気に表示する処理では次のようになります。
![B01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/c1b25572-4fb6-4856-ba1f-0ae2aca1dc34.png)

メッセージの入力ピンを右クリックし、「Promote to Variable」を選択して変数化します。
![B02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/ac4ad926-9f6c-4ecd-b0db-74bf035e04d4.png)
![B03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/4026dd8c-ea48-4257-8466-41418dee407c.png)
変数「CurrentWordCount」を追加します。型はIntegerです。
この変数は「現在の表示文字数」になります。
![B04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/f8ce1d40-8ba4-4e1b-ab25-128ec10e6638.png)
メッセージの入力時にCurrentWordCountを0にします。
![B05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/3187f08a-ed32-4a82-b3aa-2e29ff46bcf2.png)
新規にカスタムイベントを作成します。名前は「AddWordCount」としました。
![B06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a32f32ee-0e81-4e19-8d00-41a5fffcd1bc.png)
このイベントは表示文字を加算するものです。
「CurrentWordCount」を加算し、テキストを更新します。
![B07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/5686bdd5-9b52-4c55-9a2f-91c00d1dd8da.png)
「MessageText」をString型に変換します。
![B08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/74e42696-0c04-40aa-911c-44bb0c983211.png)
文字列を指定数の文字だけ切り出す**Left**ノードを使い、「CurrentWordCount」ぶんの文字を表示します。
![B09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/fb232b28-8c51-42de-bbd1-3bcc30a2a8e3.png)
文字の表示間隔を指定します。
**Set Timer by Event**を配置し、「Time」に表示間隔を、「Looping」にチェックを入れます。
![B10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a42d5313-79a4-46a7-abf8-848baf18df9c.png)
**Create Event**ノードでイベントを指定します。対象は「AddWordCount」です。
![B11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/06fb40a4-dd28-495d-aff3-eb637a1ece25.png)
文字がだんだんと表示されていくようになりました。
![B12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/31958d23-9609-43d8-822f-32c0b745e8d2.png)
表示間隔が長すぎるので、「Time」の値で調整します。
![B13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/931a84f1-2b9d-417f-a0df-fc546b3a57c0.png)

文字表示ごとに効果音を再生するには、イベント「AddWordCount」ごとに**Play Sound 2D**ノードを実行すればOKです。
![B14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/04ed5c11-7da4-4801-b763-41c75cd80fa3.png)
キューのインプットピンを右クリックして「Promote to Variable」を選択し、変数化しておくと便利です。
![B15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/c7b50b69-c408-4ef4-8693-feed91b4e36c.png)
文字表示のSEが変数化したことで、「キャラクターごとに文字表示SEを変える」「過去の回想のみSEをタイプライターの打鍵音にする」などの演出が可能です。
![B16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/10efaff1-951e-4b88-ae77-51a8f19302d5.png)

ただし、このままでは文字表示が台詞末尾まで終わったあともSEが流れ続けてしまいます。
「CurrentWordCount」が台詞の全体文字数未満の場合のみ、このイベントを実行するようにすれば完璧です。
![B17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/26f29157-a67a-4811-85aa-0909eff72ee3.png)



## 選択肢のサウンド演出

「選択肢」はノベルゲームにおいて、プレイヤーとの重要なインタラクションになります。
ボタンの選択時だけでなく、カーソルの移動（マウスオーバー時）などにもサウンドを再生し、レスポンスを返してあげると操作体験が格段に向上します。

選択肢のUIづくりから始めていきます。
コンテンツドロワー（コンテンツブラウザ）で右クリックし、「Use Interface」→「Widget Blueprint」を選択します。
![D01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/a18f87c2-31ff-472e-b46d-da0c322189a8.png)
親クラスは「User Widget」です。
![D02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/281eb04c-238f-4eff-8e58-8ef2b468483b.png)
名前を適当につけます。今回は「WBP_Selection」としました。
![D03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/8c502fec-5bec-4bb9-a540-2e21595f260b.png)
まずは基本となる「Canvas Panel」を配置します。
![D04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d71f6459-798b-4741-8830-a40e5cecb48b.png)
選択肢を縦に並べるパーツである「Vertical Box」を配置します。
![D05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/89b315bf-9084-4aee-94dc-04c7fd876680.png)

「Button」を複数配置し、その中に「Text」を子として配置します。
![D06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/ed4714b6-8765-44b4-8bbf-fa3d13294673.png)

選択肢ボタンの表示間隔を指定する「Padding」を上げ、多少の間が空くようにします。
![D07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/41ad29fa-3ba3-41c4-944a-a80771d9a319.png)
ここで「Size」を「Fill」にすると、「Verical Box」の領域いっぱいまでそれぞれの要素の大きさが拡大します。
![D08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/d0f7adb3-30c2-4ce8-bd6d-f0caa99ae6dc.png)
「Verical Box」のパラメータを設定し、画面上でのレイアウトを決めます。
![D09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1dac4d58-6923-40ed-9446-83caa5f37786.png)
「Anchors」は画面中央にし、ユーザーの画面環境によって表示がずれないようにしておきましょう。
（中央に設定しておくと、ウィンドウの大きさに関わらずウィンドウ中央を基準として表示されます）
![D10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/57da098c-da97-407f-819d-d102407f175e.png)
イベントグラフに移動します。
![D11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/2c86ce11-a841-4285-9850-4ec2911b1e79.png)
My Blueprintパネルのそれぞれのボタンを選択し、「On Released」「On Hovered」の＋ボタンを押してイベントを追加します。それぞれボタンをクリックして離したとき、ボタンにカーソルを載せたときのイベントになります。
![D12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/5d122c02-dbf1-45eb-b35c-40e6d1e6c0bc.png)
それぞれについて、対応するキューを指定しサウンドを再生します。
![D13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/7419dd51-96e3-41a1-9cb7-6c977462fbea.png)




ノベルゲームのサウンド実装について、基本的なところを見てきました。
ただ、まだキモとなる部分があります。それは「大量のサウンドの管理」です。

次の記事では、ノベルゲーム特有の大量のボイスデータを再生しつつ、台詞と紐づけて管理し、処理が煩雑にならないような設計方法を見ていきます。

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