【Unity】【Addressable】ResourceLocatorを実装して独自のキーでリソースを読み込む

ResourceLocatorを実装して独自のキーでリソースを読み込む方法についてまとめました。

Unity2020.3.17f1
Addressables 1.18.16

ResourceLocator?

AddressableアセットシステムにはIResourceLocatorというインターフェースが存在します。
これには以下のようなLocateメソッドが定義されていて、与えられたキーに対してそのリソースの場所(リソースロケーション)を返します。

// locationsにリソースロケーションが入る
// 戻り値は成功したらtrue
bool Locate(object key, Type type, out IList<IResourceLocation> locations);

デフォルトではこのインターフェースを実装したResourceLocationMapなどのクラスが存在します。
このクラスで、コンテンツカタログから得た情報を使ってキーに対応するリソースロケーションを返しています。

さてこのResourceLocatorは内部的に複数個登録されています。
あるキーに対して、リソースロケーションが正常に得られる(上述のLocateメソッドがtrueを返す)ResourceLocatorの結果が使われます。

// 全てのResourceLocatorを取得
Addressables.ResourceLocators;

さらにユーザがResourceLocatorを追加、削除できるAPIも公開されています。

// 追加、削除
Addressables.AddResourceLocator(resourceLocator);
Addressables.RemoveResourceLocator(resourceLocator);
Addressables.ClearResourceLocators();

ここまでの情報をまとめると、アドレスではなく自身で定義した独自のキーでリソースを読み込むということも可能なはずです。
そこで次節では、ResourceLocatorを実装して独自のキーでリソースを読み込んでみます。

GUIDをキーにしてAssetDatabaseからリソースを読み込むResourceLocatorを作る

今回は簡単なテストのために、GUIDを渡すとAssetDatabase経由でそのGUIDを持つリソースを読み込んでくれるResourceLocatorを作ってみます。
まずソースコードは以下の通りです。

#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
using Object = UnityEngine.Object;

namespace ResourceLocatorTest
{
    public class GuidResourcesLocator : IResourceLocator
    {
        public bool Locate(object key, Type type, out IList<IResourceLocation> locations)
        {
            var guid = key as string;
            if (guid == null)
            {
                locations = null;
                return false;
            }

            // GUIDからアセットパスを取得
            var assetPath = AssetDatabase.GUIDToAssetPath(guid);
            if (string.IsNullOrEmpty(assetPath))
            {
                locations = null;
                return false;
            }

            locations = new List<IResourceLocation>();
            // アセットパスをAssetDatabaseProviderにIDとして渡して、UnityEngine.Objectを読み込む
            locations.Add(new ResourceLocationBase(guid, assetPath, typeof(AssetDatabaseProvider).FullName,
                typeof(Object)));
            return true;
        }

        public IEnumerable<object> Keys => null;

        public string LocatorId => nameof(GuidResourcesLocator);
    }
}
#endif

Locateメソッド内で、キーが有効なGUIDだった場合にResourceLocationBaseを生成して返しています。
リソースロケーションの指定方法は上記のように、ResourceProviderの型を表す文字列とそれに渡すIDをセットにして生成します。
今回はAddressableのAssetDatabaseProviderを使い、GUIDから取得したアセットパスをIDとして渡しています。

動作確認

それでは次に前節で作ったGuidResourcesLocatorの動作確認をしていきます。
以下のように、GUIDを指定してGameObjectを読み込むスクリプトを記述します。

using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;

namespace ResourceLocatorTest
{
    public class Example : MonoBehaviour
    {
        [SerializeField] private string _guid;
    
        private async void Start()
        {
#if UNITY_EDITOR
            // 前節で作ったGuidResourcesLocatorを登録
            Addressables.AddResourceLocator(new GuidResourcesLocator());
        
            // AssetDatabaseProviderを使用するので、ResourceManagerに登録
            var resourceProviders = Addressables.ResourceManager.ResourceProviders;
            if (resourceProviders.All(x => x.GetType() != typeof(AssetDatabaseProvider)))
            {
                resourceProviders.Add(new AssetDatabaseProvider());
            }
#endif

            // GUIDをキーにして読み込む
            var handle = Addressables.LoadAssetAsync<GameObject>(_guid);
            await handle.Task;
            if (handle.Status == AsyncOperationStatus.Succeeded)
            {
                Instantiate(handle.Result);
            }
            Debug.Log(handle.Status);
        }
    }
}

説明はコメントに書いた通りです。
使用するResourceProviderは事前にResourceManagerに登録しておく必要があるので、
GuidResourcesLocatorで使用しているAssetDatabaseProviderの登録を忘れないようにしてください。

GUIDを指定してPrefabを読み込むと正常にインスタンス化されることを確認できます。

結果