using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; namespace DDD { public class AssetManager : Singleton, IManager { private struct CachedAsset { public AsyncOperationHandle Handle; public int ReferenceCount; public CachedAsset(AsyncOperationHandle handle, int count = 1) { Handle = handle; ReferenceCount = count; } } private struct CachedScene { public AsyncOperationHandle Handle; public int ReferenceCount; public CachedScene(AsyncOperationHandle handle, int count = 1) { Handle = handle; ReferenceCount = count; } } [SerializeField] private bool _enableDebugLog = false; private readonly Dictionary _cachedAssets = new(); private readonly Dictionary _cachedScenes = new(); protected override void OnApplicationQuit() { base.OnApplicationQuit(); ReleaseAllCached(); } public void PreInit() { } public async Task Init() { await Addressables.InitializeAsync().Task; } public void PostInit() { } private string GetSafeAssetName(AssetReference assetReference) { var editorAssetName = assetReference.editorAsset.name; if (string.IsNullOrWhiteSpace(editorAssetName) == false) return editorAssetName; return assetReference.ToString(); } public async Task LoadAssetAsync(AssetReference assetReference) where T : Object { var guidKey = assetReference.AssetGUID; if (_cachedAssets.TryGetValue(guidKey, out var cachedAsset)) { if (cachedAsset.Handle.IsValid() && cachedAsset.Handle.Result is T result) { cachedAsset.ReferenceCount++; _cachedAssets[guidKey] = cachedAsset; if (_enableDebugLog) Debug.Log($"[AssetManager] 에셋 참조 카운트 증가: {GetSafeAssetName(assetReference)} -> {cachedAsset.ReferenceCount}\n실제 키 값: {guidKey}"); return result; } _cachedAssets.Remove(guidKey); Debug.LogWarning($"[AssetManager] 무효한 핸들 제거됨: {GetSafeAssetName(assetReference)}\n실제 키 값: {guidKey}"); } var newHandle = Addressables.LoadAssetAsync(guidKey); await newHandle.Task; if (newHandle.Status == AsyncOperationStatus.Succeeded) { _cachedAssets[guidKey] = new CachedAsset(newHandle, 1); if (_enableDebugLog) Debug.Log($"[AssetManager] 에셋 로드 및 캐싱 완료: {GetSafeAssetName(assetReference)} (참조수: {_cachedAssets[guidKey].ReferenceCount})\n실제 키 값: {guidKey}"); return newHandle.Result; } if (newHandle.IsValid()) { Addressables.Release(newHandle); } Debug.LogError($"[AssetManager] 에셋 로드 실패: {GetSafeAssetName(assetReference)}\n실제 키 값: {guidKey}"); return null; } /// /// ScriptSingleton을 위한 동기 로딩 /// public T LoadAsset(string key) where T : Object { if (_cachedAssets.TryGetValue(key, out var cachedAsset)) { if (cachedAsset.Handle.IsValid() && cachedAsset.Handle.Result is T result) { cachedAsset.ReferenceCount++; _cachedAssets[key] = cachedAsset; if (_enableDebugLog) Debug.Log($"[AssetManager] 동기 로딩 - 에셋 참조 카운트 증가: {key} -> {cachedAsset.ReferenceCount}"); return result; } _cachedAssets.Remove(key); Debug.LogWarning($"[AssetManager] 무효한 핸들 제거됨: {key}"); } var handle = Addressables.LoadAssetAsync(key); var loaded = handle.WaitForCompletion(); if (handle.Status == AsyncOperationStatus.Succeeded) { _cachedAssets[key] = new CachedAsset(handle, 1); if (_enableDebugLog) Debug.Log($"[AssetManager] 동기 로딩 - 에셋 로드 및 캐싱 완료: {key} (참조수: {key})"); return loaded; } if (handle.IsValid()) { Addressables.Release(handle); } Debug.LogError($"[AssetManager] 동기 로딩 - 에셋 로드 실패: {key}"); return null; } public async Task> LoadAssetsByLabel(string label) where T : Object { if (_cachedAssets.TryGetValue(label, out var cachedAsset)) { if (cachedAsset.Handle.IsValid() && cachedAsset.Handle.Result is IList cachedResult) { cachedAsset.ReferenceCount++; _cachedAssets[label] = cachedAsset; if (_enableDebugLog) Debug.Log($"[AssetManager] 라벨 에셋 참조 카운트 증가: {label} -> {cachedAsset.ReferenceCount}"); return cachedResult.ToList(); } _cachedAssets.Remove(label); if (_enableDebugLog) Debug.LogWarning($"[AssetManager] 무효한 라벨 핸들 제거됨: {label}"); } var handle = Addressables.LoadAssetsAsync(label); await handle.Task; if (handle.Status == AsyncOperationStatus.Succeeded) { _cachedAssets[label] = new CachedAsset(handle, 1); if (_enableDebugLog) Debug.Log($"[AssetManager] 라벨 에셋 로드 및 캐싱 완료: {label} (참조수: {_cachedAssets[label].ReferenceCount})"); return handle.Result.ToList(); } if (handle.IsValid()) { Addressables.Release(handle); } Debug.LogError($"[AssetManager] 라벨 에셋 로드 실패: {label}"); return new List(); } public void ReleaseAsset(AssetReference assetReference) { var guidKey = assetReference.AssetGUID; if (_cachedAssets.TryGetValue(guidKey, out var cachedAsset)) { cachedAsset.ReferenceCount--; if (cachedAsset.ReferenceCount <= 0) { if (cachedAsset.Handle.IsValid()) { Addressables.Release(cachedAsset.Handle); if (_enableDebugLog) Debug.Log($"[AssetManager] 에셋 실제 해제됨: {GetSafeAssetName(assetReference)}\n실제 키 값: {guidKey}"); } _cachedAssets.Remove(guidKey); } else { _cachedAssets[guidKey] = cachedAsset; if (_enableDebugLog) Debug.Log($"[AssetManager] 에셋 참조 카운트 감소: {GetSafeAssetName(assetReference)} -> {cachedAsset.ReferenceCount}\n실제 키 값: {guidKey}"); } } else { if (_enableDebugLog) Debug.LogWarning($"[AssetManager] 캐시에서 에셋을 찾을 수 없음: {GetSafeAssetName(assetReference)}\n실제 키 값: {guidKey}"); } } public void ReleaseAsset(string key) { if (_cachedAssets.TryGetValue(key, out var cachedAsset)) { cachedAsset.ReferenceCount--; if (cachedAsset.ReferenceCount <= 0) { if (cachedAsset.Handle.IsValid()) { Addressables.Release(cachedAsset.Handle); if (_enableDebugLog) Debug.Log($"[AssetManager] 에셋 실제 해제됨: {key}"); } _cachedAssets.Remove(key); } else { _cachedAssets[key] = cachedAsset; if (_enableDebugLog) Debug.Log($"[AssetManager] 에셋 참조 카운트 감소: {key} -> {cachedAsset.ReferenceCount}"); } } else { if (_enableDebugLog) Debug.LogWarning($"[AssetManager] 캐시에서 에셋을 찾을 수 없음: {key}"); } } public async Task LoadScene(AssetReference assetReference, LoadSceneMode mode = LoadSceneMode.Additive, bool activateOnLoad = true) { var guidKey = assetReference.AssetGUID; if (_cachedScenes.TryGetValue(guidKey, out var cachedScene)) { if (cachedScene.Handle.IsValid()) { cachedScene.ReferenceCount++; _cachedScenes[guidKey] = cachedScene; if (_enableDebugLog) Debug.Log($"[AssetManager] 씬 참조 카운트 증가: {GetSafeAssetName(assetReference)} -> {cachedScene.ReferenceCount}\n실제 키 값: {guidKey}"); return cachedScene.Handle.Result; } _cachedScenes.Remove(guidKey); if (_enableDebugLog) Debug.LogWarning($"[AssetManager] 무효한 씬 핸들 제거됨: {GetSafeAssetName(assetReference)}\n실제 키 값: {guidKey}"); } var handle = Addressables.LoadSceneAsync(assetReference, mode, activateOnLoad); await handle.Task; if (handle.Status == AsyncOperationStatus.Succeeded) { _cachedScenes[guidKey] = new CachedScene(handle, 1); var sceneName = handle.Result.Scene.name; if (_enableDebugLog) Debug.Log($"[AssetManager] 씬 로드 및 캐싱 완료: {sceneName} (참조수: {_cachedScenes[guidKey].ReferenceCount})\n실제 키 값: {guidKey}"); return handle.Result; } if (handle.IsValid()) { Addressables.UnloadSceneAsync(handle); } Debug.LogError($"[AssetManager] 씬 로드 실패: {GetSafeAssetName(assetReference)}\n실제 키 값: {guidKey}"); return default; } public async Task UnloadScene(AssetReference assetReference) { var guidKey = assetReference.AssetGUID; if (_cachedScenes.TryGetValue(guidKey, out var cachedScene)) { cachedScene.ReferenceCount--; if (cachedScene.ReferenceCount <= 0) { if (cachedScene.Handle.IsValid()) { var sceneName = cachedScene.Handle.Result.Scene.name; await Addressables.UnloadSceneAsync(cachedScene.Handle).Task; if (_enableDebugLog) Debug.Log($"[AssetManager] 씬 실제 언로드됨: {sceneName}\n실제 키 값: {guidKey}"); } _cachedScenes.Remove(guidKey); } else { _cachedScenes[guidKey] = cachedScene; if (_enableDebugLog) Debug.Log($"[AssetManager] 씬 참조 카운트 감소: {GetSafeAssetName(assetReference)} -> {cachedScene.ReferenceCount}\n실제 키 값: {guidKey}"); } } else { if (_enableDebugLog) Debug.LogWarning($"[AssetManager] 캐시에서 씬을 찾을 수 없음: {GetSafeAssetName(assetReference)}\n실제 키 값: {guidKey}"); } } public void ReleaseAllCached() { foreach (var kvp in _cachedAssets) { var cachedAsset = kvp.Value; if (cachedAsset.Handle.IsValid()) { Addressables.Release(cachedAsset.Handle); } } _cachedAssets.Clear(); foreach (var kvp in _cachedScenes) { var cachedScene = kvp.Value; if (cachedScene.Handle.IsValid()) { Addressables.UnloadSceneAsync(cachedScene.Handle); } } _cachedScenes.Clear(); if (_enableDebugLog) Debug.Log("[AssetManager] 모든 캐시된 Addressable 리소스를 강제 해제했습니다."); } } }