using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets.ResourceLocators; using UnityEngine.ResourceManagement.AsyncOperations; namespace BrewMonster.Scripts { public class AddressableManager : MonoSingleton { private bool _isInitialized = false; private Dictionary> _loadedAssets = new(); private Dictionary> _loadedTextAssets = new(); public event Action OnDispose; protected override void Initialize() { base.Initialize(); _isInitialized = false; Addressables.InitializeAsync().Completed += OnInitializeComplete; } public bool IsInitialized() { return _isInitialized; } void OnInitializeComplete(AsyncOperationHandle handle) { if (handle.Status == AsyncOperationStatus.Succeeded) { _isInitialized = true; BMLogger.Log($"AddressableManager: Initialized"); } else { // print out the error BMLogger.LogError($"AddressableManager: Failed to initialize: {handle.OperationException?.Message} {handle.OperationException?.StackTrace}"); } } /// /// Load a text asset asynchronously. /// NOTE: The key must match the Addressables "Address" (or another valid key like a label/GUID). /// /// /// /// public async Task LoadTextAssetAsync(string assetPath) { if (_loadedTextAssets.ContainsKey(assetPath)) { return _loadedTextAssets[assetPath].Result; } try { var handle = Addressables.LoadAssetAsync(assetPath); await handle.Task; _loadedTextAssets[assetPath] = handle; return handle.Result; } catch (Exception e) { BMLogger.LogError($"AddressableManager: Failed to load TextAsset '{assetPath}': {e}"); return null; } } private static IEnumerable GetCandidateKeys(string assetPath) { if (string.IsNullOrWhiteSpace(assetPath)) { yield break; } // Exact key (what caller asked for) yield return assetPath; // Common fallback used by this repo's Addressables settings: full asset path address. // Example in `Assets/AddressableAssetsData/AssetGroups/configuration.asset`: // m_Address: Assets/Addressable/elements.txt if (!assetPath.Contains("/") && !assetPath.Contains("\\")) { yield return $"Assets/Addressable/{assetPath}"; } } private static void LogSimilarKeys(string needle) { // Helpful diagnostics in dev builds: show a few keys containing the substring. try { var lower = needle?.ToLowerInvariant(); if (string.IsNullOrEmpty(lower)) { return; } const int max = 20; var matches = new List(max); foreach (var locator in Addressables.ResourceLocators) { foreach (var keyObj in locator.Keys) { if (keyObj is not string keyStr) { continue; } if (!keyStr.ToLowerInvariant().Contains(lower)) { continue; } matches.Add(keyStr); if (matches.Count >= max) { goto Done; } } } Done: if (matches.Count > 0) { BMLogger.LogWarning($"AddressableManager: Similar Addressables keys for '{needle}': {string.Join(", ", matches)}"); } } catch { // ignore diagnostics failures } } /// /// Load an asset asynchronously. The address should look like this: "models/npcs/npc/魅灵首领/魅灵首领/魅灵首领.prefab" /// /// public async Task LoadPrefabAsync(string assetPath) { if (_loadedAssets.ContainsKey(assetPath)) { return _loadedAssets[assetPath].Result; } try { var handle = Addressables.LoadAssetAsync(assetPath); await handle.Task; _loadedAssets[assetPath] = handle; return handle.Result; } catch (System.Exception e) { BMLogger.LogError(e.StackTrace); return null; } } /// /// When the asset is no longer needed, call this method to unload it. /// /// The asset path used when loading the asset public void ReleaseAsset(string assetPath) { if (_loadedAssets.TryGetValue(assetPath, out var handle)) { if (handle.IsValid()) { Addressables.Release(handle); } _loadedAssets.Remove(assetPath); BMLogger.Log($"AddressableManager: Released asset: {assetPath}"); } else { BMLogger.LogWarning($"AddressableManager: Asset not found in cache: {assetPath}"); } } /// /// Release a specific asset by its handle directly. /// /// The async operation handle to release public void ReleaseAsset(AsyncOperationHandle handle) { if (handle.IsValid()) { Addressables.Release(handle); } } /// /// Release all loaded assets from the cache. /// public void ReleaseAllAssets() { foreach (var kvp in _loadedAssets) { if (kvp.Value.IsValid()) { Addressables.Release(kvp.Value); } } _loadedAssets.Clear(); BMLogger.Log("AddressableManager: Released all assets"); } /// /// Check if an asset is currently loaded in the cache. /// /// The asset path to check /// True if the asset is loaded public bool IsAssetLoaded(string assetPath) { return _loadedAssets.ContainsKey(assetPath) && _loadedAssets[assetPath].IsValid(); } /// /// Get the count of currently loaded assets. /// public int LoadedAssetCount => _loadedAssets.Count; private void OnDestroy() { OnDispose?.Invoke(); ReleaseAllAssets(); } } }