【Addressables入門2】Addressablesをもうちょっとだけ使ってみる(いろんなロード方法、アンロード)

今回は以下の記事の続きです。

あわせて読みたい
Unity Addressables入門(1.サーバーからデータをロードする) Addressablesってなに? Unityでゲーム開発をしていてこんなことに直面したことはないでしょうか?・ゲームの起動時間を短くしたい・アプリの容量が150MBを超えてしまい、...
目次

Addressablesのファイルの中身を理解

前回の記事で、Addressablesを使えばサーバーからアセットをロードすることができ、アプリ本体の容量の節約やメモリの節約に繋がる方法を解説しました。

今回は、このAddressablesをもう少しだけ深く理解します。

ビルド時に生成されるファイルの中身

Addressablesでビルドを行うと以下の3種類のファイルが作成されます。

・.build
・catalog.hash
・catalog.json

この.build拡張子のファイルは、実際にアセットをビルドしたデータの本体です。
catalog.jsonにはアセットの依存関係や、アセットのパスの情報が含まれています。

たとえば、一部抜粋ですが、以下のようにパスが格納されています。

"m_ProviderIds":
["UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider",
"UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider"],
"m_InternalIds":["Assets/0MyAssets/Data/Stage1.prefab",
"Assets/0MyAssets/Data/StageData.asset","Assets/0MyAssets/Data/Yu-Rin-ChiGameStudioLogo.png",
"https://storage.googleapis.com/addressables-test-yu-rin-chi/stage-data/e037aecb4616c6aa247ca0aa3f7616cb_monoscripts_59d65a2812ccb7c76839ba1f0ac334bd.bundle",

なので、アセットをちゃんとビルドしたのにちゃんと読み込めない、という時はcatalog.jsonのファイルの中身を確認するのが良いと思います。
そもそもcatalog.jsonにビルドしたデータが登録されているのか、catalog.jsonのパスと、実際のパスと一致しているのか、等。

指定した条件に合致するデータをロードする

ラベルを条件に取得する

実際のAddressablesの運用では、アセットを複数まとめてロードするというケースがあるかと思います。
そういった時に使うのが、ラベルを使ったロードの方法を紹介します。

ラベルは、AddressablesGroupsのウィンドウで設定します。

Labelsを選択するとManageLabelsを選択出来ます。

適当なラベルを作って、アセットに割り当てればラベルの設定は完了です。ラベルは複数選択することも可能です。

これをビルドしてサーバーにアップロードします。

次に、ロード処理です。ここからは、UniTaskの使用前提のコードで記載します。
サーバーに上がっている、「sample」というラベルの付いたTextureをまとめてロードして、UIに表示するコードです。

アセットロードする場合は、以下の構文を使います。

Addressables.LoadAssetsAsync<T>(label, callback, Addressables.MergeMode);

第1引数には、labelかキーを設定します。注意点としては、ラベルで指定する場合も、キーで指定する場合も同じ引数として指定するのでキーと被らないラベル名にする必要があります。

第2引数には、ロードしたあとのコールバックを指定します。

第3引数には、複数のラベルを使ってロードするときの条件を指定するMergeModeを指定します。ラベルを1つしか条件に入れない場合は不要です。
たとえば、ラベルAかつBの条件でロードする場合など。こちらの記事に全部書いてあるので、このブログでは割愛。

using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;

public class SampleImageLoader : MonoBehaviour
{
    [SerializeField] private List<Image> _targetImages; // ロードした画像の表示先
    [SerializeField] private Button _loadSpriteButton;  // ロードボタン

    private void Start()
    {
        _loadSpriteButton.onClick.AddListener(() => LoadImageAsync("sample").Forget());
    }

    public async UniTask LoadImageAsync(string label)
    {
        List<Texture2D> loadedTextures = new();

        // label が付いている Texture2D アセットをすべて非同期で読み込み、それぞれ読み込まれたタイミングで loadedTextures リストに追加
        var handle = Addressables.LoadAssetsAsync<Texture2D>(label, texture =>
        {
            loadedTextures.Add(texture);
        });

        await handle.Task;

        Debug.Log($"{loadedTextures.Count} 枚の画像をロードしました");

        var sortedTextures = loadedTextures.OrderBy(t => t.name).ToList();

        for (int i = 0; i < _targetImages.Count; i++)
        {
            if (i < sortedTextures.Count)
            {
                // Texture2D を Sprite に変換して Image に設定
                var sprite = Sprite.Create(
                    sortedTextures[i],
                    new Rect(0, 0, sortedTextures[i].width, sortedTextures[i].height),
                    new Vector2(0.5f, 0.5f)
                );

                _targetImages[i].sprite = sprite;
                _targetImages[i].gameObject.SetActive(true);
            }
            else
            {
                _targetImages[i].gameObject.SetActive(false);
            }
        }
    }

}

これで実行すると、以下のように設定したラベルデータに相当するテクスチャデータをまとめてロードすることが出来ました。

インスペクタで指定したアセットをロードする

インスペクタで指定したアセットをロードする方法もあります。
まずは、インスペクタでAssetReference型の変数を定義します。

[SerializeField] private AssetReference reference;

すると、Addressablesの対象オブジェクトが指定出来るようになります。

これをロードするには、以下のコードを記述します。

reference.LoadAssetAsync<T>();

まとめるとこんな感じ。

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AddressableAssets;
using Cysharp.Threading.Tasks;

public class Texture2DLoaderWithAssetReference : MonoBehaviour
{
    [Header("表示先のImageコンポーネント")]
    [SerializeField] private Image _targetImage;

    [Header("インスペクタで指定するアセット")]
    [SerializeField] private AssetReference _reference;

    private void Start()
    {
        // 起動時にロードして表示
        LoadAndDisplayTexture().Forget();
    }

    private async UniTaskVoid LoadAndDisplayTexture()
    {
        // Texture2Dとしてロード
        var handle = _reference.LoadAssetAsync<Texture2D>();
        Texture2D texture = await handle.ToUniTask();

        if (texture == null)
        {
            Debug.LogError("Texture のロードに失敗しました");
            return;
        }

        // Spriteを生成
        Sprite sprite = Sprite.Create(
            texture,
            new Rect(0, 0, texture.width, texture.height),
            new Vector2(0.5f, 0.5f)
        );

        // Imageにセット
        _targetImage.sprite = sprite;
        _targetImage.gameObject.SetActive(true);
    }
}

実行すると、ちゃんとロード出来ました。

Addressablesのロードしたデータをアンロードする

次に、ゲームで使用するためにロードしたデータは、不要になったタイミングでアンロードを行う必要があります。アンロードしないと、サーバーから取得したアセットデータがメモリに残り続けてしまいます。

アンロードする場合は、以下のコードで行います。

Addressables.ReleaseInstance(instance);

例えばステージをロードして、そのステージを破棄する時にアンロードする場合は以下のように記述します。

前回の記事で作成したステージをロードするコードを少し改修して、Prefabのロード、アンロードを行うようにしました。

using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;

public class StagePrefabLoader : MonoBehaviour
{
    [Header("Addressablesで指定したプレハブ参照(AssetReference)")]
    public AssetReference loadedStagePrefab;

    [Header("生成する位置")]
    public Transform spawnPoint;

    [Header("ロード、アンロードボタン")]
    public Button loadButton;
    public Button unloadButton;

    private GameObject _spawnedStageInstance;
    private AsyncOperationHandle<GameObject> _instanceHandle;

    private void Start()
    {
        loadButton.onClick.AddListener(() => LoadStagePrefabAsync().Forget());
        unloadButton.onClick.AddListener(DestroyStage);
    }

    private async UniTask LoadStagePrefabAsync()
    {
        if (_spawnedStageInstance != null)
        {
            Debug.LogWarning("すでにプレハブが生成されています。先にアンロードしてください。");
            return;
        }

        try
        {
            Debug.Log("プレハブのロードと生成を開始...");

            _instanceHandle = loadedStagePrefab.InstantiateAsync(spawnPoint.position, Quaternion.identity);
            _spawnedStageInstance = await _instanceHandle.ToUniTask();

            if (_spawnedStageInstance != null)
            {
                Debug.Log($"プレハブ生成成功: {_spawnedStageInstance.name}");
            }
            else
            {
                Debug.LogError("プレハブの生成に失敗しました。");
            }
        }
        catch (System.Exception ex)
        {
            Debug.LogError($"プレハブのロード・生成に失敗: {ex.Message}");
        }
    }

    /// <summary>
    /// 生成済みのプレハブを削除してアンロード
    /// </summary>
    public void DestroyStage()
    {
        if (_spawnedStageInstance != null)
        {
            Debug.Log($"プレハブを削除: {_spawnedStageInstance.name}");
            Addressables.ReleaseInstance(_spawnedStageInstance);

            _spawnedStageInstance = null;
            _instanceHandle = default;
        }
        else
        {
            Debug.Log("削除対象のプレハブがありません。");
        }
    }
}

これで実行してみましょう。

実際にメモリからアンロードされているかは、プロファイラー(Ctrl+7)から確認出来ます。
※たまにEventViewerからロード状態を参照出来る旨が書書かれている記事がありますが、古い情報です。Unity2023以降であればProfilerから見ましょう。

ロード前

ロード後

アンロード後

少し分かりづらいですが、黄緑色の線がロードされたアセットなので、ちゃんとアンロードされていることがわかりますね。

まとめ

まだまだ深いところまでは解説しきれていませんが、最低限Addressablesを使う機能は紹介出来たかと思います。他に解説の要望があればコメントまで。
ゲームを軽くしてユーザの体験をアゲちゃいましょ~

それでは、素敵なゲーム制作ライフを!

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

ゲーム制作の敷居を下げ、もっと多くの人にゲーム作りを楽しんでもらうために、ゲームをカンタンに作る方法を”網羅的に”解説しています。
よかったらブックマークお願いします。
Twitter(X)もよければフォローお願いします。

コメント

コメントする


reCaptcha の認証期間が終了しました。ページを再読み込みしてください。

目次