Unity Cloud Content Deliveryでサウンドデータを追加ダウンロード対応する with ADX

# 実はAdressable環境じゃなくても使えるUnity Cloud Content Delivery

Unity Cloud Content Delivery（略してUnity CCD）は、Unity Gaming Servicesのいち機能として提供されているCDN機能です。
モバイルアプリなどにおける、アプリ本体とは別の「追加ダウンロードデータ」の配信と管理を行うことができます。

追加データとして配信することで、アプリ本体を更新しなくてもデータの差し替えが可能になるほか、ストアにアップロードするアプリサイズを抑えることができます。

Unity Cloud Content Delivery
https://unity.com/ja/products/cloud-content-delivery

毎月50GBの転送量まで無料で利用できます。雑に計算すると5MBのデータを10,000人に毎月配布できます。
50GBを超えると1GBあたり0.08ドルなので、ユーザーが1o万人だったら追加で450GB、およそ41,000円ですね。

さて、本システムは基本的にUnityのアセット管理システムであるAddressable Asset Systemとの併用を前提に作られていますが、実はAdressableを導入していない開発フローでも使うことができます。
Unity CCDの管理画面からファイルをアップロードし、生成されたURLからUnityWebRequest経由でダウンロードするだけです。
本エントリではこの簡易刀使い方について、サウンドミドルウェアADXのデータ管理を例に紹介します。

## サウンドデータを外部に置いてアプリデータを小さく

ゲーム開発における意外と分離しやすいデータとして「サウンド」があります。効果音などはゲームのシステムやロジックと絡むので分離しにくいところもありますが、ループ再生する環境音やBGM、ボイスデータなどは追加ダウンロードデータとして比較的分けやすいです。

フルボイスゲームの場合はボイスデータの追加ダウンロードをオプションとすることで、プレイヤーの好みにあわせてアプリサイズを調整することにも繋がります。
ADX for Unityは個人利用なら無料版が使えます。の導入方法については、以下の記事をご覧ください。

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

## AssetBundle化しなくてもサウンドデータを外部サーバーに置けるADX
Unityのサウンド機能を拡張するツール＋プラグインである「CRI ADX」は、特徴として「サウンドデータを圧縮されたバイナリファイルとして出力・利用する」という点があります。
ツールで設定した圧縮サウンドを「acbファイル」「awbファイル」という独自形式で固め、通常はStreamingAssets領域に置いて利用します。

Unity標準サウンドのAudioClipデータを外部サーバーからダウンロードして扱いたい場合はAssetBundle化が必須になりますが、ADXを使うことで、同一のデータをただ外部サーバーにおいておいてダウンロード＆マウントするだけで使用できます。

UnityへのADXの導入方法については以下の記事にて手順を説明しています。

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

（最近「CRI ADX2」は、製品名「ADX」にブランド名がかわりました。）

# 具体的な手順

## オーサリングツールでサウンドデータを用意する
特別な準備は必要なく、UnityでADXを使用する時と同じようにAtom Craftから.acbファイルをビルドします。

ビルド後、通常はUnity Editor側の拡張機能でacbファイルをStreamingAssetsフォルダ下にコピーしますが、今回はサーバー経由でダウンロードするためコピーしません。

## CCDにファイルをアップロード
Unity Gaming Servicesのトップページにログインします。
https://developer.cloud.unity3d.com/

CCDは「LiveOps」のカテゴリにあります。
ビルドした.acbファイルをCCDにアップロードしましょう。CCDには、配信の単位として「Bucket」というものがあります。
CCDのメニューから「Buckets」をクリックします。

![Unity CCD bucket.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/169b86e1-229a-4641-7113-f786d8ce929f.jpeg)

まずは「Create Bucket」をクリックして、新しいBucketを作成します。

Bucketを作成したら、Uploading Instructionsの項目で「Upload using the dashboard」をクリックします。ここでacbファイルをアップロードします。

![ADX2DataDownloadFrom... - Latest - Cloud Content Delivery - Unity Gaming Services 2021-10-28 16-55-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/f55713a4-d858-a88c-200c-9157a2cdb06d.png)

アップロードに成功すると、次の画面になります。この時点では単にサーバーにデータがアップローダされただけで、まだ外部のアプリからアクセスできる状態ではありません。
![ADX2DataDownloadFrom... - Latest - Cloud Content Delivery - Unity Gaming Services 2021-10-28 16-56-44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/42a20bf7-2490-0907-3b65-a4703e1bce38.png)

外からアクセスするために「Release」を作成します。「Create Release」からRelease作成のダイアログを表示します。
Releaseの作成が成功すると次の画面になります。

![ADX2DataDownloadFrom... - Latest - Cloud Content Delivery - Unity Gaming Services 2021-10-28 16-58-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/3f772913-0bf6-0f7e-8666-396aae68fd96.png)

これでアップロードされたファイルが外からアクセス可能になります。振り出されたURLは「Addressable Remote URLs」タブから確認できます。

![Unity CCD Bucket URL.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/a5fc19be-6029-1ab0-7435-5db68923b6c4.jpeg)

こちらの「Latest」、つまり最新データをダウンロードするURLを使うことで、今後Releaseを更新した際もアプリ側の処理を変えずにデータの差し替えが可能となります。

## CCDからacbをダウンロード・再生するサンプルコード

アップロードが終わったので、こんどはアプリ側でダウンロードするスクリプトを記述します。
次のサンプルの「addressableRemotePathUrl」に、振り出されたURLを記述します。

```.cs
public class DownloadAcbFromUnityCCD : MonoBehaviour
{
    public CriAtomSource atomSource;
    public string cueSheetName = "BGM";
    public string cueName = "Pepane";
    public string addressableRemotePathUrl =
        "https://0.client-api.unity3dusercontent.com/client_api/v1/buckets/0/release_by_badge/latest/entry_by_path/content/?path=";

    public void DownloadAndPlay()
    {
        StartCoroutine(DownloadAndPlayCoroutine());
    }
    
    private IEnumerator DownloadAndPlayCoroutine()
    {
        var request = UnityWebRequest.Get(addressableRemotePathUrl+cueSheetName+".acb");

        string savePath = Path.Combine(Application.persistentDataPath, cueSheetName+".acb");
        
        request.downloadHandler = new DownloadHandlerFile(savePath);
            
        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.Success)
        {
            yield return LoadAcbFile(savePath, cueSheetName);
            
            atomSource.cueSheet = cueSheetName;
            atomSource.cueName = cueName;
            atomSource.Play();
        }
        else
        {
            Debug.Log("request error "+ request.result);
        }
    }

    private IEnumerator LoadAcbFile(string filePath, string cueSheetName)
    {
        CriAtom.AddCueSheet(cueSheetName, filePath, null, null);

        Debug.Log("Download Success");
            
        while (CriAtom.CueSheetsAreLoading) {
            yield return null;
        }
            
        Debug.Log("Load Success");
    }

    private void OnDestroy()
    {
        string savePath = Path.Combine(Application.persistentDataPath, cueSheetName+".acb");
        
        File.Delete(savePath);
    }
}

```

DownloadAndPlay()メソッドを呼ぶことで、指定されたURLからacbファイルをダウンロードし、Application.persistentDataPathに保存してからマウントして再生します。

Unity CCD側でファイルを差し替えることによって、データの更新が可能になります。

## iOSでのおやくそく

iOSにおいて、Application.persistentDataPathはデフォルトではiCloudのバックアップ対象ファイルになっています。
ここにでかいファイルを置くとバックアップサイズがでかくなってしまいます。
ゲームの追加ダウンロードデータはプレイヤーがいつでも再取得可能であることを踏まえ、今回の使い方ではiCloudの対象から外しておきましょう。

ファイルパスに対して

```cs
UnityEngine.iOS.Device.SetNoBackupFlag(path);
```

を呼んでおけば大丈夫です。

# いろいろなバイナリデータ配布に便利
開発フェーズでも使用できるところがポイントで、たとえば言語ローカライズのデータをjsonで固めたものをアップロードすれば、開発中アプリだけど翻訳データは常に最新、といった応用もできます。
せっかく無料枠があるので、ぜひいろいろな場面で活用してみましょう。