UnityのAddressableアセットシステムでアセットバンドルからアセットをロードする処理をカスタムする方法です。
Unity2019.2.10
Addressable1.3.8
はじめに
この記事ではAddressableでアセットバンドルからアセットをロードする処理をカスタムする方法をまとめます。
Addressableの概念や基礎知識についての説明はこの記事では省略しますが、
以下の記事にまとめていますので、必要に応じて参照してください。
アセットをロードする処理をカスタム
以前、Addressablesでアセットバンドルのダウンロードやロードの処理をカスタムする方法を紹介しました。
こうしてロードされたアセットバンドルから、また別のProviderを使ってアセットをロードします。
このProviderもAddressableで標準で用意されてはいますが、独自で作成することもできます。
本記事ではこの方法についてまとめます。
ソースコード(シンプル版)
それでは早速ですが、まずカスタムProviderのソースコード全文を記載します。
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.ResourceManagement.ResourceProviders; [System.ComponentModel.DisplayName("Custom Bundled Asset Provider Simple")] public class CustomBundledAssetProviderSimple : ResourceProviderBase { public class InternalOp { private AssetBundleRequest _requestOperation; private ProvideHandle _provideHandle; public void Fetch(ProvideHandle provideHandle) { provideHandle.SetProgressCallback(ProgressCallback); _provideHandle = provideHandle; _requestOperation = null; // 依存関係にあるAssetBundleを取得してロード var dependencies = new List<object>(); _provideHandle.GetDependencies(dependencies); var bundle = LoadBundleFromDependecies(dependencies); if (bundle == null) { _provideHandle.Complete<AssetBundle>(null, false, new Exception("Unable to load dependent bundle")); return; } var assetPath = _provideHandle.Location.InternalId; // アセットをロード _requestOperation = bundle.LoadAssetAsync(assetPath, _provideHandle.Type); _requestOperation.completed += op => { // ロード完了したらPovideHandle.Completeを呼ぶ object result = null; if (_requestOperation != null) { result = (_requestOperation.asset != null && _provideHandle.Type.IsAssignableFrom(_requestOperation.asset.GetType())) ? _requestOperation.asset : null; } _provideHandle.Complete(result, result != null, null); }; } /// <summary> /// 依存関係にあるアセットバンドルをロードして元のアセットバンドルを返す /// </summary> private AssetBundle LoadBundleFromDependecies(IList<object> dependencies) { if (dependencies == null || dependencies.Count == 0) { return null; } AssetBundle bundle = null; var isFirst = true; for (int i = 0; i < dependencies.Count; i++) { var assetBundleResource = dependencies[i] as IAssetBundleResource; if (assetBundleResource != null) { // ロード var b = assetBundleResource.GetAssetBundle(); if (isFirst) { // 最初のアセットバンドルを返却 bundle = b; } isFirst = false; } } return bundle; } public float ProgressCallback() { return _requestOperation != null ? _requestOperation.progress : 0.0f; } } public override void Provide(ProvideHandle provideHandle) { new InternalOp().Fetch(provideHandle); } }
細かい説明はコメントに書いた通りなので省略します。
大枠として、独自のProviderを作るにはResourceProviderBase
を継承したクラスを作ります。
ロードがリクエストされるとProvide()
が呼ばれるのでここにロード処理を書きます。
ソースコード(ちゃんとした版)
単体のアセットをロードするだけなら上記のソースコードで充分ですが、
Addressablesにはサブアセットをまとめてロードしたり、特定のサブアセットを指定してロードする機能があります。
これらに対応するためには以下のようにソースコードを書き換える必要があります。
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.ResourceManagement.ResourceProviders; using System.Collections; [System.ComponentModel.DisplayName("Custom Bundled Asset Provider")] public class CustomBundledAssetProvider : ResourceProviderBase { public class InternalOp { private AssetBundleRequest _requestOperation; private ProvideHandle _provideHandle; private string _subObjectName = null; public void Fetch(ProvideHandle provideHandle) { provideHandle.SetProgressCallback(ProgressCallback); _subObjectName = null; _provideHandle = provideHandle; _requestOperation = null; // 依存関係にあるAssetBundleを取得してロード var dependencies = new List<object>(); _provideHandle.GetDependencies(dependencies); var bundle = LoadBundleFromDependecies(dependencies); if (bundle == null) { _provideHandle.Complete<AssetBundle>(null, false, new Exception("Unable to load dependent bundle")); return; } var assetPath = _provideHandle.Location.InternalId; if (_provideHandle.Type.IsArray) { // 全てのサブアセットをリストで表示する場合 _requestOperation = bundle.LoadAssetWithSubAssetsAsync(assetPath, _provideHandle.Type.GetElementType()); } else if (_provideHandle.Type.IsGenericType && typeof(IList<>) == _provideHandle.Type.GetGenericTypeDefinition()) { // 全てのサブアセットをリストで表示する場合(Generic) _requestOperation = bundle.LoadAssetWithSubAssetsAsync(assetPath, _provideHandle.Type.GetGenericArguments()[0]); } else { var i = assetPath.LastIndexOf('['); if (i > 0) { // 中括弧がある場合はサブアセットから取得 var i2 = assetPath.LastIndexOf(']'); if (i2 < i) { _provideHandle.Complete<AssetBundle>(null, false, new Exception(string.Format("Invalid index format in internal id {0}", assetPath))); } else { _subObjectName = assetPath.Substring(i + 1, i2 - (i + 1)); assetPath = assetPath.Substring(0, i); _requestOperation = bundle.LoadAssetWithSubAssetsAsync(assetPath, _provideHandle.Type); } } else { // アセットを取得 _requestOperation = bundle.LoadAssetAsync(assetPath, _provideHandle.Type); } } _requestOperation.completed += OnComplete; } /// <summary> /// 依存関係にあるアセットバンドルをロードして元のアセットバンドルを返す /// </summary> private AssetBundle LoadBundleFromDependecies(IList<object> dependencies) { if (dependencies == null || dependencies.Count == 0) { return null; } AssetBundle bundle = null; var isFirst = true; for (int i = 0; i < dependencies.Count; i++) { var assetBundleResource = dependencies[i] as IAssetBundleResource; if (assetBundleResource != null) { // ロード var b = assetBundleResource.GetAssetBundle(); if (isFirst) { // 最初のアセットバンドルを返却 bundle = b; } isFirst = false; } } return bundle; } private void OnComplete(AsyncOperation obj) { object result = null; if (_requestOperation != null) { if (_provideHandle.Type.IsArray) { result = CreateArrayResult(_provideHandle.Type, _requestOperation.allAssets); } else if (_provideHandle.Type.IsGenericType && typeof(IList<>) == _provideHandle.Type.GetGenericTypeDefinition()) { result = CreateListResult(_provideHandle.Type, _requestOperation.allAssets); } else { if (string.IsNullOrEmpty(_subObjectName)) { result = (_requestOperation.asset != null && _provideHandle.Type.IsAssignableFrom(_requestOperation.asset.GetType())) ? _requestOperation.asset : null; } else { if (_requestOperation.allAssets != null) { foreach (var o in _requestOperation.allAssets) { if (o.name == _subObjectName) { if (_provideHandle.Type.IsAssignableFrom(o.GetType())) { result = o; break; } } } } } } } _provideHandle.Complete(result, result != null, null); } private Array CreateArrayResult(Type type, System.Object[] allAssets) { var elementType = type.GetElementType(); if (elementType == null) return null; int length = 0; foreach (var asset in allAssets) { if (asset.GetType() == elementType) length++; } var array = Array.CreateInstance(elementType, length); int index = 0; foreach (var asset in allAssets) { if (elementType.IsAssignableFrom(asset.GetType())) array.SetValue(asset, index++); } return array; } private TObject CreateArrayResult<TObject>(System.Object[] allAssets) where TObject : class { return CreateArrayResult(typeof(TObject), allAssets) as TObject; } private IList CreateListResult(Type type, System.Object[] allAssets) { var genArgs = type.GetGenericArguments(); var listType = typeof(List<>).MakeGenericType(genArgs); var list = Activator.CreateInstance(listType) as IList; var elementType = genArgs[0]; if (list == null) return null; foreach (var a in allAssets) { if (elementType.IsAssignableFrom(a.GetType())) list.Add(a); } return list; } private TObject CreateListResult<TObject>(UnityEngine.Object[] allAssets) { return (TObject)CreateListResult(typeof(TObject), allAssets); } public float ProgressCallback() { return _requestOperation != null ? _requestOperation.progress : 0.0f; } } public override void Provide(ProvideHandle provideHandle) { new InternalOp().Fetch(provideHandle); } }
このあたりはAddressableで用意されているBundled Asset Provider
をベースに作ったほうがよさそうです。
Providerを設定する
あとはこのProviderを使うように設定するだけです。
ProviderはAssetGroupのInspectorから設定できます。
Asset Providerから先ほど作ったProviderが選択できるので、これを選択すればOKです。
これでアセットバンドルからアセットをロードする処理がカスタムできました。