【Unity】【Addressable】AssetDatabaseにアセットがあったらそれを読み込み、なければリモートから取得する仕組みを作る

UnityのAddressableアセットシステムでAssetDatabaseにアセットがあったらそれを読み込み、なければリモートから取得する仕組みを作る方法についてまとめました。

Unity2020.3.40
Addressables 1.18.19

やりたいこと

まず、以下のようなプロジェクトを考えます。

  • Unity プロジェクト内(AssetDatabase)には全てのアセットが必ずしも存在せず、部分的に存在する可能性がある
    • PlasticSCMPerforce などのバージョン管理ツールで部分チェックアウトを行うようなケース
    • 全てのアセットをプロジェクトに取り込むと動作が重くなるため
  • Addressable でビルド済みのリソースがリモート環境に配置されている

ここで、ワークフローを踏まえて次のようなリソースのロード戦略を実現したいとします。

  • プロジェクト(AssetDatabase)にリソースが存在すればそれをロードする
    • 修正確認をリソースのビルドをせずに即座にできるようにするため
  • プロジェクトにないリソースはリモートからロードする
    • 全てのアセットをプロジェクトに取り込まなくても動作させたいため

本記事ではこれを実現する方法をまとめます。

方針と実装

実装方法はいくつか考えられますが、今回は ResourceLocator を使って以下の方針で実装します。

  • Addressableに登録されている ResourceLocator から ResourceLocationMapを取得
    • カタログを元に作られた、キーと ResourceLocation の情報を保持しているオブジェクト
  • プロジェクトに存在するリソースについては、ResourceLocationAssetDatabase のものに上書きする

以下はこの方針を元に実装したスクリプトです。
細かい説明はコメントに記述しています。

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;

public sealed class Example : MonoBehaviour
{
    private IEnumerator Start()
    {
        yield return Addressables.InitializeAsync();

        // AssetDatabaseProviderを使用するので、ResourceManagerに登録
        var resourceProviders = Addressables.ResourceManager.ResourceProviders;
        if (resourceProviders.All(x => x.GetType() != typeof(AssetDatabaseProvider)))
            resourceProviders.Add(new AssetDatabaseProvider());

        // AssetDatabaseから読むリソースのキーとResourceLocationの対応表を作成
        var localLocations = new Dictionary<string, List<IResourceLocation>>();

        // 対応表にAssetDatabaseから読み込むものを追加
        {
            var address = "fooAddress";
            var assetPath = "Assets/Foo.prefab";
            var providerName = typeof(AssetDatabaseProvider).FullName;
            var assetType = typeof(GameObject);
            var location = new ResourceLocationBase(address, assetPath, providerName, assetType);
            localLocations.Add(address, new List<IResourceLocation> { location });
        }

        // すでに登録されているResourceLocationMap(カタログ用のIResourceLocator)を取得
        var resourceLocationMap = Addressables
            .ResourceLocators
            .OfType<ResourceLocationMap>()
            .FirstOrDefault();
        
        if (resourceLocationMap == null)
            yield break;

        // Keyがアドレスやラベルと一致して、それらがAssetDatabaseにあったらValueを書き換える
        foreach (var location in resourceLocationMap.Locations.ToArray())
        {
            if (!(location.Key is string key))
                continue;

            if (localLocations.ContainsKey(key))
                resourceLocationMap.Locations[location.Key] = localLocations[key];
        }

        // プロジェクト内にアセットがあったらそれを読み込み、なければリモートから取得する
        var handle = Addressables.LoadAssetAsync<GameObject>("fooAddress");
        yield return handle;
    }
}

ローカル環境のAddressablesが適切に設定されている場合には、localLocationsAddressableAssetSettings から作ったりしても良いと思います。

ResourceManagerResourceProviderResourceLocator については以下の記事にもまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

light11.hatenadiary.com

注意点

上記の実装の注意点として、カタログが更新されると ResourceLocationMap が上書き更新されるという点が挙げられます。
したがって、カタログを更新した後には再度 AssetDatabase 用の ResourceLocation に書き換える必要があるのでご注意ください。

その他検討した方法メモ

その他検討した方法をメモしておきます。

AssetDatabase用のResourceLocatorを追加する
  • ResourceLocationMap を書き換えるのではなく、AssetDatabase 用の IResourceLocator を追加する方法
  • AddressablesResourceLocators の最初に AssetDatabase 用のものを追加する
    • Addressables の仕様として、最初に見つかった有効な ResourceLocator を採用するため
  • 単体アセットをロードする際にはこの実装で問題なかった
  • Addressables.LoadAssetsAsync で複数アセットをロードする際に問題が起こった
  • AssetDatabase用の ResourceLocatorResourceLocationMap の両方から IResourceLocation が取られてしまい、AssetDatabase とリモートの両方から同じリソースのロードが走ってしまう結果になった
新しいResourceLocatorを実装する
  • 以下の要件を備えたResourceLocatorを新しく実装する方法
    • プロジェクト内にアセットがある場合には AssetDatabase 用の ResourceLocation を返す
    • プロジェクト内にアセットがない場合にはリモート用の ResourceLocation を返す
  • ResourceLocationMap を削除してこの ResourceLocator を追加する
    • 追加する際に第二引数と第三引数にカタログの情報を渡す
    • カタログを更新したらこの処理を再度行う
  • この方法はおそらく実現可能ですが、検討しただけで今回は試してはいません

関連

light11.hatenadiary.com

light11.hatenadiary.com

【Unity】Unity公式パッケージ『Dependency Viewer』でアセットの依存関係を簡単に可視化する

Unity公式パッケージ『Dependency Viewer』でアセットの依存関係をサクッと可視化する方法についてまとめます。

  • Dependency Viewerとは?
  • インストール
  • 基本的な使い方
  • 依存関係をグラフ表示できるDependency Graph Viewer
  • 依存関係が変更されたら手動で再ビルドが必要
  • 参考
続きを読む

【Unity】プロジェクトのあらゆるアセットを検索するUnity Search(旧Quick Search)の使い方まとめ

Unity Searchの使い方と主要機能について簡単にまとめます。

  • Unity Searchの概要
  • 基本的な検索
  • インデックスのビルド
  • 検索ウィンドウの使い方
  • Inspector
    • テーブルビューとカラム追加
    • ブックマーク機能
  • その他便利な機能
    • 設定項目検索
    • エディタのパフォーマンス分析に使う
    • Searchオブジェクトピッカー
  • 参考
続きを読む

【Unity】Unity Searchのクエリチートシート

Unity Searchのクエリのチートシートです。

  • Unity Searchの概要
  • 基本的な使い方とインデックスのビルド
  • クエリチートシート - シーン検索
    • Canvas/MainPanelというパスを持つGameObjectだけを検索
    • Canvas/MainPanel配下の全GameObjectを検索
    • CameraあるいはLight型のコンポーネントを検索
    • 全てのポイントライトを検索
    • 頂点数が100以上のメッシュを持つMeshRendererを検索
    • 面の数が10以上100以下のメッシュを持つMeshRendererを検索
    • 頂点数が100以下のメッシュを持つMeshRendererのパスを全てログ出力
    • CharacterPool/Character001配下の全ての頂点数を足し合わせる
    • RaycastTargetがtrueになっているTextあるいはTextMeshProUGUIを検索
    • ワールドのY座標が0未満のTransformを検索
    • ローカルのY座標が0未満のTransformを検索
    • Camera / Light / MeshRendererのそれぞれの合計数を取得
    • レイヤーがDefaultでタグがMainCameraのGameObjectを取得
    • パスにArt/Materials/Characters/を含むマテリアルを検索し、現在のシーンにあるオブジェクトからそれぞれのマテリアルが参照されている数を取得し降順で並べる
  • クエリチートシート - プロジェクト
    • 全てのテクスチャとその横幅を検索
    • 横幅が1024以上2048以下のテクスチャを全て検索して横幅降順に並べる
    • テクスチャ一覧とその被参照数を取得
    • Aniso Levelが3に設定されているテクスチャを検索
    • 全てのアセットサイズを降順で並べる
    • 名前にcloudを含むマテリアルを参照している、シーン以外のアセットを全て取得
    • 名前に cloud あるいは ellen を含むマテリアルを全て検索
    • 指定したシェーダを参照しているマテリアルを全て検索
    • ellenという名前を含むマテリアルを参照するPrefabを全て検索
    • 横幅が4096以上のテクスチャを参照するアセットを全て検索
    • Assets/Textures/Effects あるいは Assets/Textures/Environment 配下にあるテクスチャを全て取得
    • EmissionのBurstCountが1以上のParticle Systemを検索
    • Prefab、テクスチャ、マテリアルの総数をそれぞれ取得
    • テクスチャの各型(Texture2D, Texture3D, Cubemap, etc..)ごとの総数を取得
  • その他
    • graphicsを含む設定項目(Project設定やPreferences)を検索
    • Charactersという名前のフォルダ配下にあるアセットを全て検索
    • Unityエディタでパフォーマンスをトラッキングされている全ての項目とその統計を表示
  • 参考
続きを読む