[未完][Unity]Addressableの基本を理解する

Unityはインストール済みでAddressableAssetSystem(以下AAS)何もわからん状態から開始。
(実際には業務でガッツリ使ってるので初見ではないものの、カスタマイズとかは別の人がやってたので、そのあたり含め理解・整理したく記事書きました)

インストール

UnityのPackage Managerからインストールします。

ツールバーからWindow > PackageManagerを選択
スクリーンショット 2024-03-08 190146

表示されたWindowでAddressableを検索し、インストールします。
(Packagesをin ProjectからUnity Repositoryに変更する)
スクリーンショット 2024-03-08 190107

AASの何が嬉しいのか

  1. コードはアセットがローカルにあるのかリモートにあるのか気にする必要がない
  2. SerializeFieldでアセットを持つよりもメモリ効率が良い
  3. アセットのロードを非同期化できる
  4. (AASというより)アセットをリモートで配信することでROMのサイズを小さくできる

AASが内包しているAssetBundleという仕組みを以前は直接アプリ開発者がカスタマイズして使っていた大部分を、AASがカバーしてくれているような感じらしい。

とにかく使ってみる

  1. CubeのPrefabを作成(CommonCubeという名前にしました)
  2. Addressableのチェックを入れる
  3. Window > Asset Management > Addressable > Groupを選択
  4. 表示されたwindowの少し右上でBuild > New Build > Default Build Scriptを選択(Addressable Assetがビルドされる)
  5. 適当なスクリプトから呼び出して生成してみる。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;

public class SampleSceneController : MonoBehaviour
{
    async void Start()
    {
        var cube = await Addressables.LoadAssetAsync<GameObject>("CommonCube").Task; // Addressableのキーを指定
        Instantiate(cube);
    }
}

動きました
スクリーンショット 2024-03-08 214415

これを起点により発展的で便利な使い方を探っていきます。

Editorでの動作モードの確認

Editor上でのAddressableの動作モードには画像の通り3種類あります

スクリーンショット 2024-03-09 223242

  1. Use Asset Database
    • アセットバンドルを用いず、キーから直接アセットをロードする。
    • ビルドが不要なので、高速でイテレートできる。アセット開発中におすすめ
  2. Simulate Groups
    • アセットバンドルは使わないが、グループ設定に従ってアセットバンドルと同じ動作をシミュレートする
    • グループ設定や動作の監視、確認に利用できる
  3. Use Existing Build
    • 実際にビルドされたアセットバンドルを使う
    • 変更があった場合にビルドが必要ではあるが、ランタイムでは最速。

EventViewerでAddressableのイベントを監視するためにはSend Profile Eventsにチェックする必要がある
スクリーンショット 2024-03-09 223847

Simulate Groupsを選択して、EventViewerを開くと、アセットバンドルのロードイベントが確認できます

スクリーンショット 2024-03-09 224813

動作はしていますがスクリーンショットにもある通り、現在EventViewerはdeprecatedらしく、Addressable Profiler Moduleを使うようにとのこと。(Addressable 1.21)

使ってみたところ、Simulate Groupsでは動作せず、UseExistingBuildでのみ動作しました。(設定があるのかな?Simulate Groupsの存在意義は。。。?)

スクリーンショット 2024-03-09 225054

グループを複数作ってアセットを分けてみる

各種アセットをグループに分けることもできます。

スクリーンショット 2024-03-08 223326

これの何が嬉しいのかというと、ビルドされ出力されるアセットバンドルの最大がグループ単位であるということです。

そのため、たくさんのアセットをAddressableにしても、必要な単位でダウンロードさせたり、削除したりすることができます。

上記の画像の例ではDefault GroupとSpecial Assetsというふたつのアセットバンドルが出力されます。
(分割方式に関してはより細かくすることも設定によって可能です。ただ最大の単位としてはグループ単位になります。)

1グループを複数のアセットバンドルに分ける

例えばAudioClipなどのアセットを全て「Sound」というグループに入れるとビルド時にbundleのサイズが肥大化していきます。

1ファイルが非常に大きくなると、ダウンロード時に時間がかかるので、複数のファイルに分けることができます。(アップデートに差分だけダウンロードサせたりも可能?要調査)

Addressable Asset GroupをInspectorで選択し、下記の選択肢から選ぶことができます。

スクリーンショット 2024-03-09 003107

書いてある通りですが

  • Pack Together: 1つのアセットバンドルにまとめる
  • Pack Separately: 複数のアセットバンドルに分ける
  • Pack Together by Label: ラベルごとにまとめる

ビルドされたbundleファイルの数と、作成されたグループの数に大きく差があった為調べてみたところ、このオプションがあることを知りました。

Remoteのホスティングを試してみる

一旦、アップデート時のハンドリングやら、ちゃんとしたサーバーでのホスティングは置いといて
Editor環境でもRemoteでアセットをホスティングする環境を作成する

  1. Hostingを定義・有効にする
  2. Remoteで管理するAsset Groupを定義する
  3. 動作確認
  4. 補足)HTTPアクセスでのアセットの取得を許可する

Window > Asset Management > Addressable > Hostingを選択

Local Hostingを作成
(DockerやらNicが複数あるのでアドレスいっぱい出てるけど、この検証で利用されているのは192.168.0.61です)

Portはデフォルト0だったが、Resetを押すと使えるポートいイイ感じに探してきてくれてるっぽい。
Enabledもデフォルトではチェック入ってないので、チェックを忘れずに。

スクリーンショット 2024-03-10 154720

Remoteで配信したいAddressable Asset Groupの設定をRemoteにする

スクリーンショット 2024-03-10 155030

補足として、デフォルトではHTTPアクセスは許可されていないので、PlayerSettingsで設定を変更する
Allow downloads over HTTPをAllewed in development buildに変更

スクリーンショット 2024-03-10 155201

ここまでやったらAASをビルド。実行し、Remoteで提供されているアセットの利用が可能なことを確認できました。

CubeがLocal、SphereがRemoteのグループにそれぞれあり、それが同じコードで実行できています。
スクリーンショット 2024-03-10 155353

ここまででビルトインさせたいアセットはLocal,後からダウンロードしてほしいアセットはRemote、のような本番環境であるあるの構成ができるようになりました。

下記より実際の開発でありそうなシチュエーションをシミュレートしてみたいと思います。

事前ダウンロードしてみる。

TODO:

特定条件ではじめてダウンロードさせてみる。

TODO:

ダウンロードされたアセットバンドルはどうなっているのか

TODO:

[TODO] アプリアップデートを想定してみる

TODO:

[TODO] アプリはそのまま、アセットバンドルだけの更新を想定してみる

TODO:

(補足)ビルドされたバンドルの実態(特に雑メモなので飛ばしても)

これで使えるのはわかったんですが、ビルドされた何かしらのファイルがどこにあるのか。

InspectorでAASのグループ設定を確認することができます。
(デフォルトで作成されるグループだとAddressableAssetData/AssetGroups以下にあります)

スクリーンショット 2024-03-08 230613

ここから Library/com.unity.addressables/以下にアセットがビルドされていることがわかります。(以降のパスはおそらく実行環境によって変わると思われます。)

そしてこのフォルダの中に入っているのが、アセットのGUIDをファイル名にしたファイルです。
スクリーンショット 2024-03-08 230834

ビルドされるとグループがAssetBundleとなって、設定されたファイルへ出力される、ところまでわかりました。

後述しますが、グループとアセットバンドルが1:1になっているのは設定によるもので、分割することも可能です。

そして今、私の手元で触ってる環境ではAddressableは1.21と1.19があり、1.21では上記のように自分でビルドパス内のファイルを確認しなくても、アセットバンドルのビルドレポートが見れるようになっていました。(すいません、1.19でも見れるかもしれませんが検証段階では見当たらなかった)

↓ビルド後にこのようなレポートが表示される
スクリーンショット 2024-03-09 215930

このあたりの内容をランタイムのエラーから逆引きしたいことがあります
(例えばxxxxのアセットが見つからない、とかエラー起きてるとか)

それは(1.21では)buildlayout.jsonを確認することができます。
非常に複雑で、自分もざっくりと読む程度ですが

例えばCommonSphereが読み込めない場合、まずbuildlayout.jsonをCommonSphereで検索してみる。
一部抜粋です。ridが1018であることを確認

{
                "rid": 1018,
                "type": {
                    "class": "BuildLayout/ExplicitAsset",
                    "ns": "UnityEditor.AddressableAssets.Build.Layout",
                    "asm": "Unity.Addressables.Editor"
                },
                "data": {
                    "Guid": "18b5908b723b6d14283f7d004438b049",
                    "AssetPath": "Assets/Prefabs/CommonSphere.prefab",
                    "InternalId": "Assets/Prefabs/CommonSphere.prefab",
                    "AssetHash": {
                        "serializedVersion": "2",
                        "Hash": "eeaab9cfd25fa1ea7525546b18794747"
                    },

同オブジェクト内にもう少し下まで見てみると
Fileがrid:1005, Bundleがrid:1010となっていることがわかります。

                    "StreamedSize": 0,
                    "File": {
                        "rid": 1005
                    },
                    "Bundle": {
                        "rid": 1010
                    },

rid:1010のデータを探してみましょう

 {
                "rid": 1010,
                "type": {
                    "class": "BuildLayout/Bundle",
                    "ns": "UnityEditor.AddressableAssets.Build.Layout",
                    "asm": "Unity.Addressables.Editor"
                },
                "data": {
                    "Name": "specialassets_assets_all_af925ca8b865876a8917fb223db89744.bundle",
                    "InternalName": "d389fc75304d6a6354bfb242656815ea",
                    "FileSize": 4680,
                    "BuildStatus": 0,
                    "ExpandedDependencyFileSize": 0,
                    "DependencyFileSize": 46910,

specialassets_assets_all_af925ca8b865876a8917fb223db89744.bundleというファイルに対象アセットが含まれていることがわかります。

もう一つの例として、specialassets_assets_all_af925ca8b865876a8917fb223db89744.bundleの読み込みエラーがランタイムエラーが出たような時の対処でも同じ流れで確認できます。

念の為rid:105(FIle)のjsonも載せておく。
おそらく ExplicitAsset(Any): Bundle(1) : File(1) でデータが定義されているのかな?

{
"rid": 1005,
"type": {
    "class": "BuildLayout/File",
    "ns": "UnityEditor.AddressableAssets.Build.Layout",
    "asm": "Unity.Addressables.Editor"
},
"data": {
    "Name": "archive:/CAB-b1a222e1cc803647548bcb2eaf6e614c/CAB-b1a222e1cc803647548bcb2eaf6e614c",
    "Bundle": {
        "rid": 1010
    },
    "SubFiles": [
        {
            "rid": 1017
        }
    ],
    "Assets": [
        {
            "rid": 1018
        }
    ],
    "OtherAssets": [
        {
            "rid": 1019
        }
    ],
    "ExternalReferences": [],
    "WriteResultFilename": "",
    "BundleObjectInfo": {
        "Size": 348
    },
    "PreloadInfoSize": 0,
    "MonoScriptCount": 0,
    "MonoScriptSize": 0
}
            },

このファイルに含まれるアセットの一覧をridで確認することができます。

(補足)アセットのキーの管理

キーを文字列としてAddressables.LoadAssetAsyncに渡せば参照が取れるし、インスタンス化もできることはサンプルコードの通りですが

キーを文字列で与えるのは、キーが変更になった時にランタイムじゃないと気が付けないと不便ですね。

下記でキー一覧を取得できるので、それでクラスとか作って管理すると良さそう。


// 明示的にAddressableを初期化
Addressables.InitializeAsync().WaitForCompletion();

// GUIDとアドレス全部を取得
foreach(var locator in Addressables.ResourceLocators)
{
    foreach(var key in locator.Keys)
    {
        IList<IResourceLocation> locations = new List<IResourceLocation>();
        locator.Locate(key, typeof(Object), out locations);
        foreach(var location in locations)
        {
            addressList.Add(location.PrimaryKey);
        }
    }
}

// 重複を排除してアドレスだけを取得
foreach(var address in addressList.Distinct())
{
    Debug.Log(address);
}

参考リンク

上部へスクロール