第3章 基础加载与释放

第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
{
// 使用async/await等待加载完成
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
{
// 加载GameObject
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);
};
}

// 加载Texture
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);
};
}

// 加载ScriptableObject
public void LoadScriptableObject<T>(string address) where T : ScriptableObject
{
AsyncOperationHandle<T> handle = Addressables.LoadAssetAsync<T>(address);
handle.Completed += (op) =>
{
if (op.Status == AsyncOperationStatus.Succeeded)
{
// 使用加载的ScriptableObject
T data = op.Result;
Debug.Log("ScriptableObject加载成功: " + data.name);
}
Addressables.Release(handle);
};
}

// 加载Sprite
public void LoadSprite(string address)
{
AsyncOperationHandle<Sprite> handle = Addressables.LoadAssetAsync<Sprite>(address);
handle.Completed += (op) =>
{
if (op.Status == AsyncOperationStatus.Succeeded)
{
// 设置为Image组件的sprite
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)
{
// 实例化Prefab
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引用
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()
{
// 加载SceneAsset本身(不是加载场景)
AsyncOperationHandle<SceneAsset> handle = Addressables.LoadAssetAsync<SceneAsset>(sceneAddress);

handle.Completed += (operation) =>
{
if (operation.Status == AsyncOperationStatus.Succeeded)
{
Debug.Log("SceneAsset加载成功: " + operation.Result.name);

// 现在可以使用Addressables来加载实际场景
LoadActualScene(sceneAddress);
}
else
{
Debug.LogError("SceneAsset加载失败: " + operation.OperationException);
}

Addressables.Release(handle);
};
}

void LoadActualScene(string sceneAddress)
{
// 使用Addressables加载实际场景
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.Release(sceneHandle);
};
}
}

资源引用计数机制

引用计数原理

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()
{
// 第一次加载,引用计数为1
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;
}
}

// 释放Addressables引用 - 只需要释放一次
if (handle.IsValid())
{
Addressables.Release(handle);
Debug.Log("释放资源,引用计数变为0");
}
}

// 获取当前引用计数(需要通过Addressables API)
int GetReferenceCount()
{
// 这里只是演示概念,实际获取引用计数需要使用内部API
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>>();

/// <summary>
/// 加载资源并创建实例
/// </summary>
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);
}
};
}

/// <summary>
/// 释放特定资源的所有实例
/// </summary>
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);
}

// 释放Addressables句柄
if (activeHandles.TryGetValue(address, out AsyncOperationHandle handle))
{
if (handle.IsValid())
{
Addressables.Release(handle);
}
activeHandles.Remove(address);
}

Debug.Log($"已释放资源: {address}");
}

/// <summary>
/// 释放所有资源
/// </summary>
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()
{
// 1. 释放操作句柄
var handle1 = Addressables.LoadAssetAsync<GameObject>(assetAddress);
GameObject asset1 = await handle1.Task;
Addressables.Release(handle1); // 释放句柄

// 2. 释放实例引用
GameObject instance = Instantiate(asset1);
Addressables.ReleaseInstance(instance); // 释放实例引用

// 3. 释放场景
var sceneHandle = Addressables.LoadSceneAsync("Assets/Scenes/Test.unity");
var sceneInstance = await sceneHandle.Task;
Addressables.Release(sceneHandle); // 释放场景句柄

// 4. 释放多个资源
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();
}

/// <summary>
/// 延迟释放资源
/// </summary>
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);
}

/// <summary>
/// 跟踪实例以便自动释放
/// </summary>
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);
// 错误:没有释放handle!
}
};
}

// 正确示例:确保释放句柄
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);

// 避免循环引用:使用弱引用或弱事件
// 错误:this引用了handle,handle的回调又引用了this
}

// 正确:在适当时候释放
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);
}
}

/// <summary>
/// 异步加载资源
/// </summary>
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;
}
}

/// <summary>
/// 同步加载资源
/// </summary>
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;
}
}

/// <summary>
/// 实例化资源
/// </summary>
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;
}

/// <summary>
/// 释放资源引用
/// </summary>
public void ReleaseAsset(string address)
{
if (assetCache.TryGetValue(address, out CachedAsset cached))
{
cached.referenceCount--;

if (cached.referenceCount <= 0)
{
// 当引用计数为0时,从缓存中移除
RemoveFromCache(address);
}
}
}

/// <summary>
/// 释放实例
/// </summary>
public void ReleaseInstance(GameObject instance)
{
if (instance != null)
{
Destroy(instance);
}
}

/// <summary>
/// 清理缓存
/// </summary>
public void ClearCache()
{
foreach (var pair in assetCache)
{
if (pair.Value.handle.IsValid())
{
Addressables.Release(pair.Value.handle);
}
}

assetCache.Clear();
cacheOrder.Clear();

Debug.Log("缓存已清理");
}

/// <summary>
/// 缓存资源
/// </summary>
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);
}

/// <summary>
/// 从缓存中移除
/// </summary>
private void RemoveFromCache(string address)
{
if (assetCache.TryGetValue(address, out CachedAsset cached))
{
if (cached.handle.IsValid())
{
Addressables.Release(cached.handle);
}
assetCache.Remove(address);
}
}

/// <summary>
/// 移除最旧的缓存项
/// </summary>
private void RemoveOldestFromCache()
{
while (cacheOrder.Count > 0)
{
string oldestAddress = cacheOrder.Dequeue();
if (assetCache.ContainsKey(oldestAddress))
{
var cached = assetCache[oldestAddress];
if (cached.referenceCount <= 0) // 只移除引用计数为0的
{
RemoveFromCache(oldestAddress);
break;
}
else
{
// 如果引用计数大于0,重新加入队列
cacheOrder.Enqueue(oldestAddress);
}
}
}
}

/// <summary>
/// 获取缓存统计信息
/// </summary>
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系统的详细配置和策略。