【CRI】Asset Support Add-onを使ってCRIの音・動画をUnityアセットとして扱う

この記事は、[サムザップ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/sumzap) の12/19の記事です。


# 概要
2022年5月頃にＣＲＩ・ミドルウェア社が開発した[CRIWARE Unity SDK](https://game.criware.jp/manual/unity_plugin/latest/contents/index.html#main_page_prerequisite)に、[Asset Support Add-on](https://game.criware.jp/manual/unity_plugin/latest/contents/index.html#main_page_prerequisite)が新しく追加されました。このアドオンには、CRI関連の音声や動画データをUnityアセットとして扱えるようにするCRI Assetsと、Unityの[Addressable Assets System（以下AAS）](https://docs.unity3d.com/Packages/com.unity.addressables@1.21/manual/index.html)に対応したCRI Addressablesという２つの機能が追加されています。
本記事では、それらの機能について紹介できればと思います。

# CRI関連データのインポート
Unityアセットとして扱えるCRI関連のデータとインポート後に付与されるコンポーネントは以下になります
| ファイル種別 | 概要 | インポート後に付与されるコンポーネント |
| :--- | :--- | :--- |
| ACFファイル | 全体設定ファイル | [CriAtomAcfAsset](https://game.criware.jp/manual/unity_plugin/latest/contents/classCriWare_1_1Assets_1_1CriAtomAcfAsset.html) |
| ACBファイル | 音声キューシートのバイナリファイル | [CriAtomAcbAsset](https://game.criware.jp/manual/unity_plugin/latest/contents/classCriWare_1_1Assets_1_1CriAtomAcbAsset.html) |
| AWBファイル | 音声ストリーミング再生用の波形データのバイナリファイル | CriAtomAwbAsset（[CriAssetBase](https://game.criware.jp/manual/unity_plugin/latest/contents/classCriWare_1_1Assets_1_1CriAssetBase.html?highlight=CriAssetBase)をただ継承しているだけなので、ドキュメントは無し） |
| USMファイル | Sofdec動画のバイナリファイル | [CriManaUsmAsset](https://game.criware.jp/manual/unity_plugin/latest/contents/classCriWare_1_1Assets_1_1CriManaUsmAsset.html) |

上記のファイルをUnityプロジェクトのStreaming Assetsフォルダ以外のフォルダに追加すると、インポートの処理が走りそれぞれのUnityアセットが生成されます。

例えば、ACBファイルだと[CriAtomAcbAssetクラス](https://game.criware.jp/manual/unity_plugin/latest/contents/classCriWare_1_1Assets_1_1CriAtomAcbAsset.html)のScriptableObjectが生成されます。
同名のACBファイルをAWBファイルを一緒に追加すると、ACBアセットにAWBアセットが紐付けされた状態でインポートされます。ACBアセットにAWBアセットが紐付いていると、ACBアセットをロードすることでAWBアセットも自動でロードされストリーミング再生ができるようになります。
また、インポートされたACBアセットは、Inspectorタブからプレビュー再生することもできます。
![acb_inspector.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200337/c9128488-6d5e-6c79-72bb-6f2f6bd26652.png)


## ビルド時にアセットの実データをどこに配置するかの設定
CRI関連ファイルのUnityアセットが生成されたら、DeployTypeというプロパティでビルド時に生成するアセットの実データ（ACBファイル、AWBファイルなどのバイナリデータ）をどこに配置するかという設定をします。
デフォルトで選択できる設定は以下になります。
| ファイル種別 | 概要 |
| :--- | :--- |
| StreamingAssets | StreamingAssetに配置する |
| OnMemory | アセットそのものの中に配置する(AWBファイルは、ストリーミング再生用のデータなので取り扱えない。) |
| Addressables (Remote) | AASで設定したリモートデータ用のパス |
| Addressables (Local) | AASで設定したローカルデータ用のパス |

OnMemoryを指定したアセットをロードするとデータは即メモリに展開されるため、ストリーミング再生用のデータを扱うAWBファイルに対しては、OnMemoryを選択できず、AWBファイルを配信用のデータとする場合はAddressables (Remote)を指定することになります。
AASを使わずにAWBファイルを配信用データとして扱いたい場合は、DeployTypeを拡張して独自の実データの取り扱いやロード時の挙動を定義することもできます。[^1]


# 音のロードと再生
DeployTypeをStreamingAssetsにしたACBアセットをロード及び再生する実装は以下のようになります。
ビルドすると、StreamingAssetsフォルダにACBファイル及びAWBファイルがそのまま配置されるので、ロードと再生は従来の実装と変わらないです。

```c#
using CriWare;
using CriWare.Assets;
using System.Threading.Tasks;

public class SoundPlayTest : MonoBehavior
{
    [SerializeField] private Button _playButton;

    private CriAtomExPlayer _player;

    private async void Awake()
    {
        _player = new CriAtomExPlayer();

        _playButton.onClick.AddListener(async () =>
        {
            CriAtomExAcb acb = await LoadAcbAssetFromStreamingAssets("acbFileName", "awbFileName")
            PlayAcb(acb, 1);
        });
    }

    /// <summary>
    /// StreamingAssetsからACBを非同期ロードする
    /// </summary>
    private async Task<CriAtomExAcb> LoadAcbAssetFromStreamingAssets(string acbFileName, string awbFileName)
    {
        string acbPath = $"{CriWare.Common.streamingAssetsPath}/{acbFileName}";
        string awbPath = $"{CriWare.Common.streamingAssetsPath}/{awbFileName}"; 

        // ACBアセットのStreamingAssetsからのロード
        CriAtomExAcbLoader loader = CriAtomExAcbLoader.LoadAcbFileAsync(null, acbPath, awbPath);

        await new WaitUntil(() =>
        {
            var status = loader.GetStatus();
            return status == CriAtomExAcbLoader.Status.Complete
                || status == CriAtomExAcbLoader.Status.Error;
        });

        if (loader.GetStatus() == CriAtomExAcbLoader.Status.Complete)
        {
            return loader.MoveAcb();
        }

        return null;
    }

    /// <summary>
    /// ACBを再生する
    /// </summary>
    private CriAtomExPlayback PlayAcb(CriAtomExAcb acb, int cueId, float volume = 1.0f)
    {
        // プレイヤーへのキューの設定
        player.SetCue(acb, cueId);
        player.SetVolume(volume);

        // 再生
        CriAtomExPlayback playBack = player.Start();

        return playBack;
    }
}
```

DeployTypeをAddressables (Remote)にしたACBアセットをロード及び再生する実装は以下のようになります。
注意するところとしては、カタログファイルロード完了後に、カタログファイル内のCRIアセット向けの情報を更新する必要があります。[^2]

```c#
using CriWare;
using CriWare.Assets;
using System.Threading.Tasks;

public class SoundPlayTest : MonoBehavior
{
    [SerializeField] private Button _playButton;

    private CriAtomExPlayer _player;

    private async void Awake()
    {
        _player = new CriAtomExPlayer();

        _playButton.onClick.AddListener(async () =>
        {
            CriAtomExAcb acb = await LoadAcbFromAddressable("acbAddress");
            PlayAcb(acb, 1);
        });

        // リモートカタログの有無を確認してAddressablesを初期化
		var catalogs = await Addressables.CheckForCatalogUpdates().Task;
		if (catalogs.Count > 0)
		{
            await Addressables.UpdateCatalogs(catalogs).Task;
        }
		else
        {
			await Addressables.InitializeAsync().Task;
        }

		// カタログロード完了後に、カタログ内のCriAddressables向けの情報を修正する
		// この処理を呼ぶことによってAddressables.GetDownloadSizeAsync等が正しい結果を返すようになる
		CriWare.Assets.CriAddressables.ModifyLocators();
    }

    /// <summary>
    /// Addressable経由でACBを非同期ロードする
    /// </summary>
    private async Task<CriAtomExAcb> LoadAcbFromAddressable(string address)
    {
        // ACBアセットのAddressableからのロード
        var acbAsset = await Addressables.LoadAssetAsync<CriAtomAcbAsset>(address);
        if (acbAsset.Loaded == false)
        {
            // ACBアセットのキューシートのロード
            acbAsset.LoadAsync();
            await new WaitUntil(() => acbAsset.Loaded || acbAsset.Status == CriAtomExAcbLoader.Status.Error);
        }

        if (acbAsset.Loaded)
        {
            return acbAsset.Handle;
        }

        return null;
    }

    /// <summary>
    /// ACBを再生する
    /// </summary>
    private CriAtomExPlayback PlayAcb(CriAtomExAcb acb, int cueId, float volume = 1.0f)
    {
        // プレイヤーへのキューの設定
        player.SetCue(acb, cueId);
        player.SetVolume(volume);

        // 再生
        CriAtomExPlayback playBack = player.Start();

        return playBack;
    }
}
```


# 動画の再生
スクリプトからUSMアセットを指定して再生する際は、[CriManaMovieControllerForAsset](https://game.criware.jp/manual/unity_plugin/latest/contents/classCriWare_1_1Assets_1_1CriManaMovieControllerForAsset.html?highlight=CriManaMovieControllerForAsset)というコンポーネントを使います。
CriManaMovieControllerForAssetでは、RenderTargetプロパティで描画対象種別をRendererコンポーネントにするかGraphicコンポーネントにするかを選択できます。
Graphicを選択した場合は、既存の[CriManaMovieControllerForUI](https://game.criware.jp/manual/unity_plugin/latest/contents/classCriWare_1_1CriManaMovieControllerForUI.html?highlight=CriManaMovieControllerForUI)と同じ描画方法になります。
CriManaMovieControllerForUIと違う点としては、ループの設定やアディティブムービーとして再生するかの設定などがUSMアセット側が持つようになったことで無くなっています。

<img width="700" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200337/a6a1cf96-edce-059a-864f-e8499ad1f6e9.png">
<img width="700" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/200337/b87c05d0-f14e-597a-187f-b8d1cf2a3864.png">

DeployTypeをStreamingAssetsにしたUSMアセットをロード及び再生する実装は以下のようになります。
ビルドすると、StreamingAssetsフォルダにUSMファイルがそのまま配置されるので、USMファイルの設定と再生は従来の実装とほぼ変わらないです。

```c#
using CriWare;
using CriWare.Assets;
using System.Threading.Tasks;

public class MoviePlayTest : MonoBehavior
{
    [SerializeField] private Button _playButton;

    [SerializeField] private CriManaMovieControllerForAsset _movieController;

    private async void Awake()
    {
        _playButton.onClick.AddListener(async () =>
        {
            SetUsmFromStreamingAssets("usmFileName");
            PlayUsm();
        });
    }

    /// <summary>
    /// StreamingAssetsフォルダのUSMファイルをプレイヤーに設定する
    /// </summary>
    private void SetUsmFromStreamingAssets(string usmFileName)
    {
        // プレイヤーにUSMファイルをセットする
        _movieController.player.SetFile(null, $"{CriWare.Common.streamingAssetsPath}/{usmFileName}");
    }

    /// <summary>
    /// USMを再生する
    /// </summary>
    private void PlayUsm()
    {
        _movieController.Play();
    }
}
```

DeployTypeをAddressables (Remote)にしたUSMアセットのロードと再生の実装は以下のようになります。
CriMana.Playerに、CriManaUsmAssetをプレイヤーに設定する拡張メソッドが用意されているので、それを使うのが便利です。

```c#
using CriWare;
using CriWare.Assets;
using System.Threading.Tasks;

public class MoviePlayTest : MonoBehavior
{
    [SerializeField] private Button _playButton;

    [SerializeField] private CriManaMovieControllerForAsset _movieController;

    private async void Awake()
    {
        _playButton.onClick.AddListener(async () =>
        {
            await LoadAndSetUsmAssetFromAddressable("usmAddress");
            PlayUsm();
        });

        // リモートカタログの有無を確認してAddressablesを初期化
		var catalogs = await Addressables.CheckForCatalogUpdates().Task;
		if (catalogs.Count > 0)
		{
            await Addressables.UpdateCatalogs(catalogs).Task;
        }
		else
        {
			await Addressables.InitializeAsync().Task;
        }

		// カタログロード完了後に、カタログ内のCriAddressables向けの情報を修正する
		// この処理を呼ぶことによってAddressables.GetDownloadSizeAsync等が正しい結果を返すようになる
		CriWare.Assets.CriAddressables.ModifyLocators();
    }

    /// <summary>
    /// Addressable経由でUSMアセットを非同期ロードしプレイヤーに設定する
    /// </summary>
    private async Task<CriManaUsmAsset> LoadAndSetUsmAssetFromAddressable(string address)
    {
        // USMアセットのAddressableからのロード
        var usmAsset = await Addressables.LoadAssetAsync<CriManaUsmAsset>(address);

        // プレイヤーにUSMアセットをセットする
        _movieController.player.SetAsset(usmAsset);
    }

    /// <summary>
    /// USMを再生する
    /// </summary>
    private void PlayUsm()
    {
        _movieController.Play();
    }
}
```




# まとめ
CRI関連データをアセットバンドルやAddressableとして扱うとなると、拡張子に.bytesを付けてバイナリデータとして配信してダウンロード後に元の形式のファイルとしてPersistantDataPathなどに書き込みを行ったり、Addressableの場合は独自にプロバイダーを実装したりする必要がありましたが、Asset Support Add-onを使うとこれらの必要がなくなります。

AASとCRIWAREを使おうと考えている方は、[Asset Support Add-on](https://game.criware.jp/manual/unity_plugin/latest/contents/index.html#main_page_prerequisite)の導入も考えてみても良いかもしれません。

明日は、@chrno001さんの記事です。

# 注釈
[^1]: https://game.criware.jp/manual/unity_plugin/latest/contents/cri4u_samples_addon_asset_scene07.html
[^2]: https://game.criware.jp/manual/unity_plugin/latest/contents/cri4u_samples_addon_asset_scene02.html#cri4u_samples_addon_asset_scene02_programs