📗
【Unity】Addressablesを管理するマネージャー
はじめに
初めての方も、そうでない方もこんにちは!
現役ゲームプログラマーのたむぼーです。
自己紹介を載せているので、気になる方は見ていただければ嬉しいです!
今回は
Addressablesを管理するマネージャー
について、僕が実践している方法紹介します
Addressablesについて
Addressablesとは、動的にアセットをロードおよび管理するためのシステム
AddressableManagerについて
AddressableManager
AddressableManager.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
using Cysharp.Threading.Tasks;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets;
using UnityEditor;
namespace Utils
{
public class AddressableManager
{
static private AddressableManager _instance;
static public AddressableManager Instance
{
get
{
if (_instance == null)
{
_instance = new AddressableManager();
_instance.Initialize();
}
return _instance;
}
}
private bool _isInitialize;
Dictionary<string, AsyncOperationHandle> _cacheHandle;
Dictionary<string, AsyncOperationHandle> _staticCacheHandle;
/// <summary>
/// 初期化
/// </summary>
private void Initialize()
{
if (_isInitialize)
{
return;
}
_cacheHandle = new Dictionary<string, AsyncOperationHandle>();
_staticCacheHandle = new Dictionary<string, AsyncOperationHandle>();
_isInitialize = true;
}
/// <summary>
/// アドレスの作成
/// </summary>
public string CreateAddress(params string[] paths)
{
return string.Concat(paths);
}
/// <summary>
/// アセットの登録
/// </summary>
public void RegisterAsset(string assetPath, string groupName)
{
AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.GetSettings(false);
AddressableAssetGroup group = settings.FindGroup(groupName);
if (group == null)
{
group = settings.CreateGroup(groupName, false, false, false, settings.DefaultGroup.Schemas);
}
AddressableAssetEntry entry = settings.CreateOrMoveEntry(AssetDatabase.AssetPathToGUID(assetPath), group, false, false);
entry.address = assetPath;
settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry, true);
AssetDatabase.SaveAssets();
}
/// <summary>
/// 破棄
/// </summary>
public void DisposeAll(bool isStatic = false)
{
if (isStatic)
{
foreach (AsyncOperationHandle handle in _staticCacheHandle.Values)
{
Addressables.Release(handle);
}
_staticCacheHandle.Clear();
}
foreach (AsyncOperationHandle handle in _cacheHandle.Values)
{
Addressables.Release(handle);
}
_cacheHandle.Clear();
}
/// <summary>
/// 破棄
/// </summary>
public void Dispose(string address, bool isStatic = false)
{
if (isStatic && _staticCacheHandle.TryGetValue(address, out AsyncOperationHandle staticHandle))
{
Addressables.Release(staticHandle);
_staticCacheHandle.Remove(address);
}
else if (_cacheHandle.TryGetValue(address, out AsyncOperationHandle dynamicHandle))
{
Addressables.Release(dynamicHandle);
_cacheHandle.Remove(address);
}
else
{
Debug.LogWarning($"Address {address} not found in cache.");
}
}
/// <summary>
/// Addressableでアセットを読み込む
/// </summary>
public async UniTask<T> LoadAssetAsync<T>(string address, bool isStatic = false) where T : UnityEngine.Object
{
string normalizeAddress = CommonUtility.NormalizePath(address);
try
{
if (TryGetCacheHandle(normalizeAddress, out AsyncOperationHandle handle))
{
return await GetAsset<T>(handle);
}
handle = Addressables.LoadAssetAsync<T>(normalizeAddress);
AddCacheHandle(normalizeAddress, handle, isStatic);
return await GetAsset<T>(handle);
}
catch (Exception ex)
{
Debug.LogError($"Failed to load addressable asset at {normalizeAddress}: {ex.Message}\n{ex.StackTrace}");
return null;
}
}
/// <summary>
/// Addressableで複数アセットを読み込む
/// </summary>
public async UniTask<List<T>> LoadAssetsAsync<T>(List<string> addresses, bool isStatic = false) where T : UnityEngine.Object
{
List<string> normalizeAddresses = CommonUtility.NormalizePath(addresses);
try
{
List<T> assets = new List<T>();
foreach (string normalizeAddress in normalizeAddresses)
{
T asset = await LoadAssetAsync<T>(normalizeAddress, isStatic);
assets.Add(asset);
}
return assets;
}
catch (Exception ex)
{
Debug.LogError($"Failed to load addressable asset : {ex.Message}\n{ex.StackTrace}");
return null;
}
}
/// <summary>
/// Addressableでパッキングアセットを読み込む
/// </summary>
public async UniTask<UnityEngine.Sprite[]> LoadPackingAssetAsync(string address, bool isStatic = false)
{
string normalizeAddress = CommonUtility.NormalizePath(address);
try
{
if (TryGetCacheHandle(normalizeAddress, out AsyncOperationHandle handle))
{
return await GetAsset<UnityEngine.Sprite[]>(handle);
}
handle = Addressables.LoadAssetAsync<UnityEngine.Sprite[]>(normalizeAddress);
AddCacheHandle(normalizeAddress, handle, isStatic);
return await GetAsset<UnityEngine.Sprite[]>(handle);
}
catch (Exception ex)
{
Debug.LogError($"Failed to load addressable asset at {normalizeAddress}: {ex.Message}\n{ex.StackTrace}");
return null;
}
}
/// <summary>
/// Addressableでシーンを読み込む
/// </summary>
public async UniTask<SceneInstance> LoadSceneAsync(string address, LoadSceneMode loadMode, bool isStatic = false)
{
string normalizeAddress = CommonUtility.NormalizePath(address);
try
{
if (TryGetCacheHandle(normalizeAddress, out AsyncOperationHandle handle))
{
return await GetAsset<SceneInstance>(handle);
}
handle = Addressables.LoadSceneAsync(normalizeAddress, loadMode, true);
AddCacheHandle(normalizeAddress, handle, isStatic);
return await GetAsset<SceneInstance>(handle);
}
catch (Exception ex)
{
Debug.LogError($"Failed to load scene at {normalizeAddress}: {ex.Message}\n{ex.StackTrace}");
return default;
}
}
/// <summary>
/// Addressableで事前にシーンを読み込む
/// </summary>
public async UniTask PreloadSceneAsync(string address, LoadSceneMode loadMode, bool isStatic = false)
{
string normalizeAddress = CommonUtility.NormalizePath(address);
try
{
if (TryGetCacheHandle(normalizeAddress, out AsyncOperationHandle handle))
{
await UniTask.WaitUntil(() => handle.PercentComplete >= 0.9f);
return;
}
handle = Addressables.LoadSceneAsync(normalizeAddress, loadMode, false);
AddCacheHandle(normalizeAddress, handle, isStatic);
await UniTask.WaitUntil(() => handle.PercentComplete >= 0.9f);
return;
}
catch (Exception ex)
{
Debug.LogError($"Failed to load scene at {normalizeAddress}: {ex.Message}\n{ex.StackTrace}");
}
}
/// <summary>
/// Addressableで事前に読み込んだシーンをアクティベート
/// </summary>
public async UniTask ActivatePreloadedSceneAsync(string address)
{
string normalizeAddress = CommonUtility.NormalizePath(address);
try
{
if (! TryGetCacheHandle(normalizeAddress, out AsyncOperationHandle handle))
{
throw new Exception($"addressable load error.");
}
await UniTask.WaitUntil(() => handle.Status == AsyncOperationStatus.Succeeded && handle.PercentComplete >= 0.9f);
if (handle.Status == AsyncOperationStatus.Succeeded)
{
SceneInstance sceneInstance = await GetAsset<SceneInstance>(handle);
await sceneInstance.ActivateAsync();
}
}
catch (Exception ex)
{
Debug.LogError($"Failed to load scene at {normalizeAddress}: {ex.Message}\n{ex.StackTrace}");
}
}
/// <summary>
/// ハンドルキャッシュに追加
/// </summary>
private void AddCacheHandle(string address, AsyncOperationHandle handle, bool isStatic = false)
{
if (isStatic)
{
_staticCacheHandle.Add(address, handle);
}
else
{
_cacheHandle.Add(address, handle);
}
}
/// <summary>
/// キャッシュしたハンドルを取得
/// </summary>
private bool TryGetCacheHandle(string address, out AsyncOperationHandle handle)
{
if (_staticCacheHandle.TryGetValue(address, out handle))
{
return true;
}
if (_cacheHandle.TryGetValue(address, out handle))
{
return true;
}
handle = default;
return false;
}
/// <summary>
/// アセットを取得
/// </summary>
private async UniTask<T> GetAsset<T>(AsyncOperationHandle handle)
{
await handle.Task;
// ハンドルが無効な場合
if (!handle.IsValid())
{
throw new Exception($"Addressable load error: Invalid handle.");
}
// ロードが失敗した場合
if (handle.Status == AsyncOperationStatus.Failed)
{
string errorMessage = handle.OperationException != null ? handle.OperationException.Message : "Unknown error";
throw new Exception($"Failed to load addressable asset. Error: {errorMessage}");
}
return (T)handle.Result;
}
}
}
CommonUtility
CommonUtility.cs
using System.Collections.Generic;
namespace Utils
{
static public class CommonUtility
{
/// <summary>
/// パスのスラッシュを正しい形式に変換
/// </summary>
static public List<string> NormalizePath(List<string> paths)
{
List<string> normalizedPaths = new List<string>();
foreach (string path in paths)
{
normalizedPaths.Add(NormalizePath(path));
}
return normalizedPaths;
}
/// <summary>
/// パスのスラッシュを正しい形式に変換
/// </summary>
static public string NormalizePath(string path)
{
if (string.IsNullOrEmpty(path))
{
return path;
}
string normalizedPath = path.Replace("\\", "/");
while (normalizedPath.Contains("//"))
{
normalizedPath = normalizedPath.Replace("//", "/");
}
return normalizedPath;
}
}
}
使い方
・アセットアドレスの作成
// アドレスを生成
string address = AddressableManager.Instance.CreateAddress("path/to/", "assetName", ".identifier");
// ※"path/to/assetName.identifier"になっていれば何でも良い
// CreateAddress("path/". "to/assetName, ".identifier");
// CreateAddress("path/". "to/", "assetName, ".identifier");
// CreateAddress("path/to/assetName, ".identifier");
// CreateAddress("path/to/, "assetName.identifier");
・アセットの読み込み
// addressからアセットを読み込む
GameObject displayObj = await AddressableManager.Instance.LoadAssetAsync<GameObject>(address);
// staticなハンドルとしてaddressからアセットを読み込む(isStaticをtrueにする)
GameObject displayObj = await AddressableManager.Instance.LoadAssetAsync<GameObject>(address, isStatic);
・指定のハンドルを破棄(ハンドルの解放)
// addressに紐づくハンドルを破棄
AddressableManager.Instance.Dispose(address);
// addressに紐づくstaticなハンドルを破棄(isStaticをtrueにする)
AddressableManager.Instance.Dispose(address, isStatic);
・すべてのハンドルを破棄(ハンドルの解放)
// すべてのハンドルを破棄
AddressableManager.Instance.DisposeAll();
// staticなハンドルを含めたすべてのハンドルを破棄(isStaticをtrueにする)
AddressableManager.Instance.DisposeAll(isStatic);
おまけ
・Editor等でスクリプトからアセットを登録
// Addressableに登録
AddressableManager.Instance.RegisterAsset(assetPath, AssetGroupName);
Discussion