yotiky Tech Blog

とあるエンジニアの備忘録

Unity - Addressable Assets System

目次

検証環境

  • Unity 2019.4.3f1
  • Addressables 1.8.5

概要

Addressable Asset System は、Prefab や Scene、Texture、Shader、Material、TextMeshPro の FontAsset などのリソースに付与したアドレス(Addresable Name)を使ってアセットを管理するシステム。
従来からある AssetBundle で、各自実装しなければならなかった「管理する仕組み」などを提供している。
ローカルから生のアセットやビルドしたAssetBundle、リモートに配置した AssetBundle など、読み込む配置先を設定で切り替えることで、統一的なインターフェースでアセットを扱うことができるようになる。

以前は Resources フォルダを使ったアセットの読み込みがあった。 こちらはメタ情報を付与したり、圧縮したり、リソースは変換されてアプリに埋め込まれる。現在は非推奨な方法。

StreamingAssets フォルダを使ってもアセットを読み込める。 こちらは余計な変換処理は挟まれないが、Resources フォルダと同様にアプリに埋め込まれる。

AssetDatabase はプロジェクトフォルダ内にあるアセットならどれでも読み込めるが UnityEditor でしか動かない。

AssetBundle は、予めアセットをプラットフォームごとにビルドして、サーバーや StreamingAssets フォルダに配置することで、ロードできるようになる。 外部に配置するためアプリの容量は小さくなるが、ビルドや管理に関する機能が提供されておらず各自で行う必要があり手間がかかってしまう。

インストール

Window > Package Manager を開いて、[Addressables] からインストールする。

アセットを読み込む

AssetDatabase を使って UnityEditor で読み込む

対象となるアセットの Inspector を開いて、[Addresable] にチェックを付ける。

f:id:yotiky:20210204225106p:plain

もしくは、Window > Asset Management > Addresables > Groups を開いて、対象のアセットをドラッグ&ドロップする。

f:id:yotiky:20210204225406p:plain

スクリプトからは Addresable Name を用いてアセットをロードするため、任意の名前をつける。

Play Mode Scripts で [Use Asset Database (faster)] を選択すると、実行時に AssetDatabase を使って直接アセットを読み込むことができる。

f:id:yotiky:20210205002320p:plain

続いてスクリプトから読み込む方法。

public AssetReference assetReference;

async Task Start()
{
    var m = await Addressables.LoadAssetAsync<Material>("Assets/Materials/PulseCopy.mat").Task;

    var go = await Addressables.LoadAssetAsync<GameObject>("Assets/Prefabs/Sphere.prefab");
    var o1 = Instantiate(go);

    var o2 = await Addressables.InstantiateAsync("Assets/Prefabs/Sphere.prefab");
    o2.GetComponent<Renderer>().material = m;

    var o3 = await assetReference.InstantiateAsync();
}
void OnDestroy()
{
    if (m != null)
        Addressables.Release(m);
    if (go != null)
        Addressables.Release(go);
    if (o2 != null)
        Addressables.ReleaseInstance(o2);
    if (o3 != null)
        Addressables.ReleaseInstance(o3);
}

アセットのロードは、リリースと対となる。Addressables は内部で参照カウントによって管理しているため、GameObject を単に Destroy すると不整合が起きる。Addressables からロードしたアセットは必ずアンロードする。(手動自動は問わない)*1

アセットを読み込むには、アドレス(Addresable Name)を指定して Addressables.LoadAssetAsync() を呼び出す。開放する時は、ロードの戻り値として受け取ったオブジェクトを Release() に渡す。

GameObject を生成するには2パターンある。
1つ目は Addressables.LoadAssetAsync() してから Instantiate() する方法。 こちらは前述した通りRelease() でリリースする。
2つ目は直接 Addressables.InstantiateAsync() する方法。 こちらは ReleaseInstance()GameObject を渡す。 また、InstantiateAsync() で生成された GameObject のライフサイクルはシーンに紐づくので、シーンが破棄される時は同時自動で破棄される。

AssetReference を使うと Inspector 上から Addressable に登録したアセットを設定できる。

ロード処理は非同期のため、戻り値である AsyncOperationHandleTask プロパティを await する。
UnitTask を使った場合は、InstantiateAsync() を直接 await できる。

Play Mode Script

  • Use Asset Database (faster)
  • Simulate Groups (advanced)
  • Use Existing Build (requires built groups)

[Use Asset Database] は、AssetDatabase を使って直接アセットをロードする。アセットのビルドが不要ですぐに実行できるが、 Addressables Event Viewer には AssetBundle の情報は表示されない。

f:id:yotiky:20210205014348p:plain:w250

[Simulate Groups] は、AssetDatabase を使ってロードされるためアセットのビルドは不要だが、AssetBundle の依存関係を分析してシュミレーションすることができる。

f:id:yotiky:20210205014523p:plain

[Use Existing Build] は、実際にビルドした AssetBundle をロードする。

f:id:yotiky:20210205014744p:plain

AssetBundle をローカルから読み込む

Play Mode Script で [Use Existing Build] を選択する。

f:id:yotiky:20210204225628p:plain:w250

Addressables Groups ウィンドウでグループを選択すると、Inspector に設定が表示される。 Build Path と Load Path がどちらも Local になっていることを確認する。

f:id:yotiky:20210205020457p:plain:w350

Addressables Groups ウィンドウで New Build > Default Build Script を実行するとビルドが実行され AssetBundle が生成される。

f:id:yotiky:20210204225709p:plain

スクリプトは、「基本的な使い方 (faster)」の項のものがそのまま使える。

AssetBundle をリモート(ローカルサーバー)から読み込む

Addressables Groups ウィンドウで Tools > Hosting Services を開いて、[Local Hosting] を作成する。

f:id:yotiky:20210205023109p:plain:w200 f:id:yotiky:20210205023147p:plain:w200

任意の [Service Name] を入力し、[Enable] にチェックを付けるとポートが割り当てられる。

f:id:yotiky:20210205023313p:plain

続いて、Addressables Group ウィンドウで Profile > Manage Profiles で Addressables Profile ウィンドウを開いて、[Profile] を作成する。

f:id:yotiky:20210205023528p:plain:w200 f:id:yotiky:20210205023712p:plain

f:id:yotiky:20210205023744p:plain

任意の [Profile Name] を付けて、RemoteBuildPath に [LocalHostData/[BuildTarget]]、RemoteLoadPath に [http://[PrivateIpAddress]:[HostingServicePort]] と入力する。

f:id:yotiky:20210205025746p:plain

Addressables Group ウィンドウで Profile から作成したプロファイルを選択する。

f:id:yotiky:20210205025720p:plain:w200

Addressables Group ウィンドウでグループを選択し、 Inspector 上で、 Build Path と Load Path に Remote を選択する。それぞれプロファイルで設定した内容が反映される。

f:id:yotiky:20210205030004p:plain:w350
f:id:yotiky:20210205020607p:plain:w250

同 Inspector から [Inspect Top Level Settings] をクリックして、AddressableAssetSettings を開く。 [Build Remote Catalog] にチェックを付け、Build Path と Load Path に Remote を選択する。

f:id:yotiky:20210205034729p:plain

[Player Version Override] は指定しないとビルド日時で名前が生成されるため、ビルドするたびに新しいファイルが生成される。同じバージョン番号を指定しているうちはコンテンツカタログが上書き更新されるのでファイルが増え続けることはない。

f:id:yotiky:20210205035737p:plain

Play Mode Script で [Use Existing Build] を選択する。

f:id:yotiky:20210204225628p:plain:w250

アセットをビルドすると、プロジェクト直下に LocalHostData フォルダが作成され Catalog と AssetBundle が出力される。

f:id:yotiky:20210205032453p:plain:w200

スクリプトは、「基本的な使い方 (faster)」の項のものがそのまま使える。

AssetBundle をリモート(Azure Blob Storage)から読み込む

今回は、Azure Blob Storage にパブリックアクセスレベルを「BLOB」にした assets という名前でコンテナを用意する。(危険なので使い終わったらさっさと除去しすること) 実際は SAS などを生成してセキリティに気をつける必要がある。

コンテナのプロパティから URL をメモしておく。

f:id:yotiky:20210205102202p:plain


Addressables Groups ウィンドウで Create > Group > Packed Assets を選択して、グループを作成する。

f:id:yotiky:20210205052315p:plain

任意のアドレス(Addressable Name)を付け、対象のアセットを登録する。

f:id:yotiky:20210205052715p:plain

Group を選択して Inspector を開いて、Build Path と Load Path に Remote を選択する。

f:id:yotiky:20210205053117p:plain:w300

同 Inspector から Inspect Top Level Settings をクリックして、AddressableAssetSettings を開く。 [Build Remote Catalog] にチェックを付け、Build Path と Load Path に Remote を選択する。

f:id:yotiky:20210205034729p:plain

Addressables Group ウィンドウで Profile > Manage Profiles で Addressables Profile ウィンドウを開いて、[Profile] を作成する。

f:id:yotiky:20210205023528p:plain:w200 f:id:yotiky:20210205023712p:plain

任意の [Profile Name] を付けて、RemoteLoadPath に用意した Blob の URL を使ってhttps://xxx.blob.core.windows.net/assets/[BuildTarget] と設定する。[BuildTarget] はビルドしたときのプラットフォームの文字列が入る。

f:id:yotiky:20210205023744p:plain

Addressables Group ウィンドウで Profile から作成したプロファイルを選択する。

f:id:yotiky:20210205101418p:plain:w200

Play Mode Script で [Use Existing Build] を選択する。

f:id:yotiky:20210204225628p:plain:w250

アセットをビルドすると、プロジェクト直下に ServerData フォルダが作成され Catalog と AssetBundle が出力される。

f:id:yotiky:20210205101550p:plain f:id:yotiky:20210205101746p:plain:w300

作成した Catalog と AssetBundle を、Azure Blob Storage にアップロードする。 コンテナのパスなど RemoteLoadPath が変わった場合は再度アセットをビルドしてアップロードし直す。

f:id:yotiky:20210205102437p:plain

スクリプトは、「基本的な使い方 (faster)」の項のものがそのまま使える。

グループ

フォルダをドラッグ&ドロップすることもできる。階層を維持する場合、フォルダに対してアドレス(Addresable Name)とラベルを設定できる。フォルダを指定してまとめて読み込むことはできなそう。 ラベルは配下のアセットにも適用されるので問題なくまとめて読み込むことができる。 アドレス(Addresable Name)もラベルも配下のアセットに個別に設定することはできないが、決められたアドレス(Addresable Name)を使えば個別に読み込むことは可能である。

f:id:yotiky:20210205132108p:plain

フォルダが不要な場合はグループの直下に移動する。

f:id:yotiky:20210205132229p:plain

グループとプロファイルの関係

Addressables Groups

グループでは、アセットにアドレス(Addresable Name)とラベルを付与して、グループに登録する。 アセットはいずれかのグループに所属し、複数のグループには登録できない。

f:id:yotiky:20210205152731p:plain

グループの設定

グループ単位で AssetBundle 化されるため、グループの設定では、Build Path と Load Path が選択できたり、圧縮やプロバイダーの選択など、Packing や Loading などの設定ができる。

f:id:yotiky:20210205153754p:plain:w300

AddressableAssetSettings

Assets/AddressableAssetsData/AddressableAssetSettings では、コンテンツカタログの設定ができる。コンテンツカタログを出力するかどうか、Build Path と Load Path をどこにするか、バージョンを上書きするかなどの設定が含まれる。

f:id:yotiky:20210205154027p:plain:w350

Addressables Profiles

プロファイルでは、ビルドとロードのパスをどこにするかの設定を作り分けられるようになっている。Unity Editor で実行する時、ローカルホストや開発サーバー、本番サーバーで実行する時などプロファイルを切り替えるだけでビルド先とロード先を変更できるようになる。

f:id:yotiky:20210205153238p:plain

ビルド

各グループはグループ毎に Build Path / Load Path の設定を持つ。
コンテンツカタログも別途 Build Path / Load Path の設定を持ち、出力するように設定にする必要がある。
これらの Path は、選択した Profile によって値が切り替えられる。

コンテンツカタログは AddressableAssetSettings で Build Retemo Catalog にチェックを付けると生成されるようになる。

f:id:yotiky:20210708181908p:plain:w300

ビルドしたけど出力されていないと思った時は各グループ、コンテンツカタログの Build Path がどこになっているかを確認すると良い。

ラベル

アセットにラベルを付けてグルーピングすることができる。アセットでラベルを指定してまとめて読み込んだり、ダウンロードすることができる。

Addressables Groups ウィンドウで、Tools > Labels を開いて、ラベルを作成する。

f:id:yotiky:20210205125644p:plain:w200 f:id:yotiky:20210205125806p:plain:w250

Addressables Groups ウィンドウで任意のアセットにラベルを設定する。

f:id:yotiky:20210205130021p:plain:w250

Addressables.LoadAssetsAsync() の引数にラベルを渡すとまとめてロードできる。(Assets が複数形なので注意)

var objects = await Addressables.LoadAssetsAsync<Object>("Model", null);
foreach(var item in objects)
{
    Debug.Log(item);
}

f:id:yotiky:20210205130747p:plain:w300

コンテンツカタログ

ビルド結果のファイル構成は以下の通り。

f:id:yotiky:20210205155523p:plain:w300

  • catalog_<version>.hash
  • catalog_<version>.json
  • グループ毎の AssetBundle や依存関係のある AssetBundle など

hash ファイルは、コンテンツカタログの更新の有無を比較するために使われる。 hash ファイルに違いがある場合、リモートから json ファイルをロードする。 hash ファイルが同じ場合はローカルのキャッシュからロードされる。

これら hash ファイルと json ファイルの取得元やファイル名などの情報は、アプリのビルド時に settings.json を書き出され StreamingAssets に保存される。*2 アプリのビルドをしないと取得元を変更することができないため、[Player Version Override] はアプリのバージョンと同じにして、アプリが同じバージョンの間はコンテンツカタログの名前が変わらないようにして上書き更新し続けるのが良さそうである。

light11.hatenadiary.com

Bundle Mode (Packing Mode)

AssetBundle の分割単位を設定できる。 原則としてグループ毎に AssetBundle に分割され、グループ内であっても Scene とそれ以外では更に分割される。

f:id:yotiky:20210205162021p:plain:w300

  • Pack Together
  • Pack Separately
  • Pack Together By Label


Pack Together はグループ単位でまとめられる。同じグループでも Scene は別になる。

f:id:yotiky:20210205162829p:plain:w300

Pack Separately はアドレス単位ですべて個別に分割される。

f:id:yotiky:20210205163357p:plain

Pack Together By Label はラベル単位で分割される。同じグループでも Scene は別になる。また、ラベルの付いていないアセットはまとめて1つの AssetBundle になる。

f:id:yotiky:20210205163631p:plain:w300

Window > Asset Management > Addressables > Analyze を開くと分割の内容を解析できる。(前述の画像)

light11.hatenadiary.com

キャッシュ

設定

Project ウィンドウで Addressables > Initialization > Cache Initialization Settings を追加する。

f:id:yotiky:20210207170304p:plain

f:id:yotiky:20210207170435p:plain:w300

設定した CacheInitializationSettings を AddressableAssetSettings の Initialization Objects に追加する。

f:id:yotiky:20210207172035p:plain:w300

プロパティ 内容
Compress Bundles キャッシュに保存される AssetBundle を LZ4 形式で圧縮するかどうか
Cache Directory Override キャッシュの保存先
Expiration Delay キャッシュの保存期間(最大12960000秒(150日))
Limit Cache Size 最大キャッシュサイズを有効にするかどうか

保存先

Unity Editor (Windows)

リモートのコンテンツカタログがキャッシュされる場所。

C:\Users\<UserName>\AppData\LocalLow\<CompanyName>\<ProductName>\com.unity.addressables

f:id:yotiky:20210205205330p:plain:w200

ダウンロードした AssetBundle がキャッシュされる場所。

C:\Users\<UserName>\AppData\LocalLow\Unity\<CompanyName>_<ProductName>

f:id:yotiky:20210205205555p:plain:w250

グループの設定に以下の項目がある。(機能は未調査)

f:id:yotiky:20210205235518p:plain:w300

参考:qiita.com

HoloLens2

リモートのコンテンツカタログがキャッシュされる場所。

C:\Data\Users\<UserName>\AppData\Local\Packages\<ApplicationName_hash>\LocalState\com.unity.addressables

ダウンロードした AssetBundle がキャッシュされる場所。

C:\Data\Users\<UserName>\AppData\Local\Packages\MR-<ApplicationName_hash>LocalState\UnityCache\Shared

削除

ダウンロードした AssetBundle のキャッシュを削除する。コンテンツカタログは削除されない。

Caching.ClearCache();

補足

Addressables Event Viewer で参照カウントを可視化する

Addressables Event Viewer (旧名 RM Profiler)を使うと参照カウントを可視化したり、アセットが含まれている AssetBundle を確認することができる。

機能を有効にするには、Window > Asset Management > Addressables > Settings を開いて、[Send Profiler Events] にチェックを付ける。

f:id:yotiky:20210205021610p:plain:w300 f:id:yotiky:20210205021643p:plain:w200

light11.hatenadiary.com

Addressables で NullReferenceException

NullReferenceException when using Addressables.DownloadDependenciesAsync(label) - Unity Forum

Addressables のメソッドを呼び出して Taskawait しようとした時に Task 自体が nullNullReferenceException が発生する場合があるらしい。

以下のようにラップしてしまう拡張メソッドを用意するか、await して取り出すような拡張メソッドを用意する。

public static class AsyncOperationHandleExtensions
{
    public static Task<T> NotNullTask<T>(this AsyncOperationHandle<T> handle)
        => handle.Task ?? Task.FromResult(handle.Result);
}

別プロジェクトで作成したコンテンツカタログを読み込む

// ファイルパスかURL
var catalog = await Addressables.LoadContentCatalogAsync("http://169.254.37.215:12345/catalog.json");

// カタログは追加しなくても動きそう
//Addressables.AddResourceLocator(catalog);

await Addressables.InstantiateAsync("Assets/Prefabs/Sphere.prefab");

tsubakit1.hateblo.jp

ダウンロード処理のカスタマイズ

light11.hatenadiary.com

便利ツール

スクリプトからの操作

Addressables クラス

// 手動での初期化
// 呼び出していない場合は、LoadAssetAsync などが呼ばれた時に実行される
// https://docs.unity3d.com/Packages/com.unity.addressables@1.13/manual/InitializeAsync.html
await Addressables.InitializeAsync();

// 追加するコンテンツカタログをロードする
// パスはファイルの絶対パスやURLなど
// https://baba-s.hatenablog.com/entry/2020/03/19/063000
await Addressables.LoadContentCatalogAsync("CatalogPath");

// AssetBundle のサイズを取得する
await Addressables.GetDownloadSizeAsync("key");

// 事前に AssetBundle をダウンロードする
// https://baba-s.hatenablog.com/entry/2020/03/19/042000_1
await Addressables.DownloadDependenciesAsync("key");

参考