Unity Localization PackageとADXの多言語ボイス音声切り替え機能を連携する

# はじめに
本投稿はUnity アドベントカレンダー17日目の記事です。また、本内容は先日開催された「【年末だよ】Unity お・と・なのLT大会 2021」で発表した内容から一部を抜粋して再構成したものです。
https://meetup.unity3d.jp/jp/events/1337

## Unity Localization Packageについて
Unityが公式に提供しているゲームのローカリゼーション拡張です。2021年12月現在、バージョン1.0.5(プレビューパッケージ)となっています。

Localization Packageそのものの基本機能や導入手順については、Learning Materialsの「プロ必見！最新ローカライズ機能徹底解説」が分かりやすいです。
https://learning.unity3d.jp/7906/

## ADXについて
ADXは、ゲーム開発向けサウンドミドルウェアです。ゲームに組み込むランタイムと、再生用のデータを作るオーサリングツールの2つで構成されています。「音データに制御用パラメータを埋め込む」発想で、フェード処理や同時再生数制御などをプログラムコードなしで設定できます。また、Androidにおけるサウンドの再生遅延を低減したり、BGMがゲームと連動して変化していく「インタラクティブ・ミュージック」の仕組みを導入できます。

その他のメリットや導入手順は以下の記事をご参照ください。

Unityのサウンド機能をADXで強化する
https://qiita.com/Takaaki_Ichijo/items/16e6501fc07f5b3b3377

## ADXの「多言語対応」について

ADXには、ゲームのローカライズにおいて、声優のセリフ音声データが複数言語ごとにあるとき、それらを一括して管理できる機能があります。

・多言語対応
https://game.criware.jp/manual/adx2_tool/latest/criatom_tools_atomcraft_multi_language.html

たとえばある同じセリフの「日本語」「英語」の音声データがあったとき、ツールの上ではそれらをひとつの音声データと見なして扱います。
たとえば、「Boy_01」という音声データには日本語と英語が含まれており、ツールの全体設定で「日本語」「英語」で切り替えると、再生される音声データも切り替わります。

ツール上では同じデータとして扱うので、この音声データにエフェクトをかけたり、同時再生数の上限値を設定した場合、すべての言語データに対して一括で同じ設定が適用されます。

そして、ツールからUnity用のデータを出力する際「日本語用データ」「英語用データ」の2種類を同じファイル名で生成します。
プログラムコード側では、どちらのデータをロードするかによって再生される言語が切り替わります。

たとえば、英語用データをStreamingAssets/en/、日本語用をStreamingAssets/ja/に配置します。
サンプルコードは次の通りです。

```playvoice.cs
    public CriAtomSource atomSource;

    private void LoadCueSheet(string localeCode)
    {
        CriAtom.AddCueSheet("voice",localeCode+"/voice.acb", null);
    }

    public void PlayVoice()
    {
        atomSource.cueName = "Boy_01";
        atomSource.Play();
    }
```

LoadCueSheetメソッドで、音声データvoice.acbをロードします。このとき、読み込みパスにロケールコード（jaとかenとか）を指定します。
ロードする際にデータを差し替えますが、音声データそのものの名称は同じなので、PlayVoiceメソッドで指定している「Boy_01」の部分は変える必要がありません。
この機能をLocalization Packageと連動させます。

# 実際の手順

手順を開設します。これ以降はADX用語も出てきますので、まだADXを使ったことが無い方は[導入ガイド](https://qiita.com/Takaaki_Ichijo/items/16e6501fc07f5b3b3377)を先にご覧ください。

## Atom Craftの設定
まずはAtom Craftでデータを作っていきます。プロジェクトツリーの「言語設定」で必要な分の言語を作成します。

![言語設定の追加 2021-12-07 21.49.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/c903c286-c572-bf7b-ea3e-218f9433ed8f.png)

マテリアルツリーで「ローカライズ用管理フォルダー」を作成します。すると、「言語設定」で指定した分のフォルダができます。

![ローカライズ用管理フォルダーの作成 2021-12-07 21.51.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/ef79dc14-6e1b-d844-505a-4133562f1bc4.png)

それぞれの言語ごとのフォルダに、同名のwavファイルとしてセリフ音声データ(WAVファイル)を格納します。
このとき、左上の「言語」表示の部分を切り替えることで、再生される言語が切り替わります。

![CRI Atom Craft LE Ver.3.44.20 (built on Feb  5 2021) 2021_12_07 21_52_48.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/238bdc16-cf1b-2352-8cef-f36b0a796413.jpeg)

## キューシートをビルドしStreamingAssetsに配置

Atom Craftからビルドを行ってデータ出力します。ビルドダイアログの「言語」で出力したい言語を選択してからビルドします。

![Atomキューシートバイナリのビルド 2021-12-07 21.54.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/164f1d5c-33b3-8edd-18b9-6293074b3c4d.png)

出力データをStreamingAssets下に配置します。このとき、ロケールコードにそったフォルダ名にしておきます。例では英語用をStreamingAssets/en/voice.acb、日本語用をStreamingAssets/ja/voice.acbに配置しています。

![UnityLocalizeTest - SampleScene2 - PC, Mac & Linux .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/11ed4d69-ca80-cedc-8601-13822e5d9fb1.png)

## Localization Packageの言語設定切り替えを検出

Unity Localization PackageではSelectedLocaleで現在の言語設定が取れます。また、SelectedLocaleChangedで言語設定が変わった時のコールバックを設定できます。

引数のlocaleからは、locale.Identifier.Codeからロケールコードが取得できます。

```SelectedLocaleChanged.cs
LocalizationSettings.SelectedLocaleChanged += (locale) =>
{
  LoadCueSheet(locale.Identifier.Code);
};

var currentLocale = LocalizationSettings.SelectedLocale;

LoadCueSheet(currentLocale.Identifier.Code);
```

言語の切り替えを検出しつつ、すでに同名ファイルがロードされていたらそれをアンロードしてから新しいファイルをロードします。

```LoadCueSheet.cs
private void LoadCueSheet(string localeCode)
{
   if (CriAtom.GetCueSheet("voice") != null)
   {
       CriAtom.RemoveCueSheet("voice");
   }
  
   CriAtom.AddCueSheet("voice",localeCode+"/voice.acb", null);
}
```

ロード処理が完了してからボイス再生を行います。この処理で、Localization Packageと連動したボイスデータ切り替えが実現できました。

```PlayVoice.cs
public void PlayVoice()
{
    //厳密にはセリフデータロード終わってから呼ばれるようにする
    if (CriAtom.CueSheetsAreLoading == false)
    {
        atomSource.cueName = "Boy_01";
        atomSource.Play();
    }
}
```

# 応用と課題
単純に言語ごとに音声データを差し替えるだけではうまくいかないローカライズ対応もあります。たとえば、言語によって再生するタイミングが違ったり、再生される音声ファイルの長さが大きく異なる、言語ごとにエフェクトのかかり具合を調整したい、といったケースです。

その場合は、キューの「トラック」を言語ごとに分ける設定を行います。トラックのプロパティに「適用言語」という項目があるので、このトラックが何言語用なのかを指定してからそれぞれのトラックに設定を追加します。

ローカライズ言語によるトラック切替
https://game.criware.jp/manual/adx2_tool/latest/criatom_tools_atomcraft_multi_language_switch_track.html

ADXから出力されるデータ名が全く一緒だと、ディレクトリが別とはいえ混乱が生じる可能性があります。同一ファイル名を使用すると管理上問題がある場合があるため、ファイル名に言語用の「接頭語」「接尾語」を付加して運用する方法もサポートされています。開発人数が多い場合は注意です。