UE5 + ADXでつくる、ノベルゲームの台詞・ボイスデータの管理Tips


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





# なぜ台詞をデータテーブルで管理するの？

ノベルゲーム製作では、膨大な台詞・音声をまとめて管理する必要があります。
他にも、演出ごとに専用ロジックを作っていると処理が煩雑化してしまい、不具合の温床になるなどの懸念もあります。

これらを解決するために、台詞データを構造化し、スプレッドシートで管理 → UEに取り込み → 処理を実行 というフローを作るのが理想です。

データテーブルはデータを表として記述しますが、そのために**どのような情報を扱うか**という「構造体」を作っておく必要があります。


# 構造体の設計例と役割

ノベルゲームにおける基本的な情報を扱うため、次のような構造体を構成してみましょう。

| 変数名 | 用途 |
| ---- | ---- |
| ID | 行の識別子、通し番号やイベント名など |
| SpeakerName | 台詞における、話者名の表示に使用 |
| DialogueText | 実際に表示する台詞テキスト |
| CueName | Atom Sound Cueの名前（文字列） |



こういった構造体を元にデータテーブルを作れば、スプレッドシートで1行ずつイベントを記述できます。

通常ボイス用アセットなどはリファレンスを指定する関係上、外部のスプレッドシートで記述することは難しいですが、ADX for UEでは文字列からキュー名を指定して取得できる機能があるため、取り回しが効くようになっています。

構造体を作ってみましょう。

コンテンツドロワー（コンテンツブラウザ）を右クリックし、「Blueprint」→「Structure」を選択します。
![A01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/2f863f73-4f2d-4d7e-a161-79ad80e8007b.png)
名前をつけます。今回は「FScriptRow」としました。
![A02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/f2815951-3c76-4b2c-b38d-3bf4bf6776dd.png)
ダブルクリックして開くと、構造体が持つ変数を確認・編集できます。
![A03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/74c6c314-2ddd-4d28-8d58-42308a229015.png)
変数を追加し、次のようにしました。データテーブルに欲しい情報を登録していきます。変数の型を間違えないように注意！
![A04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/72204537-4f0d-4790-8e1d-05dbe41a981c.png)
作成した構造体をもとに、データテーブルアセットを作ります。
コンテンツドロワーで右クリックし、「Miscellaneous」→「Data Table」です。
![A05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/22f0901e-2fbb-434e-b150-2eb36de51ab0.png)
データテーブルが扱う構造体には、先ほど作った「FScriptRow」を指定します。
![A06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/68583ee6-e6de-4981-9dfd-f1f1814da1a6.png)
分かりやすいよう名前をつけます。
![A07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/bd7538cf-577b-4b46-a23c-a3b75859665f.png)
データテーブルが作られ、編集できるようになりました。
![A08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/96b2cb08-1a88-44e8-96b9-e28d0b2bd79c.png)

……が、UE内のエディタで編集していくのはちょっと面倒なので、外部のスプレッドシートで記述してみましょう。



# 外部CSVからデータテーブルを生成する

制作フローにスプレッドシートを取り入れることで複数人での編集を簡単にできるほか、UEを触らないシナリオライターも直接の台詞編集が可能になります。

今回はGoogleスプレッドシートを利用します。Excelなどでも同様の手順で反映させることができます。
スプレッドシートを新規に作成します。
![B01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/37482a45-007c-4eb1-a946-5362b6115d50.png)
開きます。
![B02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/bf2d9156-30c2-44e7-8a70-dd34f8ffdd83.png)
シナリオの流れに沿ってコマンドや話者、テキスト内容、そして音声のキュー名を記述していきます。
コマンドは後ほど実装していきますが、文字列なのでバリエーションを作りやすいでしょう。
![B03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/116adb89-5843-4026-9571-473c21129099.png)
一通り書けたら、「ファイル」→「ダウンロード」→「csv」でcsv形式でエクスポートします。
![B04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/bb483816-c05b-48da-b239-d643b995b50a.png)

コンテンツドロワーにドラッグ&ドロップしてインポートします。

![B05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/78c56589-7aac-49d3-89ab-dd80532aea9b.png)

インポート時にベースとなる構造体を選択します。
![B06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/4ecf7a6e-74a4-4345-84c0-a19619162069.png)
編集されたデータテーブルがインポートされました。
![B07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/6d68a244-8f64-486f-81a7-8869090c6ab9.png)



# キュー名から音声アセットを取得する（Blueprint）

データテーブルにはサウンドアセットそのものではなく、文字列でCue名を記述しています。これにより、外部CSVでも安全に記述・編集が可能になります。
データテーブルに記述されたキュー名から、対象のキューを取得してみましょう。

データテーブルのデータを読み出すには、**Get Data Table Row**ノードを使用します。
これはデータテーブル内の行を指定して、構造体を出力するものです。
![C01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/4bada12e-e4c2-4f15-aab6-3ff6ebf6665e.png)
青いインプットピン「Data Table」を右クリックし、「Promote to Variable」を選択して変数化します。
これにより、シナリオや章ごとにスクリプトのスプレッドシートを分けることが可能になります。
![C02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/eca092a9-0c82-45e0-a45e-5f88b5faa0d9.png)
変数名をつけます。
![C03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/96348bdb-0e84-4ace-9a4f-0a8c58491ac1.png)
一度コンパイルして、デフォルト値に今回インポートしたデータテーブルを指定します。
![C04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/eb1a4073-1c40-420f-9967-206924af5fd4.png)
Integer型の変数「CurrentScriptID」を作成します。これはこれから読み出す行を指定するための変数です。
同じように組んでいると、Row Name（行の名前）は0から始まるIDになっているはずです。
String型に変換し、読み出す行の名前として指定します。
![C05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/6f9aa2e4-be7d-4631-bdd1-5aedcfa22b6c.png)
**Break FScriptRow**ノードを使い、構造体を分解して中身の変数を取り出します。
![C06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1285c197-67ce-41bb-83a7-69b5929c3154.png)
まず行うのが、Command名による処理の分岐です。「Talk」なら会話の表示、「PlayBGM」ならBGMの再生……といったように、異なる処理に分岐させます。
![C07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/20ddf689-56f6-4f46-9568-c4974ab7c1ce.png)
Commandが「Talk」であれば、新しいテキストを表示する関数を呼び出します。
![C08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/1d200207-9b84-4ac2-8d49-0904ef7a193c.png)
今回は話者を表示する専用のレイアウト領域を作っていないので、文章の初めに表示し、そこから改行して台詞を表示するという形にします。
改行するために**Append String**ノードを使用します。
![C09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/008049ad-72c6-47ac-8cc5-0a27315ce881.png)
台詞表示時に音声となるキューを読み出すため、変数「CurrentCueSheet」を作ります。型は「AtomCueSheet」です。
![C10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/28909afe-0a91-443c-bf4b-ad1c9acd2f08.png)
**Get Sound Cue by Name**ノードで、「CueName」の文字列をキュー名として取得します。
![C11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/2f641473-300f-41b2-afc9-e7aa0c745994.png)
台詞が表示され、台詞が表示されるようになりました。
![C12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/fd03d615-9444-4604-b76b-95873858eb1c.png)

他のCommandの場合も、処理ごとにノードを配置します。「PlayBGM」などの場合も、キュー名を指定すれば同じように読み出すことができます。
![C13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154452/87c0d339-432c-438b-a7a4-3eb6e548a89f.png)




外部でスクリプトを編集し、UE5で読み出せる環境をつくり、より簡単で安全なシナリオ作りができるようにしてみましょう。
データテーブルを活用し、台詞再生やBGM再生などのイベントをテンプレート化すれば、ライターとエンジニアが共同で扱える、柔軟な制作環境が実現できます。