In side head

〜ある技術者の記録〜

【Unity】 Addressablesのテストデータが欲しい

Addressablesにテストデータを追加したい事ってありませんか?そういう時に使えるテクニックです。

環境

シナリオ

例えば、画像 + ナレーションなどが含まれるストーリープレイヤーを作りたいとします。
メモリの使用量を抑えるため、プレイヤーの再生時に必要な画像をロードしたい場合、現状の環境ではAddressablesが最適な選択肢です。

テストシーンを準備して、開発を進めていく中で本番とは切り離したテストデータが欲しくなります。 ですが、Addressablesの仕様上、テストとして追加したものでも本番のビルドに含まれてしまいます。

それを本番には含めずテストのみで使用する方法です。

解決方法

Editor再生のタイミングでグループを作成して、必要なリソースを追加します。
そして、Editorを終了するタイミングで追加したグループとリソースを削除します。
このように動的に追加削除することで本番とは完全に切り離したテストデータが使用できます。

注意

  • Editor上でしか動作しません
  • 今回は、テストシーンを用意する前提ですがTestRunnerでも動作します。

AddressablesTest.cs

下記のコードでは、SpriteをInspectorからアタッチしてますが、そうすると再生時にメモリ上に展開されるのでメモリテストにはなりません。

テストの流れ

下記の流れで実行します。

[ Editor再生 ]
  1. Addressablesにグループ作成
  2. グループにSpriteを追加
  3. AssetReferenceSpriteを作成
  4. SpriteをAssetReferenceSpriteからロード
  5. SpriteRendererに表示
[ Editor停止 ]
  1. Spriteのアンロード
  2. グループを削除
#if UNITY_EDITOR
using System.Collections;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Utils;

public class AddressablesTest : MonoBehaviour
{
    const string _testGroupName = "__TEST_GROUP__";
    
    [SerializeField] Sprite _sprite;
    [SerializeField] SpriteRenderer _renderer;

    AssetReferenceSprite _reference;
    AsyncOperationHandle<Sprite> _handle;
    
    // グループの作成
    void Awake() => _testGroupName.CreateOrLoadGroup();

    IEnumerator Start()
    {
        // グループにスプライトを追加
        var entry = _testGroupName.SetEntry(_sprite);
        _reference = new AssetReferenceSprite(entry.guid);
        // 追加したスプライトのロードテスト
        var handle = Addressables.LoadAssetAsync<Sprite>(_reference);
        yield return handle;
        _renderer.sprite = handle.Result;
    }
    
    void OnDestroy()
    {
        if (_handle.IsValid())
        {
            Addressables.Release(_handle);
        }
        // グループの削除
        _testGroupName.RemoveGroup();
    }
}
#endif

AddressableExtensions.cs

なんかごちゃごちゃ書いてますが、ほぼオーバーロード。見にくくてごめんなさい。

#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
using Object = UnityEngine.Object;

namespace Utils
{
    public static class AddressableExtensions
    {
        public static AddressableAssetSettings GetSettings()
        {
            var settings = AddressableAssetSettingsDefaultObject.Settings;
            if (settings == null)
            {
                throw new InvalidOperationException("Addressables settings is not found. Execute \"Create Addressables Settings\" in Window > Asset Management > Addressables > Group.");
            }
            return settings;
        }

        public static AddressableAssetGroup CreateOrLoadGroup(this string groupName)
            => GetSettings().CreateOrLoadGroup(groupName);

        public static AddressableAssetGroup CreateOrLoadGroup(this AddressableAssetSettings settings, string groupName)
        {
            var group = settings.FindGroup(groupName);
            return group != null
                ? group
                : settings.CreateGroup(groupName);
        }

        public static AddressableAssetGroup CreateGroup(this AddressableAssetSettings settings, string groupName)
        {
            var template = settings.GroupTemplateObjects
                .OfType<AddressableAssetGroupTemplate>()
                .First(x => x.HasSchema(typeof(BundledAssetGroupSchema)));
            var group = settings.CreateGroup(groupName, false, false, false, null, template.GetTypes());
            template.ApplyToAddressableAssetGroup(group);
            return group;
        }

        public static AddressableAssetEntry SetEntry(this AddressableAssetGroup group, Object obj, string newAddress)
        {
            var entry = group.SetEntry(obj);
            entry.address = newAddress;
            return entry;
        }

        public static AddressableAssetEntry SetEntry(this AddressableAssetGroup group, Object obj)
            => GetSettings().SetEntry(group, obj);

        public static AddressableAssetEntry SetEntry(this AddressableAssetSettings settings, AddressableAssetGroup group, Object obj, string newAddress)
        {
            var entry = settings.SetEntry(group, obj);
            entry.address = newAddress;
            return entry;
        }

        public static AddressableAssetEntry SetEntry(this string groupName, Object obj)
        {
            var settings = GetSettings();
            var group = settings.FindGroup(groupName);
            if (group == null)
            {
                throw new InvalidOperationException("Group is not found. GroupName: " + groupName);
            }
            return settings.SetEntry(group, obj);
        }
        
        public static AddressableAssetEntry SetEntry(this AddressableAssetSettings settings, string groupName, Object obj)
        {
            var group = settings.FindGroup(groupName);
            if (group == null)
            {
                throw new InvalidOperationException("Group is not found. GroupName: " + groupName);
            }
            return settings.SetEntry(group, obj);
        }
        
        public static AddressableAssetEntry SetEntry(this AddressableAssetSettings settings, AddressableAssetGroup group, Object obj)
        {
            var path = AssetDatabase.GetAssetPath(obj);
            var guid = AssetDatabase.AssetPathToGUID(path);
            var entry = settings.CreateOrMoveEntry(guid, group, false, false);
            return entry;
        }

        public static void RemoveGroup(this string groupName) => GetSettings().RemoveGroup(groupName);

        public static void RemoveGroup(this AddressableAssetSettings settings, string groupName)
        {
            var group = settings.FindGroup(groupName);
            if (group == null)
            {
                return;
            }
            settings.RemoveGroup(group);
            AssetDatabase.SaveAssets();
        }

        public static void RemoveAllEntry(string groupName) => GetSettings().RemoveAllEntry(groupName);

        public static void RemoveAllEntry(this AddressableAssetSettings settings, string groupName)
        {
            var group = settings.FindGroup(groupName);
            if (group != null)
            {
                group.RemoveAllEntry(); 
            }
        }

        public static void RemoveAllEntry(this AddressableAssetGroup group)
        {
            var entries = new Stack<AddressableAssetEntry>(group.entries);
            while (entries.Count > 0)
            {
                var entry = entries.Pop();
                group.RemoveAssetEntry(entry);
            }
            AssetDatabase.SaveAssets();
        }
    }
}
#endif