第3章 基础加载与释放 发表于 2026-01-03 更新于 2026-01-03
第3章 基础加载与释放 同步vs异步加载 同步加载 同步加载会阻塞当前线程直到资源加载完成,通常不推荐在主线程使用,因为它会导致游戏卡顿。
同步加载示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using UnityEngine;using UnityEngine.AddressableAssets;public class SynchronousLoader : MonoBehaviour { public string assetAddress = "Assets/Prefabs/MyPrefab.prefab" ; void Start () { GameObject loadedAsset = Addressables.LoadAssetSync<GameObject>(assetAddress); if (loadedAsset != null ) { Instantiate(loadedAsset); Debug.Log("同步加载完成" ); } else { Debug.LogError("同步加载失败: " + assetAddress); } } }
同步加载的适用场景
编辑器工具开发
非主线程的后台加载
小型资源的快速加载
调试和测试
异步加载 异步加载不会阻塞当前线程,是推荐的加载方式,特别是在游戏运行时。
异步加载示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class AsynchronousLoader : MonoBehaviour { public string assetAddress = "Assets/Prefabs/MyPrefab.prefab" ; void Start () { AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(assetAddress); handle.Completed += (operation) => { if (operation.Status == AsyncOperationStatus.Succeeded) { GameObject loadedAsset = Instantiate(operation.Result); Debug.Log("异步加载完成: " + assetAddress); } else { Debug.LogError("异步加载失败: " + operation.OperationException); } Addressables.Release(handle); }; } }
使用async/await的异步加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 using System;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class AsyncAwaitLoader : MonoBehaviour { public string assetAddress = "Assets/Prefabs/MyPrefab.prefab" ; async void Start () { try { AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(assetAddress); GameObject loadedAsset = await handle.Task; if (loadedAsset != null ) { Instantiate(loadedAsset); Debug.Log("异步加载完成 (async/await): " + assetAddress); } } catch (Exception e) { Debug.LogError("异步加载异常: " + e.Message); } } }
加载性能对比 性能测试脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 using System.Collections;using UnityEngine;using UnityEngine.AddressableAssets;public class LoadPerformanceTest : MonoBehaviour { public string [] assetAddresses; public bool useAsync = true ; void Start () { StartCoroutine(PerformanceTest()); } IEnumerator PerformanceTest () { float startTime = Time.realtimeSinceStartup; if (useAsync) { yield return StartCoroutine (AsyncLoadTest( )) ; } else { SyncLoadTest(); } float endTime = Time.realtimeSinceStartup; Debug.Log($"加载完成,耗时: {(endTime - startTime):F3} 秒" ); } IEnumerator AsyncLoadTest () { foreach (string address in assetAddresses) { var handle = Addressables.LoadAssetAsync<GameObject>(address); yield return handle; if (handle.Status == AsyncOperationStatus.Succeeded) { Destroy(handle.Result); } Addressables.Release(handle); } } void SyncLoadTest () { foreach (string address in assetAddresses) { GameObject asset = Addressables.LoadAssetSync<GameObject>(address); if (asset != null ) { Destroy(asset); } } } }
LoadAssetAsync API详解 基本API用法 泛型加载方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class LoadAssetAsyncExamples : MonoBehaviour { public void LoadGameObject (string address ) { AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(address); handle.Completed += (op) => { if (op.Status == AsyncOperationStatus.Succeeded) { Instantiate(op.Result); } Addressables.Release(handle); }; } public void LoadTexture (string address ) { AsyncOperationHandle<Texture> handle = Addressables.LoadAssetAsync<Texture>(address); handle.Completed += (op) => { if (op.Status == AsyncOperationStatus.Succeeded) { Renderer renderer = GetComponent<Renderer>(); if (renderer != null ) { renderer.material.mainTexture = op.Result; } } Addressables.Release(handle); }; } public void LoadScriptableObject <T >(string address ) where T : ScriptableObject { AsyncOperationHandle<T> handle = Addressables.LoadAssetAsync<T>(address); handle.Completed += (op) => { if (op.Status == AsyncOperationStatus.Succeeded) { T data = op.Result; Debug.Log("ScriptableObject加载成功: " + data.name); } Addressables.Release(handle); }; } public void LoadSprite (string address ) { AsyncOperationHandle<Sprite> handle = Addressables.LoadAssetAsync<Sprite>(address); handle.Completed += (op) => { if (op.Status == AsyncOperationStatus.Succeeded) { UnityEngine.UI.Image image = GetComponent<UnityEngine.UI.Image>(); if (image != null ) { image.sprite = op.Result; } } Addressables.Release(handle); }; } }
高级API特性 进度追踪 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;using UnityEngine.UI;public class LoadProgressTracker : MonoBehaviour { public string assetAddress = "Assets/Prefabs/HeavyAsset.prefab" ; public Slider progressSlider; public Text progressText; void Start () { LoadAssetWithProgress(); } void LoadAssetWithProgress () { AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(assetAddress); handle.PercentCompleteChanged += (op) => { float progress = op.PercentComplete; if (progressSlider != null ) { progressSlider.value = progress; } if (progressText != null ) { progressText.text = $"加载进度: {(progress * 100 ):F1} %" ; } }; handle.Completed += (op) => { if (op.Status == AsyncOperationStatus.Succeeded) { Instantiate(op.Result); if (progressText != null ) { progressText.text = "加载完成!" ; } } else { if (progressText != null ) { progressText.text = "加载失败: " + op.OperationException?.Message; } } handle.PercentCompleteChanged = null ; Addressables.Release(handle); }; } }
取消加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class LoadCancelExample : MonoBehaviour { private AsyncOperationHandle<GameObject> currentHandle; private bool isCancelled = false ; public string assetAddress = "Assets/Prefabs/MyPrefab.prefab" ; void Update () { if (Input.GetKeyDown(KeyCode.Space)) { CancelCurrentLoad(); } } public void StartLoad () { isCancelled = false ; currentHandle = Addressables.LoadAssetAsync<GameObject>(assetAddress); currentHandle.Completed += (op) => { if (isCancelled) { Debug.Log("加载被取消" ); return ; } if (op.Status == AsyncOperationStatus.Succeeded) { Instantiate(op.Result); Debug.Log("加载完成" ); } else { Debug.LogError("加载失败: " + op.OperationException); } Addressables.Release(currentHandle); }; } void CancelCurrentLoad () { if (currentHandle.IsValid()) { isCancelled = true ; Addressables.Release(currentHandle); Debug.Log("加载已取消" ); } } }
加载GameObject、Prefab、SceneAsset 加载GameObject和Prefab 基本加载和实例化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class GameObjectLoader : MonoBehaviour { public string prefabAddress = "Assets/Prefabs/Player.prefab" ; public Transform spawnPoint; void Start () { LoadAndInstantiatePrefab(); } void LoadAndInstantiatePrefab () { AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(prefabAddress); handle.Completed += (operation) => { if (operation.Status == AsyncOperationStatus.Succeeded) { GameObject instance = Instantiate(operation.Result, spawnPoint != null ? spawnPoint.position : Vector3.zero, spawnPoint != null ? spawnPoint.rotation : Quaternion.identity); if (spawnPoint != null ) { instance.transform.SetParent(spawnPoint); } Debug.Log("Prefab加载并实例化成功: " + operation.Result.name); } else { Debug.LogError("Prefab加载失败: " + operation.OperationException); } Addressables.Release(handle); }; } public void UnloadAndDestroy (GameObject instance ) { if (instance != null ) { Destroy(instance); Addressables.ReleaseInstance(instance); } } }
带参数的Prefab加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class ParameterizedPrefabLoader : MonoBehaviour { [System.Serializable ] public class PrefabLoadRequest { public string address; public Vector3 position; public Quaternion rotation = Quaternion.identity; public Transform parent; } public PrefabLoadRequest[] loadRequests; void Start () { foreach (var request in loadRequests) { LoadPrefabWithParameters(request); } } void LoadPrefabWithParameters (PrefabLoadRequest request ) { AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(request.address); handle.Completed += (operation) => { if (operation.Status == AsyncOperationStatus.Succeeded) { GameObject instance = Instantiate(operation.Result, request.position, request.rotation); if (request.parent != null ) { instance.transform.SetParent(request.parent); } InitializeLoadedObject(instance, request); } else { Debug.LogError($"加载失败 ({request.address} ): {operation.OperationException} " ); } Addressables.Release(handle); }; } void InitializeLoadedObject (GameObject instance, PrefabLoadRequest request ) { var initializer = instance.GetComponent<PrefabInitializer>(); if (initializer != null ) { initializer.Initialize(request); } } } public class PrefabInitializer : MonoBehaviour { public void Initialize (GameObjectLoader.PrefabLoadRequest request ) { Debug.Log($"初始化对象: {gameObject.name} at {request.position} " ); } }
加载SceneAsset 场景加载示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;using UnityEngine.SceneManagement;public class SceneAssetLoader : MonoBehaviour { public string sceneAddress = "Assets/Scenes/Level1.unity" ; void Start () { LoadSceneAsset(); } void LoadSceneAsset () { AsyncOperationHandle<SceneAsset> handle = Addressables.LoadAssetAsync<SceneAsset>(sceneAddress); handle.Completed += (operation) => { if (operation.Status == AsyncOperationStatus.Succeeded) { Debug.Log("SceneAsset加载成功: " + operation.Result.name); LoadActualScene(sceneAddress); } else { Debug.LogError("SceneAsset加载失败: " + operation.OperationException); } Addressables.Release(handle); }; } void LoadActualScene (string sceneAddress ) { AsyncOperationHandle<SceneInstance> sceneHandle = Addressables.LoadSceneAsync(sceneAddress, LoadSceneMode.Single); sceneHandle.Completed += (sceneOp) => { if (sceneOp.Status == AsyncOperationStatus.Succeeded) { Debug.Log("场景加载成功: " + sceneOp.Result.Scene.name); } else { Debug.LogError("场景加载失败: " + sceneOp.OperationException); } }; } }
资源引用计数机制 引用计数原理 Addressables系统使用引用计数机制来管理资源的生命周期。每个加载的资源都有一个引用计数器,当计数器归零时,资源才会被真正卸载。
引用计数示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class ReferenceCountingExample : MonoBehaviour { public string assetAddress = "Assets/Prefabs/MyPrefab.prefab" ; private GameObject[] instances = new GameObject[3 ]; private AsyncOperationHandle<GameObject> handle; void Start () { LoadResource(); } void LoadResource () { handle = Addressables.LoadAssetAsync<GameObject>(assetAddress); handle.Completed += (operation) => { if (operation.Status == AsyncOperationStatus.Succeeded) { for (int i = 0 ; i < instances.Length; i++) { instances[i] = Instantiate(operation.Result); instances[i].transform.position = new Vector3(i * 2f , 0 , 0 ); } Debug.Log("资源加载完成,引用计数: " + GetReferenceCount()); } }; } public void ReleaseResource () { for (int i = 0 ; i < instances.Length; i++) { if (instances[i] != null ) { Destroy(instances[i]); instances[i] = null ; } } if (handle.IsValid()) { Addressables.Release(handle); Debug.Log("释放资源,引用计数变为0" ); } } int GetReferenceCount () { return handle.IsValid() ? 1 : 0 ; } }
引用计数管理最佳实践 完整的资源管理示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 using System.Collections.Generic;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class ResourceManager : MonoBehaviour { private Dictionary<string , AsyncOperationHandle> activeHandles = new Dictionary<string , AsyncOperationHandle>(); private Dictionary<string , List<GameObject>> activeInstances = new Dictionary<string , List<GameObject>>(); public void LoadAndInstantiate (string address, Vector3 position, Quaternion rotation = default ) { if (rotation == default ) rotation = Quaternion.identity; AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(address); activeHandles[address] = handle; handle.Completed += (operation) => { if (operation.Status == AsyncOperationStatus.Succeeded) { GameObject instance = Instantiate(operation.Result, position, rotation); if (!activeInstances.ContainsKey(address)) { activeInstances[address] = new List<GameObject>(); } activeInstances[address].Add(instance); Debug.Log($"成功加载并实例化: {address} " ); } else { Debug.LogError($"加载失败: {address} , 错误: {operation.OperationException} " ); activeHandles.Remove(address); } }; } public void ReleaseResource (string address ) { if (activeInstances.TryGetValue(address, out List<GameObject> instances)) { foreach (GameObject instance in instances) { if (instance != null ) { Destroy(instance); } } activeInstances.Remove(address); } if (activeHandles.TryGetValue(address, out AsyncOperationHandle handle)) { if (handle.IsValid()) { Addressables.Release(handle); } activeHandles.Remove(address); } Debug.Log($"已释放资源: {address} " ); } public void ReleaseAllResources () { foreach (var pair in activeInstances) { foreach (GameObject instance in pair.Value) { if (instance != null ) { Destroy(instance); } } } activeInstances.Clear(); foreach (var pair in activeHandles) { if (pair.Value.IsValid()) { Addressables.Release(pair.Value); } } activeHandles.Clear(); Debug.Log("已释放所有资源" ); } void OnDestroy () { ReleaseAllResources(); } }
正确释放资源(Release) 释放API详解 不同类型的释放方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class ResourceReleaseExamples : MonoBehaviour { public string assetAddress = "Assets/Prefabs/MyPrefab.prefab" ; void Start () { DemonstrateReleaseMethods(); } async void DemonstrateReleaseMethods () { var handle1 = Addressables.LoadAssetAsync<GameObject>(assetAddress); GameObject asset1 = await handle1.Task; Addressables.Release(handle1); GameObject instance = Instantiate(asset1); Addressables.ReleaseInstance(instance); var sceneHandle = Addressables.LoadSceneAsync("Assets/Scenes/Test.unity" ); var sceneInstance = await sceneHandle.Task; Addressables.Release(sceneHandle); string [] addresses = { "Asset1" , "Asset2" , "Asset3" }; var multiHandle = Addressables.LoadAssetsAsync<GameObject>(addresses, null ); var assets = await multiHandle.Task; Addressables.Release(multiHandle); } void ManualReleaseExample () { AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(assetAddress); handle.Completed += (operation) => { if (operation.Status == AsyncOperationStatus.Succeeded) { GameObject instance = Instantiate(operation.Result); StartCoroutine(DelayedRelease(handle, instance)); } else { Addressables.Release(handle); } }; } System.Collections.IEnumerator DelayedRelease (AsyncOperationHandle<GameObject> handle, GameObject instance ) { yield return new WaitForSeconds (5f ) ; Destroy(instance); Addressables.ReleaseInstance(instance); Addressables.Release(handle); } }
释放时机管理 自动释放管理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class AutoReleaseManager : MonoBehaviour { private class ReleaseInfo { public AsyncOperationHandle handle; public DateTime releaseTime; public Action<GameObject> onRelease; } private List<ReleaseInfo> scheduledReleases = new List<ReleaseInfo>(); private Dictionary<GameObject, AsyncOperationHandle> trackedInstances = new Dictionary<GameObject, AsyncOperationHandle>(); void Update () { CheckScheduledReleases(); } public void ScheduleRelease (AsyncOperationHandle handle, float delaySeconds, Action<GameObject> onRelease = null ) { var releaseInfo = new ReleaseInfo { handle = handle, releaseTime = DateTime.Now.AddSeconds(delaySeconds), onRelease = onRelease }; scheduledReleases.Add(releaseInfo); } public void TrackInstance (GameObject instance, AsyncOperationHandle handle ) { trackedInstances[instance] = handle; var destroyer = instance.AddComponent<InstanceDestroyer>(); destroyer.onDestroy = () => OnInstanceDestroyed(instance); } void OnInstanceDestroyed (GameObject instance ) { if (trackedInstances.TryGetValue(instance, out AsyncOperationHandle handle)) { Addressables.ReleaseInstance(instance); Addressables.Release(handle); trackedInstances.Remove(instance); } } void CheckScheduledReleases () { DateTime now = DateTime.Now; for (int i = scheduledReleases.Count - 1 ; i >= 0 ; i--) { if (now >= scheduledReleases[i].releaseTime) { var info = scheduledReleases[i]; if (info.onRelease != null && info.handle.Result != null ) { info.onRelease(info.handle.Result); } Addressables.Release(info.handle); scheduledReleases.RemoveAt(i); } } } void OnDestroy () { foreach (var pair in trackedInstances) { Addressables.ReleaseInstance(pair.Key); if (pair.Value.IsValid()) { Addressables.Release(pair.Value); } } trackedInstances.Clear(); foreach (var info in scheduledReleases) { if (info.handle.IsValid()) { Addressables.Release(info.handle); } } scheduledReleases.Clear(); } } public class InstanceDestroyer : MonoBehaviour { public System.Action onDestroy; void OnDestroy () { if (onDestroy != null ) { onDestroy(); } } }
常见内存泄漏问题与解决 内存泄漏常见场景 1. 未释放句柄 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class MemoryLeakExample : MonoBehaviour { public string assetAddress = "Assets/Prefabs/MyPrefab.prefab" ; void LoadWithoutRelease () { var handle = Addressables.LoadAssetAsync<GameObject>(assetAddress); handle.Completed += (operation) => { if (operation.Status == AsyncOperationStatus.Succeeded) { Instantiate(operation.Result); } }; } void LoadWithProperRelease () { var handle = Addressables.LoadAssetAsync<GameObject>(assetAddress); handle.Completed += (operation) => { if (operation.Status == AsyncOperationStatus.Succeeded) { Instantiate(operation.Result); } Addressables.Release(handle); }; } }
2. 循环引用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class CircularReferenceExample : MonoBehaviour { private AsyncOperationHandle<GameObject> handle; private GameObject loadedAsset; void Start () { LoadAsset(); } void LoadAsset () { handle = Addressables.LoadAssetAsync<GameObject>(assetAddress); handle.Completed += OnAssetLoaded; } void OnAssetLoaded (AsyncOperationHandle<GameObject> operation ) { if (operation.Status == AsyncOperationStatus.Succeeded) { loadedAsset = Instantiate(operation.Result); } Addressables.Release(handle); } void OnDestroy () { if (loadedAsset != null ) { Destroy(loadedAsset); } if (handle.IsValid()) { Addressables.Release(handle); } } }
内存泄漏检测工具 资源监控脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 using System.Collections.Generic;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class ResourceMonitor : MonoBehaviour { private Dictionary<string , int > activeResourceCounts = new Dictionary<string , int >(); private List<AsyncOperationHandle> allHandles = new List<AsyncOperationHandle>(); public void RegisterHandle (AsyncOperationHandle handle, string address ) { allHandles.Add(handle); if (activeResourceCounts.ContainsKey(address)) { activeResourceCounts[address]++; } else { activeResourceCounts[address] = 1 ; } Debug.Log($"注册资源: {address} , 当前数量: {activeResourceCounts[address]} " ); } public void UnregisterHandle (AsyncOperationHandle handle, string address ) { allHandles.Remove(handle); if (activeResourceCounts.ContainsKey(address)) { activeResourceCounts[address]--; if (activeResourceCounts[address] <= 0 ) { activeResourceCounts.Remove(address); } } Debug.Log($"注销资源: {address} , 当前数量: {activeResourceCounts.ContainsKey(address) ? activeResourceCounts[address] : 0 } " ); } [ContextMenu("打印资源统计" ) ] public void PrintResourceStats () { Debug.Log("=== 资源统计 ===" ); foreach (var pair in activeResourceCounts) { Debug.Log($"{pair.Key} : {pair.Value} " ); } int validHandles = 0 ; foreach (var handle in allHandles) { if (handle.IsValid()) { validHandles++; } } Debug.Log($"有效句柄数量: {validHandles} /{allHandles.Count} " ); } void OnDestroy () { foreach (var handle in allHandles) { if (handle.IsValid()) { Addressables.Release(handle); } } allHandles.Clear(); activeResourceCounts.Clear(); } }
实战案例:制作简单的资源加载管理器 完整的资源管理器实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 using System;using System.Collections.Generic;using System.Threading.Tasks;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class SimpleAssetManager : MonoBehaviour { #region Singleton Pattern private static SimpleAssetManager _instance; public static SimpleAssetManager Instance { get { if (_instance == null ) { GameObject managerObj = new GameObject("SimpleAssetManager" ); _instance = managerObj.AddComponent<SimpleAssetManager>(); } return _instance; } } #endregion [Header("缓存设置" ) ] public int maxCacheSize = 100 ; private Dictionary<string , CachedAsset> assetCache = new Dictionary<string , CachedAsset>(); private Queue<string > cacheOrder = new Queue<string >(); private class CachedAsset { public GameObject asset; public AsyncOperationHandle<GameObject> handle; public DateTime lastAccessed; public int referenceCount = 0 ; } void Awake () { if (_instance == null ) { _instance = this ; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } public async Task<GameObject> LoadAssetAsync (string address, bool cache = true ) { if (string .IsNullOrEmpty(address)) { Debug.LogError("地址不能为空" ); return null ; } if (assetCache.ContainsKey(address)) { var cached = assetCache[address]; cached.lastAccessed = DateTime.Now; cached.referenceCount++; return cached.asset; } try { AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>(address); GameObject loadedAsset = await handle.Task; if (loadedAsset != null ) { if (cache) { CacheAsset(address, loadedAsset, handle); } else { return loadedAsset; } return loadedAsset; } else { Debug.LogError($"加载资源失败: {address} " ); return null ; } } catch (Exception e) { Debug.LogError($"加载资源异常: {address} , 错误: {e.Message} " ); return null ; } } public GameObject LoadAssetSync (string address, bool cache = true ) { if (string .IsNullOrEmpty(address)) { Debug.LogError("地址不能为空" ); return null ; } if (assetCache.ContainsKey(address)) { var cached = assetCache[address]; cached.lastAccessed = DateTime.Now; cached.referenceCount++; return cached.asset; } try { GameObject loadedAsset = Addressables.LoadAssetSync<GameObject>(address); if (loadedAsset != null && cache) { var handle = Addressables.LoadAssetAsync<GameObject>(address); CacheAsset(address, loadedAsset, handle); } return loadedAsset; } catch (Exception e) { Debug.LogError($"同步加载资源异常: {address} , 错误: {e.Message} " ); return null ; } } public async Task<GameObject> InstantiateAssetAsync (string address, Vector3 position = default , Quaternion rotation = default , Transform parent = null ) { if (rotation == default ) rotation = Quaternion.identity; GameObject prefab = await LoadAssetAsync(address); if (prefab != null ) { GameObject instance = Instantiate(prefab, position, rotation, parent); return instance; } return null ; } public void ReleaseAsset (string address ) { if (assetCache.TryGetValue(address, out CachedAsset cached)) { cached.referenceCount--; if (cached.referenceCount <= 0 ) { RemoveFromCache(address); } } } public void ReleaseInstance (GameObject instance ) { if (instance != null ) { Destroy(instance); } } public void ClearCache () { foreach (var pair in assetCache) { if (pair.Value.handle.IsValid()) { Addressables.Release(pair.Value.handle); } } assetCache.Clear(); cacheOrder.Clear(); Debug.Log("缓存已清理" ); } private void CacheAsset (string address, GameObject asset, AsyncOperationHandle<GameObject> handle ) { if (assetCache.Count >= maxCacheSize) { RemoveOldestFromCache(); } var cached = new CachedAsset { asset = asset, handle = handle, lastAccessed = DateTime.Now, referenceCount = 1 }; assetCache[address] = cached; cacheOrder.Enqueue(address); } private void RemoveFromCache (string address ) { if (assetCache.TryGetValue(address, out CachedAsset cached)) { if (cached.handle.IsValid()) { Addressables.Release(cached.handle); } assetCache.Remove(address); } } private void RemoveOldestFromCache () { while (cacheOrder.Count > 0 ) { string oldestAddress = cacheOrder.Dequeue(); if (assetCache.ContainsKey(oldestAddress)) { var cached = assetCache[oldestAddress]; if (cached.referenceCount <= 0 ) { RemoveFromCache(oldestAddress); break ; } else { cacheOrder.Enqueue(oldestAddress); } } } } public string GetCacheStats () { int totalAssets = assetCache.Count; int cachedAssets = 0 ; int totalReferences = 0 ; foreach (var pair in assetCache) { cachedAssets++; totalReferences += pair.Value.referenceCount; } return $"缓存统计: 总计{totalAssets} 个资源, {cachedAssets} 个缓存, {totalReferences} 个引用" ; } void OnDestroy () { ClearCache(); _instance = null ; } }
资源管理器使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 using System.Collections;using UnityEngine;public class AssetManagerExample : MonoBehaviour { public string [] assetAddresses = { "Assets/Prefabs/Character.prefab" , "Assets/Prefabs/Weapon.prefab" , "Assets/Prefabs/Effect.prefab" }; void Start () { StartCoroutine(ExampleUsage()); } IEnumerator ExampleUsage () { foreach (string address in assetAddresses) { GameObject asset = SimpleAssetManager.Instance.LoadAssetSync(address); if (asset != null ) { Debug.Log($"同步加载: {address} " ); } yield return new WaitForSeconds (0.1f ) ; } GameObject character = SimpleAssetManager.Instance.InstantiateAssetAsync( "Assets/Prefabs/Character.prefab" , Vector3.zero ).Result; if (character != null ) { Debug.Log("角色实例化成功" ); yield return new WaitForSeconds (5f ) ; SimpleAssetManager.Instance.ReleaseInstance(character); } Debug.Log(SimpleAssetManager.Instance.GetCacheStats()); } }
通过本章的学习,您已经掌握了Addressables资源加载和释放的核心概念,包括同步异步加载、API使用、引用计数机制、正确释放方法以及常见内存泄漏问题的解决方案。下一章我们将深入探讨Groups系统的详细配置和策略。