ごっこランドに於ける Addressables 活用事例のご紹介

はじめに

このセッションでは、「ごっこランド」に Addressable Assets System を実際に投入した際に得られた知見などをご紹介します。

Case & Study

Addressable Groups の分割

基本的には管理のしやすさを基準に分割することをお勧めします。

Addressable Group は Group 単位に一つの ScriptableObject として保存されるため、複数のメンバーが関わっている場合に単一の Group で大量の Entry を取り扱うとバージョン管理システム上でコンフリクトする可能性が高くなるので、「シーン単位」や「アセット種別ごと」などプロジェクトに適した粒度で分割することが望ましいでしょう。

また、Group の Bundle Mode が Pack Together になっている場合、Group の単位で .bundle ファイルが作成されるので、細かくしすぎるとランタイムでのダウンロード時における HTTP コネクション数が増えるなどの問題も発生します。

BundledAssetGroupSchema の設定

BundledAssetGroupSchema は Addressables ビルド時に、Group をどのような設定で AssetBundle にするのか?を設定するためのスキーマです。

ごっこランドでは Bundle Mode を Pack Separately にしている以外は殆どデフォルトの設定にしています。

Bundle Mode や Compression の設定は[マニュアル](https://docs.unity3d.com/Packages/com.unity.addressables@1.18/manual/AddressableAssetsGettingStarted.html)を参考にしつつプロジェクトに合ったものを選択しましょう。

開発時にのみ必要になる Group

開発中は必要だが、Player ビルドでは必要のないアセットを管理する Group は Include in Build のチェックを外すことで、Addressables ビルド時に含まれなくなります。

なお、Include in Build のチェックが付いた Group に属するアセットが Include in Build のチェックが外れた Group に含まれるアセットを参照している場合、Addressables ビルド時にチェックが付いた Group 内のアセット側にコピーされる形になるので、サイズの肥大化が発生する原因となり得ますので注意が必要です。

Addressable Name のルール

Addressable Group への追加時のデフォルト Addressable Name はアセットのパスになります。

このルールは AddressableAssetSettings.OnModificationGlobal を設定することでカスタマイズ可能です。

AddressableAssetSettings.OnModificationGlobal +=
    (settings, modificationEvent, eventData) =>
    {
        if (modificationEvent != ModificationEvent.GroupAdded) return;
        var entries = eventData as IEnumerable;
        foreach (var entry in entries)
        {
            entry.SetAddress(/* Address your own rules */);
        }
    };

また、SetAddress() 以外にも SetLabel() を用いて Label を設定することなども可能です。

AssetBundle Variants の代替

Addressables には AssetBundle Variants に相当する機能はありません。

しかしながら、Group Schema と Build Scripts のカスタマイズにより同等の機能は実現できそうです。

Unity-Technologies/Addressables-Sample という GitHub リポジトリに Addressables Variants というプロジェクトがコミットされていますので、必要な場合は参考にしてみると良いかもしれません。

ビルドされた .bundle 置き場

Addressables の本質的な機能とは関連しませんが、ビルドされた AssetBundle をどこに置くのか?という問題も運用視点では考える必要があります。

ポイントとしては、使いやすさもさる事ながら転送量あたりの料金が重要になってきます。

2020年9月に正式スタートした Unity Cloud Content Delivery というサービスは転送量が一定以上の規模になっている場合、かなりボリュームディスカウントが効くので選択肢にしても良いかも知れません。

なお、ごっこランドでは RSTOR という新興クラウドストレージ・CDN に移管しました。

ダウンロードサイズとダウンロード進捗表示

iOS は追加コンテンツをダウンロードする前などにユーザにダウンロード予定のサイズを明記する必要があります。

Addressables では旧来の AssetBundle では自前管理する必要があったダウンロードサイズを事前取得する方法が提供されています。

また、ダウンロード進捗の表示に関しては UniTask などのサードパーティライブラリを導入すると簡単に実現することができます。

var resourceLocations = /* IEnumerable */;
var totalSize = await Addressables
    .GetDownloadSizeAsync(resourceLocations)
    .WithCancellation(cancellationToken);
var progress = Progress
    .Create(ratio => /* Update UI */);
await Addressables
    .DownloadDependenciesAsync(resourceLocations)
    .ToUniTask(progress, cancellationToken: cancellationToken);

本番・開発での URL 切り替え

実際のゲーム開発の現場では、開発ビルドと本番ビルドとで Addressables のダウンロード先 URL を切り替える運用をすることがよくあります。

ビルド時に Profile を切り替えることで URL を変更することも可能ですが、Addressables v1.13.1 から追加された TransformInternalId という機能を用いれば、ランタイムで Catalog や AssetBundle のダウンロード先を書き換えることが可能になります。

Addressables.ResourceManager.InternalIdTransformFunc というプロパティに IResourceLocation を引数に取り string を返すメソッドや匿名関数などを設定することで、ランタイムで InternalId (Address を示す URL などの値)を参照する際に設定した処理が呼ばれて、戻り値の string が実際の InternalId として利用されるようになります。

Addressables.ResourceManager
    .InternalIdTransformFunc +=
    (resourceLocation) =>
    {
        if (!Debug.isDebugBuild)
            return resourceLocation.InternalId;
        return resourceLocation.InternalId.Replace(
            "assets.monry.jp",
            "assets.dev.monry.jp"
        );
    };

このコードスニペットでは、「本番ビルドの場合は元々の InternalId をそのまま利用し、開発ビルドの場合は InternalId 内の "assets.monry.jp" というホスト名を "assets.dev.monry.jp" に置換する」といった変換処理を設定しています。

Remote Catalog のバージョニング

アプリの更新をせずにランタイムで更新を行えるような設計にする場合、Remote Catalog をビルドする必要があります。

その際、Remote Catalog のバージョン名をどう設定するか?という点についても気を配る必要があります。

基本的にはマニュアルに記載されている方法に則って更新設計を行うことが望ましいのですが、ごっこランドの場合は各ゲームコンテンツ毎にビルドされる Addressables の addressables_content_state.bin を本体側のプロジェクトに共有することが難しかったため、ビルド毎にインクリメントさせる整数を Remote Catalog のバージョンとして Player Version Override に設定しています。

同期 API の活用

Addressables v1.17.4-preview に「同期 API」が実装されました。

Addressables.LoadAssetAsync() などの戻り値である AsyncOperationHandle 型のクラスに新たに追加された WaitForCompletion() というメソッドを実行すると、 await をせずとも非同期処理の結果を待つことができるようになりました。

var material = Addressables
    .LoadAssetAsync("Foo")
    .WaitForCompletion();

しかし、現時点ではパフォーマンス的に問題があるようなので、特別な理由が無い限り無理に使う必要はなさそうです。

AssetBundle との併用

最後に、旧来の AssetBundle と Addressables との併用について説明します。

ごっこランドは、各コンテンツが独立した Unity プロジェクトとして開発されているため、アプリ全体で見ると Addressables を用いたコンテンツと AssetBundle Manifest を用いたコンテンツとが混在した状態で運用できています。

しかしながら、1つのコンテンツ内に Addressables と AssetBundle とを混在させるのは、リソース読み込みの処理や画面遷移の処理などが複雑化するため、あまりオススメできません。

ごっこランドの場合は、Addressables の導入を検討した時点で 40 個以上のコンテンツが AssetBundle Manifest を用いて提供されていたため全移行は断念しましたが、混在を前提に考えるよりは移行を検討する方が現実的だと思われます。

まとめ

「Addressables はいいぞ!」

Addressables はバージョンが重なるにつれてどんどん安定性が上がっており、ダウンロードコンテンツを必要とする新規プロジェクトの場合に採択しない理由は殆ど無くなっていると言えるでしょう。

また、既に旧来の AssetBundle で運用が開始されているプロジェクトにおいても、リソース追加・更新に留まらないようなアップデートが頻繁に行われる場合には、移行を検討する価値はあるのではないでしょうか。

スライド