Addressableアセットシステムの中で使われているResourceManagerの使い方をまとめました。
Unity2019.3.5
Addressables 1.7.5
Resource Manager?
Addressableアセットシステムは、内部的にResourceManagerというリソース管理システムを使用しています。
これを使うとResourcesやAssetBundleなど様々なリソース読み込みを共通のインタフェースで実装できます。
ProviderをカスタムしたりAddressableを少し高度に使う場合には理解が必要になりますが、公式ドキュメントは全くと言っていいほどありません。
おとなしくAddressableの標準機能を使えというお告げな気もしますが、本記事ではこのResource Managerの使い方をまとめてみます。
Resourcesから読み込む
さてまずはResource ManagerでResourcesフォルダからリソースを読み込むシンプルな例で基本的な流れを確認します。
まずはコード全文です。
using UnityEngine; using UnityEngine.ResourceManagement; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; public class Example : MonoBehaviour { private ResourceManager _resourceManager; private void Awake() { // ResourceMangerを生成 _resourceManager = new ResourceManager(); // 使用するProviderを登録 _resourceManager.ResourceProviders.Add(new LegacyResourcesProvider()); } private void Start() { var resourceId = "Example"; // リソースID(LegacyResourcesProviderを使う場合はResourcesからの拡張子なしの相対パス) var providerId = typeof(LegacyResourcesProvider).FullName; // 使用するProviderの型のFullName var resouceType = typeof(GameObject); // リソースの型 // ResourceLocationを生成する var location = new ResourceLocationBase(resourceId, resourceId, providerId, resouceType); // locationを使って読み込む var handle = _resourceManager.ProvideResource<GameObject>(location); handle.Completed += x => { Instantiate(handle.Result); // 不要になったら解放 _resourceManager.Release(handle); }; } private void OnDestroy() { // ResourceManagerを破棄 _resourceManager.Dispose(); } }
ResourceManagerはいろんなリソースの読み込み方を同じインタフェースで実現している関係で
使い方が若干複雑なので、上のコードで登場した概念について以下で一つ一つ説明します。
Providerについて
まずAwakeメソッドで生成しているLegacyResourcesProvider
について説明します。
ResourceManagerではProviderと呼ばれるクラスがリソースの取得を行います。
このProviderには複数の種類が存在し、例えばResourcesから読み込むためのもの、AssetDatabaseから読み込むためのもの、
AssetBundleを読み込むためのもの、AssetBundleからAssetを読み込むためのものなどが用意されています。
今回はそのうちResourcesから読み込むためのProviderであるLegacyResourcesProvider
を生成しています。
生成したProviderはResourceManager.ResourceProviders.Add()
でResourceManagerに登録しておきます。
_resourceManager.ResourceProviders.Add(new LegacyResourcesProvider());
ちなみにProviderは複数登録しておくことができます。
Resource Locationについて
次にStartメソッド内で生成しているResourceLocationBase
について説明します。
これはResourceManagerに読み込みで使用するProviderの種類やProviderがリソースを特定するのに使うID、読み込むリソースの型などを教えます。
今回のようにLegacyResourceProvider
を使う場合、IDにはResourcesフォルダからの拡張子なしの相対パスを使います。
var resourceId = "Example"; // リソースID(LegacyResourcesProviderを使う場合はResourcesからの拡張子なしの相対パス) var providerId = typeof(LegacyResourcesProvider).FullName; // 使用するProviderの型のFullName var resouceType = typeof(GameObject); // リソースの型 // ResourceLocationを生成する var location = new ResourceLocationBase(resourceId, resourceId, providerId, resouceType);
ちなみにResourceLocationBase
はIResourceLocation
を実装したクラスです。
こうして作ったIResourceLocation
をResourceManagerに渡せばリソースを取得できます。
_resourceManager.ProvideResource<GameObject>(location)
解放処理について
ResourceManagerは内部でリソースの参照カウントを管理しています。
リソースの取得を行ったときに参照カウントがインクリメントされます。
また不要になったリソースの参照カウントは減らす必要があります。
参照カウントを減らすには、ResourceManager.Release()
を呼びます。
_resourceManager.Release(handle);
またResourceManager自体も不要になったら破棄する必要があります。
ResourceManager.Dispose();
AssetDatabaseから読み込む
次にAssetDatabaseProvider
を使ってAssetDatabaseからリソースを読み込んでみます。
使い方はLegacyResourcesProvider
とほぼ変わりません。
using UnityEngine; using UnityEngine.ResourceManagement; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; public class Example : MonoBehaviour { private ResourceManager _resourceManager; private void Awake() { _resourceManager = new ResourceManager(); // AssetDatabaseProviderを登録 _resourceManager.ResourceProviders.Add(new AssetDatabaseProvider()); } private void Start() { var resourceId = "Assets/Example/Example.prefab"; // アセットパス var providerId = typeof(AssetDatabaseProvider).FullName; // AssetDatabaseProviderのFullName var resouceType = typeof(GameObject); var location = new ResourceLocationBase(resourceId, resourceId, providerId, resouceType); var handle = _resourceManager.ProvideResource<GameObject>(location); handle.Completed += x => { Instantiate(handle.Result); _resourceManager.Release(handle); }; } private void OnDestroy() { _resourceManager.Dispose(); } }
LegacyResourcesProvider
のソースコードから変更した部分のみコメントを記載しています。
リソースIDにアセットパス(Assetsフォルダからのパス・拡張子あり)を設定する点に注意が必要です。
AssetBundleからリソースを読み込む
次に若干複雑なAssetBundleからのリソース取得について説明します。
まずはソースコードの全文を記載します。
using UnityEngine; using UnityEngine.ResourceManagement; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; public class Example : MonoBehaviour { private ResourceManager _resourceManager; private void Awake() { _resourceManager = new ResourceManager(); // AssetBundleProviderとBundledAssetProviderを登録する _resourceManager.ResourceProviders.Add(new AssetBundleProvider()); _resourceManager.ResourceProviders.Add(new BundledAssetProvider()); } private void Start() { // アセットパスをIDにしてResourceLocationを作成 // ProviderはBundledAssetProvider var assetPath = "Assets/Bundles/PrefabA.prefab"; var location = new ResourceLocationBase(assetPath, assetPath, typeof(BundledAssetProvider).FullName, typeof(GameObject)); // Prefabが所属するAssetBundleをDependenciesに追加 // ProviderはAssetBundleProvider // 順番大事: 当該アセットが含まれているAssetBundleを一番最初に持ってくること var prefabBundlePath = "http://(リソース用URL)/prefab_a.bundle"; location.Dependencies.Add ( new ResourceLocationBase(prefabBundlePath, prefabBundlePath, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource)) ); // 依存するAssetBundleをDependenciesに追加 // ProviderはAssetBundleProvider var shaderBundlePath = "(StreamingAssetsのパス)/defaultlocalgroup_unitybuiltinshaders.bundle"; location.Dependencies.Add ( new ResourceLocationBase(shaderBundlePath, shaderBundlePath, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource)) ); location.ComputeDependencyHash(); // ロード _resourceManager.ProvideResource<GameObject>(location).Completed += x => { Instantiate(x.Result); _resourceManager.Release(x); }; } private void OnDestroy() { _resourceManager.Dispose(); } }
AssetBundleを読み込むためにはBundledAssetProviderとAssetBundleProviderの二つのProviderを使う必要があります。
以下、それぞれについて細かく説明します。
BundledAssetProvider
BundledAssetProviderはAssetBundleからAssetを読み込むためのProviderです。
アセットパスをIDとして与えると、指定されたAssetBundleからそのアセットを読み込みます。
var assetPath = "Assets/Bundles/PrefabA.prefab"; var location = new ResourceLocationBase(assetPath, assetPath, typeof(BundledAssetProvider).FullName, typeof(GameObject));
さてこのProviderはあくまで与えられたAssetBundleからアセットを読み込むためのものであるため、
AssetBundleのロードやダウンロードは別のProviderで行って、その結果をBundledAssetProviderに渡す必要があります。
AssetBundleProvider
BundledAssetProviderに渡すためのAssetBundleのロードやダウンロードを行うのがAssetBundleProviderです。
AssetBundleProviderは、AssetBundleのURLやStreamingAssetsなどのローカルパスを与えることでそこからAssetBundleを読み込みます。
型にはIAssetBundleResource型を指定します。
// URLを与える場合 var prefabBundlePath = "http://(リソース用URL)/prefab_a.bundle"; var prefabBundleLocation = new ResourceLocationBase(prefabBundlePath, prefabBundlePath, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource)) // ローカルパスを与える場合 var shaderBundlePath = "(StreamingAssetsのパス)/defaultlocalgroup_unitybuiltinshaders.bundle"; var shaderBundleLocation = new ResourceLocationBase(shaderBundlePath, shaderBundlePath, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource))
ローカルパスを与えた場合にはAssetBundle.LoadFromFileAsync()
を使ってAssetBundleが読み込まれます。
これに対してURLを与えた場合にはUnityWebRequestAssetBundle.GetAssetBundle()
が使われます。
従って、UnityWebRequestAssetBundleのキャッシュ機構に基づいて、ローカルキャッシュがあればそちらからロードされ、なければダウンロードが行われます。
さてこのようにして作成したAssetBundleProviderは前節で作ったBundledAssetProviderのResourceLocationのDependenciesに追加します。
こうすることでAssetBundleProviderで読み込まれたAssetBundleがBundledAssetProviderに渡されます。
またDependenciesに追加する際には読み込み対象のアセットが含まれるAssetBundleを最初に追加する必要があります。
Sceneを読み込む
ResourceManagerを使ってシーンを読み込む際には使うべきメソッドがResourceManager.ProvideScene()
に変わります。
組み込みシーンを読み込む
まずビルド設定にシーンを追加してビルドにシーンを含んでいる場合には、以下のように読み込みます。
using System; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; public class Example : MonoBehaviour { private ResourceManager _resourceManager; private SceneProvider _sceneProvider; private void Awake() { _resourceManager = new ResourceManager(); // SceneProviderを生成 _sceneProvider = new SceneProvider(); } private void Start() { // Assetsからの相対パス(Assetsは含まない) = Build Settingsに表示されるパス で指定する必要がある // シーン名だけだと実機だと読み込めるがエディタでうまくいかない(ケースがある)ので注意 // AssetsPathだとエディタだとうまくいくが実機だと読めないので注意 var path = "Scenes/Example2"; // ResourceLocationを作成 // 型はSceneInstance var location = new ResourceLocationBase(path, path, typeof(SceneProvider).FullName, typeof(SceneInstance)); // ProvideSceneで読み込み _resourceManager.ProvideScene(_sceneProvider, location, LoadSceneMode.Additive, true, 0); } private void OnDestroy() { _resourceManager.Dispose(); } }
パスにはAssetsからの相対パス(Build Settingsに表示されているパス)を指定する点に注意してください。
AssetBundleから読み込む
次にAssetBundleに格納されているシーンを読み込みます。
要領は非シーンのリソースをAssetBundle経由で読み込む際とほぼ同じです。
using System; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; using UnityEngine.UIElements; public class Example : MonoBehaviour { private ResourceManager _resourceManager; private SceneProvider _sceneProvider; private void Awake() { _resourceManager = new ResourceManager(); _sceneProvider = new SceneProvider(); _resourceManager.ResourceProviders.Add(new BundledAssetProvider()); _resourceManager.ResourceProviders.Add(new AssetBundleProvider()); } private void Start() { // シーン名をIDにしてResourceLocationを作成 var name = "Example2"; // Assetsからの相対パスでもOK //var name = "Scenes/Example2"; // AssetPathでもOK //var name = "Assets/Scenes/Example2.unity"; var location = new ResourceLocationBase(name, name, typeof(BundledAssetProvider).FullName, typeof(SceneInstance)); // Sceneを含むAssetBundleをDependenciesに追加 var sceneBundlePath = "http://(リソース用URL)/scene_a.bundle"; location.Dependencies.Add ( new ResourceLocationBase(sceneBundlePath, sceneBundlePath, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource)) ); // 依存するAssetBundleをDependenciesに追加 var shaderBundlePath = "(StreamingAssetsのパス)/defaultlocalgroup_unitybuiltinshaders.bundle"; location.Dependencies.Add ( new ResourceLocationBase(shaderBundlePath, shaderBundlePath, typeof(AssetBundleProvider).FullName, typeof(IAssetBundleResource)) ); location.ComputeDependencyHash(); // ProvideSceneでロード _resourceManager.ProvideScene(_sceneProvider, location, LoadSceneMode.Additive, true, 0); } private void OnDestroy() { _resourceManager.Dispose(); } }