第6章 场景管理

第6章 场景管理

Addressable场景加载

场景加载基础

Unity Addressables系统提供了强大的场景加载功能,允许开发者以地址的方式加载场景,而不需要直接引用场景文件。这种方式提供了更大的灵活性,特别是在处理远程场景和动态场景加载时。

Addressable场景加载示例

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
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;

public class AddressableSceneLoader : MonoBehaviour
{
[System.Serializable]
public class SceneLoadRequest
{
public string sceneAddress;
public LoadSceneMode loadMode = LoadSceneMode.Single;
public bool activateOnLoad = true;
}

[Header("场景加载配置")]
public SceneLoadRequest[] sceneRequests;

[Header("进度回调")]
public System.Action<float> onProgressChanged;
public System.Action<SceneInstance> onLoadComplete;
public System.Action<string> onLoadError;

private AsyncOperationHandle<SceneInstance> currentLoadHandle;

/// <summary>
/// 异步加载场景
/// </summary>
public async void LoadSceneAsync(string sceneAddress, LoadSceneMode mode = LoadSceneMode.Single, bool activateOnLoad = true)
{
try
{
// 创建场景加载操作
currentLoadHandle = Addressables.LoadSceneAsync(sceneAddress, mode, activateOnLoad);

// 监听加载进度
currentLoadHandle.PercentCompleteChanged += (handle) =>
{
float progress = handle.PercentComplete;
onProgressChanged?.Invoke(progress);

if (progress >= 1.0f)
{
Debug.Log($"场景 {sceneAddress} 加载完成");
}
};

// 等待加载完成
SceneInstance sceneInstance = await currentLoadHandle.Task;

if (sceneInstance.Scene.IsValid())
{
Debug.Log($"场景加载成功: {sceneInstance.Scene.name}");
onLoadComplete?.Invoke(sceneInstance);
}
else
{
string errorMsg = $"场景加载失败: {sceneAddress}";
Debug.LogError(errorMsg);
onLoadError?.Invoke(errorMsg);
}
}
catch (System.Exception e)
{
string errorMsg = $"加载场景时发生异常: {e.Message}";
Debug.LogError(errorMsg);
onLoadError?.Invoke(errorMsg);
}
}

/// <summary>
/// 同步加载场景
/// </summary>
public SceneInstance LoadSceneSync(string sceneAddress, LoadSceneMode mode = LoadSceneMode.Single, bool activateOnLoad = true)
{
try
{
SceneInstance sceneInstance = Addressables.LoadSceneSync(sceneAddress, mode, activateOnLoad);

if (sceneInstance.Scene.IsValid())
{
Debug.Log($"同步场景加载成功: {sceneInstance.Scene.name}");
return sceneInstance;
}
else
{
Debug.LogError($"同步场景加载失败: {sceneAddress}");
return new SceneInstance();
}
}
catch (System.Exception e)
{
Debug.LogError($"同步加载场景异常: {e.Message}");
return new SceneInstance();
}
}

/// <summary>
/// 卸载场景
/// </summary>
public async void UnloadSceneAsync(SceneInstance sceneInstance)
{
try
{
AsyncOperationHandle<SceneInstance> unloadHandle = Addressables.UnloadSceneAsync(sceneInstance);
await unloadHandle.Task;

Debug.Log($"场景卸载完成: {sceneInstance.Scene.name}");
Addressables.Release(unloadHandle);
}
catch (System.Exception e)
{
Debug.LogError($"卸载场景异常: {e.Message}");
}
}

/// <summary>
/// 批量加载场景
/// </summary>
public async System.Threading.Tasks.Task LoadMultipleScenesAsync(SceneLoadRequest[] requests)
{
var handles = new List<AsyncOperationHandle<SceneInstance>>();

// 创建所有加载操作
foreach (var request in requests)
{
var handle = Addressables.LoadSceneAsync(request.sceneAddress, request.loadMode, request.activateOnLoad);
handles.Add(handle);
}

// 等待所有操作完成
foreach (var handle in handles)
{
await handle.Task;

if (handle.Status == AsyncOperationStatus.Succeeded)
{
Debug.Log($"批量加载场景成功: {handle.Result.Scene.name}");
}
else
{
Debug.LogError($"批量加载场景失败: {handle.OperationException}");
}

Addressables.Release(handle);
}
}
}

场景地址标记

要使场景能够通过Addressables加载,需要将其标记为Addressable:

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
using UnityEngine;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;

public class SceneAddressableMarker : EditorWindow
{
[MenuItem("Tools/Addressables/Mark Scenes as Addressable")]
static void Init()
{
SceneAddressableMarker window = (SceneAddressableMarker)EditorWindow.GetWindow(typeof(SceneAddressableMarker));
window.titleContent = new GUIContent("Scene Addressable Marker");
window.Show();
}

bool markAllScenes = true;
string[] specificScenePaths = new string[0];

void OnGUI()
{
GUILayout.Label("场景Addressable标记工具", EditorStyles.boldLabel);

markAllScenes = EditorGUILayout.Toggle("标记所有场景", markAllScenes);

if (!markAllScenes)
{
GUILayout.Label("指定场景路径:");
for (int i = 0; i < specificScenePaths.Length; i++)
{
specificScenePaths[i] = EditorGUILayout.TextField($"场景 {i + 1}:", specificScenePaths[i]);
}

if (GUILayout.Button("添加场景路径"))
{
System.Array.Resize(ref specificScenePaths, specificScenePaths.Length + 1);
}
}

if (GUILayout.Button("标记场景"))
{
MarkScenesAsAddressable();
}
}

void MarkScenesAsAddressable()
{
var settings = AddressableAssetSettingsDefaultObject.Settings;
if (settings == null)
{
Debug.LogError("Addressable Asset Settings not found");
return;
}

string[] sceneGUIDs;

if (markAllScenes)
{
// 获取所有场景
sceneGUIDs = AssetDatabase.FindAssets("t:Scene", new[] { "Assets" });
}
else
{
// 获取指定路径的场景
sceneGUIDs = new string[specificScenePaths.Length];
for (int i = 0; i < specificScenePaths.Length; i++)
{
sceneGUIDs[i] = AssetDatabase.AssetPathToGUID(specificScenePaths[i]);
}
}

int markedCount = 0;

foreach (string guid in sceneGUIDs)
{
string scenePath = AssetDatabase.GUIDToAssetPath(guid);
if (!string.IsNullOrEmpty(scenePath) && scenePath.EndsWith(".unity"))
{
var entry = settings.CreateOrMoveEntry(guid, settings.DefaultGroup, false, true);

// 设置场景地址(使用场景名)
string sceneName = System.IO.Path.GetFileNameWithoutExtension(scenePath);
entry.address = sceneName;

// 添加场景标签
entry.SetLabel("Scene", true, true);

markedCount++;
Debug.Log($"标记场景: {scenePath} -> 地址: {sceneName}");
}
}

settings.SetDirty(AddressableAssetSettings.ModificationEvent.BatchModification,
settings.groups, true, true);

Debug.Log($"成功标记 {markedCount} 个场景为Addressable");
}
}

场景的加载模式(Single/Additive)

单场景模式(Single)

单场景模式会卸载当前所有场景,然后加载新场景。这是最常见的场景切换方式。

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
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;

public class SingleSceneLoader : MonoBehaviour
{
public System.Action<float> onProgress;
public System.Action<string> onComplete;
public System.Action<string> onError;

private AsyncOperationHandle<SceneInstance> loadHandle;

/// <summary>
/// 加载单场景(替换当前场景)
/// </summary>
public async void LoadSingleScene(string sceneAddress)
{
try
{
Debug.Log($"开始加载单场景: {sceneAddress}");

// 使用LoadSceneMode.Single模式
loadHandle = Addressables.LoadSceneAsync(sceneAddress, LoadSceneMode.Single, true);

// 监听进度
loadHandle.PercentCompleteChanged += (handle) =>
{
onProgress?.Invoke(handle.PercentComplete);
};

// 等待加载完成
SceneInstance sceneInstance = await loadHandle.Task;

if (sceneInstance.Scene.IsValid())
{
Debug.Log($"单场景加载完成: {sceneInstance.Scene.name}");
onComplete?.Invoke($"场景 {sceneInstance.Scene.name} 加载成功");
}
else
{
string errorMsg = $"单场景加载失败: {sceneAddress}";
Debug.LogError(errorMsg);
onError?.Invoke(errorMsg);
}
}
catch (System.Exception e)
{
string errorMsg = $"加载单场景异常: {e.Message}";
Debug.LogError(errorMsg);
onError?.Invoke(errorMsg);
}
finally
{
Addressables.Release(loadHandle);
}
}

/// <summary>
/// 验证单场景加载
/// </summary>
public void ValidateSingleSceneLoad(string sceneAddress)
{
// 检查当前场景数量
int currentSceneCount = SceneManager.sceneCount;
Debug.Log($"加载前场景数量: {currentSceneCount}");

// 加载新场景
LoadSingleScene(sceneAddress);

// 在场景加载完成后验证
StartCoroutine(ValidateAfterLoad());
}

System.Collections.IEnumerator ValidateAfterLoad()
{
yield return new WaitForSeconds(1f); // 等待场景完全加载

int sceneCountAfter = SceneManager.sceneCount;
Debug.Log($"加载后场景数量: {sceneCountAfter}");

if (sceneCountAfter == 1)
{
Debug.Log("单场景加载验证通过:当前只有一个场景");
}
else
{
Debug.LogWarning($"单场景加载验证失败:当前有 {sceneCountAfter} 个场景");
}
}
}

叠加场景模式(Additive)

叠加场景模式会在当前场景基础上加载新场景,不会卸载现有场景。这在需要多个场景同时存在时非常有用。

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
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;
using System.Collections.Generic;

public class AdditiveSceneLoader : MonoBehaviour
{
[System.Serializable]
public class LoadedSceneInfo
{
public string address;
public SceneInstance sceneInstance;
public float loadTime;
}

private List<LoadedSceneInfo> loadedScenes = new List<LoadedSceneInfo>();
private Dictionary<string, AsyncOperationHandle<SceneInstance>> activeLoadHandles =
new Dictionary<string, AsyncOperationHandle<SceneInstance>>();

public System.Action<string, float> onSceneLoaded;
public System.Action<string> onSceneUnloaded;
public System.Action<string> onLoadError;

/// <summary>
/// 加载叠加场景
/// </summary>
public async void LoadAdditiveScene(string sceneAddress)
{
if (activeLoadHandles.ContainsKey(sceneAddress))
{
Debug.LogWarning($"场景 {sceneAddress} 正在加载中");
return;
}

try
{
Debug.Log($"开始加载叠加场景: {sceneAddress}");

// 使用LoadSceneMode.Additive模式
var handle = Addressables.LoadSceneAsync(sceneAddress, LoadSceneMode.Additive, true);
activeLoadHandles[sceneAddress] = handle;

System.DateTime loadStartTime = System.DateTime.Now;

// 监听加载进度
handle.PercentCompleteChanged += (op) =>
{
float progress = op.PercentComplete;
Debug.Log($"场景 {sceneAddress} 加载进度: {(progress * 100):F1}%");
};

// 等待加载完成
SceneInstance sceneInstance = await handle.Task;

if (sceneInstance.Scene.IsValid())
{
float loadTime = (float)(System.DateTime.Now - loadStartTime).TotalSeconds;

var sceneInfo = new LoadedSceneInfo
{
address = sceneAddress,
sceneInstance = sceneInstance,
loadTime = loadTime
};

loadedScenes.Add(sceneInfo);

Debug.Log($"叠加场景加载完成: {sceneInstance.Scene.name}, 耗时: {loadTime:F2}秒");
onSceneLoaded?.Invoke(sceneInstance.Scene.name, loadTime);
}
else
{
string errorMsg = $"叠加场景加载失败: {sceneAddress}";
Debug.LogError(errorMsg);
onLoadError?.Invoke(errorMsg);
}
}
catch (System.Exception e)
{
string errorMsg = $"加载叠加场景异常: {e.Message}";
Debug.LogError(errorMsg);
onLoadError?.Invoke(errorMsg);
}
finally
{
if (activeLoadHandles.ContainsKey(sceneAddress))
{
Addressables.Release(activeLoadHandles[sceneAddress]);
activeLoadHandles.Remove(sceneAddress);
}
}
}

/// <summary>
/// 卸载叠加场景
/// </summary>
public async void UnloadAdditiveScene(string sceneAddress)
{
var sceneInfo = loadedScenes.Find(s => s.address == sceneAddress);
if (sceneInfo == null)
{
Debug.LogWarning($"未找到已加载的场景: {sceneAddress}");
return;
}

try
{
Debug.Log($"开始卸载叠加场景: {sceneInfo.sceneInstance.Scene.name}");

var unloadHandle = Addressables.UnloadSceneAsync(sceneInfo.sceneInstance);
await unloadHandle.Task;

loadedScenes.Remove(sceneInfo);

Debug.Log($"叠加场景卸载完成: {sceneInfo.sceneInstance.Scene.name}");
onSceneUnloaded?.Invoke(sceneInfo.sceneInstance.Scene.name);

Addressables.Release(unloadHandle);
}
catch (System.Exception e)
{
string errorMsg = $"卸载叠加场景异常: {e.Message}";
Debug.LogError(errorMsg);
onLoadError?.Invoke(errorMsg);
}
}

/// <summary>
/// 批量加载叠加场景
/// </summary>
public async System.Threading.Tasks.Task LoadMultipleAdditiveScenes(string[] sceneAddresses)
{
var loadTasks = new List<System.Threading.Tasks.Task>();

foreach (string address in sceneAddresses)
{
var task = System.Threading.Tasks.Task.Run(async () =>
{
await LoadAdditiveSceneTask(address);
});
loadTasks.Add(task);
}

await System.Threading.Tasks.Task.WhenAll(loadTasks);

Debug.Log($"批量加载叠加场景完成,共加载 {sceneAddresses.Length} 个场景");
}

private async System.Threading.Tasks.Task LoadAdditiveSceneTask(string sceneAddress)
{
// 简单的异步加载任务
await System.Threading.Tasks.Task.Delay(100); // 模拟加载时间
await System.Threading.Tasks.Task.Run(() => LoadAdditiveScene(sceneAddress));
}

/// <summary>
/// 获取已加载场景信息
/// </summary>
public LoadedSceneInfo[] GetLoadedScenes()
{
return loadedScenes.ToArray();
}

/// <summary>
/// 卸载所有叠加场景
/// </summary>
public async System.Threading.Tasks.Task UnloadAllAdditiveScenes()
{
var unloadTasks = new List<System.Threading.Tasks.Task>();

// 保存当前列表的副本以避免在迭代时修改
var scenesToUnload = new List<LoadedSceneInfo>(loadedScenes);

foreach (var sceneInfo in scenesToUnload)
{
var task = System.Threading.Tasks.Task.Run(async () =>
{
await UnloadAdditiveScene(sceneInfo.address);
});
unloadTasks.Add(task);
}

await System.Threading.Tasks.Task.WhenAll(unloadTasks);

Debug.Log("所有叠加场景卸载完成");
}
}

混合场景管理

在实际项目中,通常需要同时使用单场景和叠加场景模式:

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
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;
using System.Collections.Generic;

public class MixedSceneManager : MonoBehaviour
{
[System.Serializable]
public class SceneGroup
{
public string groupName;
public string[] sceneAddresses;
public LoadSceneMode loadMode;
public bool isEssential; // 是否为必需场景
}

[Header("场景组配置")]
public SceneGroup[] sceneGroups;

[Header("场景管理")]
public bool autoLoadEssentialScenes = true;

private Dictionary<string, SceneInstance> loadedScenes = new Dictionary<string, SceneInstance>();
private Dictionary<string, SceneGroup> sceneGroupMap = new Dictionary<string, SceneGroup>();

void Start()
{
// 初始化场景组映射
foreach (var group in sceneGroups)
{
sceneGroupMap[group.groupName] = group;

if (autoLoadEssentialScenes && group.isEssential)
{
StartCoroutine(LoadSceneGroup(group.groupName));
}
}
}

/// <summary>
/// 加载场景组
/// </summary>
public System.Collections.IEnumerator LoadSceneGroup(string groupName)
{
if (!sceneGroupMap.ContainsKey(groupName))
{
Debug.LogError($"未找到场景组: {groupName}");
yield break;
}

var group = sceneGroupMap[groupName];

Debug.Log($"开始加载场景组: {groupName}, 模式: {group.loadMode}");

foreach (string sceneAddress in group.sceneAddresses)
{
if (loadedScenes.ContainsKey(sceneAddress))
{
Debug.Log($"场景已加载: {sceneAddress}");
continue;
}

var handle = Addressables.LoadSceneAsync(sceneAddress, group.loadMode, true);
yield return handle;

if (handle.Status == AsyncOperationStatus.Succeeded)
{
loadedScenes[sceneAddress] = handle.Result;
Debug.Log($"场景组 {groupName} 场景加载成功: {handle.Result.Scene.name}");
}
else
{
Debug.LogError($"场景组 {groupName} 场景加载失败: {sceneAddress}, 错误: {handle.OperationException}");
}

Addressables.Release(handle);
}

Debug.Log($"场景组 {groupName} 加载完成");
}

/// <summary>
/// 卸载场景组
/// </summary>
public System.Collections.IEnumerator UnloadSceneGroup(string groupName)
{
if (!sceneGroupMap.ContainsKey(groupName))
{
Debug.LogError($"未找到场景组: {groupName}");
yield break;
}

var group = sceneGroupMap[groupName];

Debug.Log($"开始卸载场景组: {groupName}");

foreach (string sceneAddress in group.sceneAddresses)
{
if (!loadedScenes.ContainsKey(sceneAddress))
{
Debug.Log($"场景未加载: {sceneAddress}");
continue;
}

var sceneInstance = loadedScenes[sceneAddress];
var handle = Addressables.UnloadSceneAsync(sceneInstance);
yield return handle;

if (handle.Status == AsyncOperationStatus.Succeeded)
{
loadedScenes.Remove(sceneAddress);
Debug.Log($"场景组 {groupName} 场景卸载成功: {sceneInstance.Scene.name}");
}
else
{
Debug.LogError($"场景组 {groupName} 场景卸载失败: {sceneInstance.Scene.name}, 错误: {handle.OperationException}");
}

Addressables.Release(handle);
}

Debug.Log($"场景组 {groupName} 卸载完成");
}

/// <summary>
/// 切换主场景(单场景模式)
/// </summary>
public async void SwitchMainScene(string sceneAddress)
{
Debug.Log($"切换主场景到: {sceneAddress}");

// 首先卸载所有非必需场景
await UnloadNonEssentialScenes();

// 然后加载新主场景
var handle = Addressables.LoadSceneAsync(sceneAddress, LoadSceneMode.Single, true);
var sceneInstance = await handle.Task;

if (sceneInstance.Scene.IsValid())
{
loadedScenes[sceneAddress] = sceneInstance;
Debug.Log($"主场景切换成功: {sceneInstance.Scene.name}");
}
else
{
Debug.LogError($"主场景切换失败: {sceneAddress}");
}

Addressables.Release(handle);
}

private async System.Threading.Tasks.Task UnloadNonEssentialScenes()
{
var scenesToUnload = new List<SceneInstance>();

foreach (var pair in loadedScenes)
{
// 检查该场景是否属于必需场景组
bool isEssential = false;
foreach (var group in sceneGroups)
{
if (group.isEssential && System.Array.IndexOf(group.sceneAddresses, pair.Key) >= 0)
{
isEssential = true;
break;
}
}

if (!isEssential)
{
scenesToUnload.Add(pair.Value);
}
}

foreach (var sceneInstance in scenesToUnload)
{
var handle = Addressables.UnloadSceneAsync(sceneInstance);
await handle.Task;
Addressables.Release(handle);

// 从字典中移除
var addressToRemove = "";
foreach (var pair in loadedScenes)
{
if (pair.Value.Scene.handle == sceneInstance.Scene.handle)
{
addressToRemove = pair.Key;
break;
}
}

if (!string.IsNullOrEmpty(addressToRemove))
{
loadedScenes.Remove(addressToRemove);
}
}

Debug.Log($"已卸载 {scenesToUnload.Count} 个非必需场景");
}

/// <summary>
/// 获取场景加载状态
/// </summary>
public bool IsSceneLoaded(string sceneAddress)
{
return loadedScenes.ContainsKey(sceneAddress);
}

/// <summary>
/// 获取当前加载的场景数量
/// </summary>
public int GetLoadedSceneCount()
{
return loadedScenes.Count;
}
}

场景依赖管理

场景依赖分析

在复杂的项目中,场景之间可能存在依赖关系,需要合理管理这些依赖以确保正确的加载顺序。

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
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;

public class SceneDependencyManager
{
[System.Serializable]
public class SceneDependency
{
public string sceneAddress;
public string[] dependencies; // 依赖的其他场景地址
public int priority; // 加载优先级
}

private List<SceneDependency> dependencies = new List<SceneDependency>();
private Dictionary<string, SceneDependency> dependencyMap = new Dictionary<string, SceneDependency>();

public SceneDependencyManager()
{
InitializeDefaultDependencies();
}

private void InitializeDefaultDependencies()
{
// 示例:定义场景依赖关系
AddDependency("Level1", new[] { "CommonAssets", "UI" }, 1);
AddDependency("Level2", new[] { "CommonAssets", "UI", "Level1" }, 1);
AddDependency("BossBattle", new[] { "CommonAssets", "UI", "Audio" }, 2);
}

public void AddDependency(string sceneAddress, string[] dependencies, int priority = 0)
{
var dependency = new SceneDependency
{
sceneAddress = sceneAddress,
dependencies = dependencies,
priority = priority
};

this.dependencies.Add(dependency);
dependencyMap[sceneAddress] = dependency;

Debug.Log($"添加场景依赖: {sceneAddress} 依赖于 [{string.Join(", ", dependencies)}]");
}

/// <summary>
/// 获取场景的完整依赖列表(递归)
/// </summary>
public List<string> GetFullDependencyList(string sceneAddress)
{
var fullDependencies = new List<string>();
var visited = new HashSet<string>();

GetFullDependencyListRecursive(sceneAddress, fullDependencies, visited);

// 移除重复项并保持顺序
var uniqueDependencies = new List<string>();
foreach (string dep in fullDependencies)
{
if (!uniqueDependencies.Contains(dep) && dep != sceneAddress)
{
uniqueDependencies.Add(dep);
}
}

return uniqueDependencies;
}

private void GetFullDependencyListRecursive(string sceneAddress, List<string> dependencies, HashSet<string> visited)
{
if (visited.Contains(sceneAddress))
{
return; // 避免循环依赖
}

visited.Add(sceneAddress);

if (dependencyMap.ContainsKey(sceneAddress))
{
var sceneDep = dependencyMap[sceneAddress];

// 先添加依赖项
foreach (string dep in sceneDep.dependencies)
{
GetFullDependencyListRecursive(dep, dependencies, visited);
if (!dependencies.Contains(dep))
{
dependencies.Add(dep);
}
}
}

// 最后添加当前场景
if (!dependencies.Contains(sceneAddress))
{
dependencies.Add(sceneAddress);
}
}

/// <summary>
/// 获取场景加载顺序
/// </summary>
public List<string> GetLoadOrder(string targetScene)
{
var dependencies = GetFullDependencyList(targetScene);
var loadOrder = new List<string>(dependencies);

// 按优先级排序
loadOrder.Sort((a, b) =>
{
int priorityA = dependencyMap.ContainsKey(a) ? dependencyMap[a].priority : 0;
int priorityB = dependencyMap.ContainsKey(b) ? dependencyMap[b].priority : 0;
return priorityA.CompareTo(priorityB);
});

return loadOrder;
}

/// <summary>
/// 验证依赖完整性
/// </summary>
public bool ValidateDependencies()
{
foreach (var dep in dependencies)
{
foreach (string dependency in dep.dependencies)
{
if (!dependencyMap.ContainsKey(dependency))
{
Debug.LogWarning($"场景 {dep.sceneAddress} 依赖的场景 {dependency} 未定义");
return false;
}
}
}

return true;
}

/// <summary>
/// 检测循环依赖
/// </summary>
public bool HasCircularDependency()
{
var visited = new HashSet<string>();
var recursionStack = new HashSet<string>();

foreach (var dep in dependencies)
{
if (HasCircularDependency(dep.sceneAddress, visited, recursionStack))
{
return true;
}
}

return false;
}

private bool HasCircularDependency(string sceneAddress, HashSet<string> visited, HashSet<string> recursionStack)
{
if (recursionStack.Contains(sceneAddress))
{
Debug.LogError($"检测到循环依赖: {sceneAddress}");
return true;
}

if (visited.Contains(sceneAddress))
{
return false;
}

visited.Add(sceneAddress);
recursionStack.Add(sceneAddress);

if (dependencyMap.ContainsKey(sceneAddress))
{
foreach (string dependency in dependencyMap[sceneAddress].dependencies)
{
if (HasCircularDependency(dependency, visited, recursionStack))
{
return true;
}
}
}

recursionStack.Remove(sceneAddress);
return false;
}
}

带依赖的场景加载器

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
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;

public class DependencySceneLoader : MonoBehaviour
{
private SceneDependencyManager dependencyManager = new SceneDependencyManager();
private Dictionary<string, SceneInstance> loadedScenes = new Dictionary<string, SceneInstance>();

[Header("加载配置")]
public bool validateDependencies = true;
public float loadDelayBetweenScenes = 0.1f; // 场景间加载延迟

[Header("回调事件")]
public System.Action<string, float> onSceneLoadProgress; // (场景地址, 进度0-1)
public System.Action<string> onSceneLoadComplete; // (场景地址)
public System.Action<string, string> onSceneLoadError; // (场景地址, 错误信息)

/// <summary>
/// 加载带依赖的场景
/// </summary>
public async System.Threading.Tasks.Task LoadSceneWithDependencies(string sceneAddress,
UnityEngine.SceneManagement.LoadSceneMode mode = UnityEngine.SceneManagement.LoadSceneMode.Additive)
{
if (validateDependencies && dependencyManager.HasCircularDependency())
{
onSceneLoadError?.Invoke(sceneAddress, "检测到循环依赖,无法加载场景");
return;
}

// 获取加载顺序
var loadOrder = dependencyManager.GetLoadOrder(sceneAddress);

Debug.Log($"场景 {sceneAddress} 的加载顺序: [{string.Join(", ", loadOrder)}]");

// 按顺序加载场景
foreach (string sceneToLoad in loadOrder)
{
if (loadedScenes.ContainsKey(sceneToLoad))
{
Debug.Log($"场景已加载,跳过: {sceneToLoad}");
continue;
}

try
{
Debug.Log($"开始加载场景: {sceneToLoad}");

var handle = Addressables.LoadSceneAsync(sceneToLoad, mode, true);

// 监听进度
handle.PercentCompleteChanged += (op) =>
{
float progress = op.PercentComplete;
onSceneLoadProgress?.Invoke(sceneToLoad, progress);
};

// 等待加载完成
SceneInstance sceneInstance = await handle.Task;

if (sceneInstance.Scene.IsValid())
{
loadedScenes[sceneToLoad] = sceneInstance;
Debug.Log($"场景加载成功: {sceneToLoad}");
onSceneLoadComplete?.Invoke(sceneToLoad);
}
else
{
string errorMsg = $"场景加载失败: {sceneToLoad}";
Debug.LogError(errorMsg);
onSceneLoadError?.Invoke(sceneToLoad, errorMsg);
}

Addressables.Release(handle);
}
catch (System.Exception e)
{
string errorMsg = $"加载场景异常: {e.Message}";
Debug.LogError(errorMsg);
onSceneLoadError?.Invoke(sceneToLoad, errorMsg);
}

// 场景间添加延迟,避免同时加载过多场景
if (loadDelayBetweenScenes > 0)
{
await System.Threading.Tasks.Task.Delay((int)(loadDelayBetweenScenes * 1000));
}
}

Debug.Log($"场景 {sceneAddress} 及其依赖加载完成");
}

/// <summary>
/// 卸载场景及其依赖(如果不再被其他场景需要)
/// </summary>
public async System.Threading.Tasks.Task UnloadSceneWithDependencies(string sceneAddress)
{
// 首先检查哪些依赖场景不再被其他场景需要
var dependencies = dependencyManager.GetFullDependencyList(sceneAddress);
var scenesToUnload = new List<string>();

// 添加目标场景
scenesToUnload.Add(sceneAddress);

// 检查依赖场景是否还需要
foreach (string dependency in dependencies)
{
if (IsDependencyStillNeeded(dependency, sceneAddress))
{
Debug.Log($"依赖场景仍在使用,不卸载: {dependency}");
}
else
{
scenesToUnload.Add(dependency);
}
}

// 按相反顺序卸载(先卸载依赖,再卸载主场景)
scenesToUnload.Reverse();

foreach (string sceneToUnload in scenesToUnload)
{
if (!loadedScenes.ContainsKey(sceneToUnload))
{
Debug.Log($"场景未加载,跳过卸载: {sceneToUnload}");
continue;
}

try
{
var sceneInstance = loadedScenes[sceneToUnload];
var handle = Addressables.UnloadSceneAsync(sceneInstance);

await handle.Task;

loadedScenes.Remove(sceneToUnload);
Debug.Log($"场景卸载成功: {sceneToUnload}");

Addressables.Release(handle);
}
catch (System.Exception e)
{
string errorMsg = $"卸载场景异常: {e.Message}";
Debug.LogError(errorMsg);
onSceneLoadError?.Invoke(sceneToUnload, errorMsg);
}
}

Debug.Log($"场景 {sceneAddress} 及相关依赖卸载完成");
}

private bool IsDependencyStillNeeded(string dependencyScene, string unloadingScene)
{
// 检查是否有其他已加载的场景依赖于这个依赖场景
foreach (var loadedScene in loadedScenes)
{
if (loadedScene.Key != unloadingScene) // 不检查正在卸载的场景
{
var deps = dependencyManager.GetFullDependencyList(loadedScene.Key);
if (deps.Contains(dependencyScene))
{
return true; // 仍有其他场景需要此依赖
}
}
}

return false; // 没有其他场景需要此依赖
}

/// <summary>
/// 获取场景加载状态
/// </summary>
public bool IsSceneLoaded(string sceneAddress)
{
return loadedScenes.ContainsKey(sceneAddress);
}

/// <summary>
/// 获取所有已加载场景
/// </summary>
public string[] GetLoadedScenes()
{
var loadedSceneList = new List<string>();
foreach (var pair in loadedScenes)
{
loadedSceneList.Add(pair.Key);
}
return loadedSceneList.ToArray();
}

void OnDestroy()
{
// 清理所有已加载的场景
foreach (var pair in loadedScenes)
{
Addressables.UnloadSceneAsync(pair.Value);
}
loadedScenes.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
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;

public class SceneLoadProgressTracker : MonoBehaviour
{
[System.Serializable]
public class SceneLoadProgress
{
public string sceneAddress;
public float progress;
public AsyncOperationStatus status;
public string errorMessage;
public System.DateTime startTime;
public System.DateTime endTime;
public float loadTime;
}

private Dictionary<string, SceneLoadProgress> progressTracker = new Dictionary<string, SceneLoadProgress>();
private List<string> loadingQueue = new List<string>();

[Header("进度回调")]
public System.Action<string, float> onSceneProgress; // (场景地址, 进度0-1)
public System.Action<string> onSceneComplete; // (场景地址)
public System.Action<string> onSceneError; // (场景地址)
public System.Action<float> onOverallProgress; // (总体进度0-1)

/// <summary>
/// 开始追踪场景加载
/// </summary>
public async void TrackSceneLoad(string sceneAddress, LoadSceneMode mode = LoadSceneMode.Additive)
{
if (progressTracker.ContainsKey(sceneAddress))
{
Debug.LogWarning($"场景加载已在追踪中: {sceneAddress}");
return;
}

var progress = new SceneLoadProgress
{
sceneAddress = sceneAddress,
progress = 0,
status = AsyncOperationStatus.None,
startTime = System.DateTime.Now
};

progressTracker[sceneAddress] = progress;
loadingQueue.Add(sceneAddress);

try
{
var handle = Addressables.LoadSceneAsync(sceneAddress, mode, true);

// 监听进度变化
handle.PercentCompleteChanged += (op) =>
{
UpdateSceneProgress(sceneAddress, op.PercentComplete, AsyncOperationStatus.None);
};

// 等待加载完成
SceneInstance sceneInstance = await handle.Task;

if (sceneInstance.Scene.IsValid())
{
UpdateSceneProgress(sceneAddress, 1.0f, AsyncOperationStatus.Succeeded);
progressTracker[sceneAddress].endTime = System.DateTime.Now;
progressTracker[sceneAddress].loadTime = (float)(progressTracker[sceneAddress].endTime - progressTracker[sceneAddress].startTime).TotalSeconds;

Debug.Log($"场景加载完成: {sceneAddress}, 耗时: {progressTracker[sceneAddress].loadTime:F2}秒");
onSceneComplete?.Invoke(sceneAddress);
}
else
{
UpdateSceneProgress(sceneAddress, 0, AsyncOperationStatus.Failed);
progressTracker[sceneAddress].errorMessage = "场景加载失败";

Debug.LogError($"场景加载失败: {sceneAddress}");
onSceneError?.Invoke(sceneAddress);
}

Addressables.Release(handle);
}
catch (System.Exception e)
{
UpdateSceneProgress(sceneAddress, 0, AsyncOperationStatus.Failed);
progressTracker[sceneAddress].errorMessage = e.Message;

Debug.LogError($"场景加载异常: {sceneAddress}, 错误: {e.Message}");
onSceneError?.Invoke(sceneAddress);
}
finally
{
loadingQueue.Remove(sceneAddress);
UpdateOverallProgress();
}
}

private void UpdateSceneProgress(string sceneAddress, float progress, AsyncOperationStatus status)
{
if (progressTracker.ContainsKey(sceneAddress))
{
progressTracker[sceneAddress].progress = progress;
progressTracker[sceneAddress].status = status;

onSceneProgress?.Invoke(sceneAddress, progress);
UpdateOverallProgress();
}
}

private void UpdateOverallProgress()
{
if (progressTracker.Count == 0)
{
onOverallProgress?.Invoke(0);
return;
}

float totalProgress = 0;
int completedScenes = 0;

foreach (var progress in progressTracker.Values)
{
totalProgress += progress.progress;
if (progress.status == AsyncOperationStatus.Succeeded)
{
completedScenes++;
}
}

float overallProgress = totalProgress / progressTracker.Count;
onOverallProgress?.Invoke(overallProgress);

Debug.Log($"总体加载进度: {(overallProgress * 100):F1}%, 完成场景数: {completedScenes}/{progressTracker.Count}");
}

/// <summary>
/// 获取场景加载进度
/// </summary>
public SceneLoadProgress GetSceneProgress(string sceneAddress)
{
if (progressTracker.ContainsKey(sceneAddress))
{
return progressTracker[sceneAddress];
}
return null;
}

/// <summary>
/// 获取总体加载进度
/// </summary>
public float GetOverallProgress()
{
if (progressTracker.Count == 0) return 0;

float totalProgress = 0;
foreach (var progress in progressTracker.Values)
{
totalProgress += progress.progress;
}

return totalProgress / progressTracker.Count;
}

/// <summary>
/// 获取加载统计信息
/// </summary>
public string GetLoadStatistics()
{
int total = progressTracker.Count;
int completed = 0;
int failed = 0;
int loading = 0;

foreach (var progress in progressTracker.Values)
{
switch (progress.status)
{
case AsyncOperationStatus.Succeeded:
completed++;
break;
case AsyncOperationStatus.Failed:
failed++;
break;
default:
loading++;
break;
}
}

return $"总计: {total}, 完成: {completed}, 失败: {failed}, 加载中: {loading}";
}

/// <summary>
/// 批量追踪场景加载
/// </summary>
public async System.Threading.Tasks.Task TrackMultipleSceneLoads(string[] sceneAddresses, LoadSceneMode mode = LoadSceneMode.Additive)
{
var loadTasks = new List<System.Threading.Tasks.Task>();

foreach (string address in sceneAddresses)
{
var task = System.Threading.Tasks.Task.Run(async () =>
{
await System.Threading.Tasks.Task.Delay(Random.Range(100, 500)); // 模拟不同加载时间
TrackSceneLoad(address, mode);
});
loadTasks.Add(task);
}

await System.Threading.Tasks.Task.WhenAll(loadTasks);
}

/// <summary>
/// 清理进度追踪
/// </summary>
public void ClearProgressTracker()
{
progressTracker.Clear();
loadingQueue.Clear();
Debug.Log("场景加载进度追踪已清理");
}
}

多场景管理策略

场景管理器

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
298
299
300
301
302
303
304
305
306
307
308
309
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;

public class AdvancedSceneManager : MonoBehaviour
{
[System.Serializable]
public class SceneConfiguration
{
public string sceneAddress;
public string sceneName;
public LoadSceneMode loadMode;
public bool isPersistent; // 是否持久场景
public bool isRequired; // 是否必需场景
public int priority; // 加载优先级
public string[] tags; // 场景标签
}

[Header("场景配置")]
public SceneConfiguration[] sceneConfigs;

[Header("管理设置")]
public bool autoLoadRequiredScenes = true;
public float sceneLoadTimeout = 30f; // 场景加载超时时间

private Dictionary<string, SceneConfiguration> sceneConfigMap = new Dictionary<string, SceneConfiguration>();
private Dictionary<string, SceneInstance> loadedScenes = new Dictionary<string, SceneInstance>();
private Dictionary<string, AsyncOperationHandle<SceneInstance>> activeLoads = new Dictionary<string, AsyncOperationHandle<SceneInstance>>();

[Header("回调事件")]
public System.Action<string> onSceneLoadStarted;
public System.Action<string, float> onSceneLoadProgress;
public System.Action<string> onSceneLoadCompleted;
public System.Action<string, string> onSceneLoadFailed;
public System.Action<string> onSceneUnloaded;

void Start()
{
// 初始化场景配置映射
foreach (var config in sceneConfigs)
{
sceneConfigMap[config.sceneAddress] = config;

if (autoLoadRequiredScenes && config.isRequired)
{
StartCoroutine(LoadRequiredScene(config.sceneAddress));
}
}
}

private System.Collections.IEnumerator LoadRequiredScene(string sceneAddress)
{
yield return new WaitForSeconds(0.1f); // 短暂延迟
LoadSceneAsync(sceneAddress);
}

/// <summary>
/// 异步加载场景
/// </summary>
public async void LoadSceneAsync(string sceneAddress)
{
if (activeLoads.ContainsKey(sceneAddress))
{
Debug.LogWarning($"场景正在加载中: {sceneAddress}");
return;
}

if (loadedScenes.ContainsKey(sceneAddress))
{
Debug.Log($"场景已加载: {sceneAddress}");
onSceneLoadCompleted?.Invoke(sceneAddress);
return;
}

if (!sceneConfigMap.ContainsKey(sceneAddress))
{
Debug.LogError($"未找到场景配置: {sceneAddress}");
onSceneLoadFailed?.Invoke(sceneAddress, "场景配置未找到");
return;
}

var config = sceneConfigMap[sceneAddress];

Debug.Log($"开始加载场景: {sceneAddress}, 模式: {config.loadMode}");
onSceneLoadStarted?.Invoke(sceneAddress);

try
{
var handle = Addressables.LoadSceneAsync(sceneAddress, config.loadMode, true);
activeLoads[sceneAddress] = handle;

// 设置超时检测
System.DateTime startTime = System.DateTime.Now;

// 监听进度
handle.PercentCompleteChanged += (op) =>
{
float progress = op.PercentComplete;
onSceneLoadProgress?.Invoke(sceneAddress, progress);

// 检查是否超时
if ((System.DateTime.Now - startTime).TotalSeconds > sceneLoadTimeout)
{
Debug.LogError($"场景加载超时: {sceneAddress}");
Addressables.Release(handle);
activeLoads.Remove(sceneAddress);
onSceneLoadFailed?.Invoke(sceneAddress, "加载超时");
return;
}
};

// 等待加载完成
SceneInstance sceneInstance = await handle.Task;

if (sceneInstance.Scene.IsValid())
{
loadedScenes[sceneAddress] = sceneInstance;
Debug.Log($"场景加载成功: {sceneInstance.Scene.name}");
onSceneLoadCompleted?.Invoke(sceneAddress);
}
else
{
string errorMsg = "场景加载失败";
Debug.LogError(errorMsg);
onSceneLoadFailed?.Invoke(sceneAddress, errorMsg);
}
}
catch (System.Exception e)
{
string errorMsg = $"场景加载异常: {e.Message}";
Debug.LogError(errorMsg);
onSceneLoadFailed?.Invoke(sceneAddress, errorMsg);
}
finally
{
if (activeLoads.ContainsKey(sceneAddress))
{
Addressables.Release(activeLoads[sceneAddress]);
activeLoads.Remove(sceneAddress);
}
}
}

/// <summary>
/// 卸载场景
/// </summary>
public async void UnloadSceneAsync(string sceneAddress)
{
if (!loadedScenes.ContainsKey(sceneAddress))
{
Debug.LogWarning($"场景未加载: {sceneAddress}");
return;
}

if (sceneConfigMap.ContainsKey(sceneAddress) && sceneConfigMap[sceneAddress].isPersistent)
{
Debug.LogWarning($"无法卸载持久场景: {sceneAddress}");
return;
}

var sceneInstance = loadedScenes[sceneAddress];

try
{
var handle = Addressables.UnloadSceneAsync(sceneInstance);
await handle.Task;

loadedScenes.Remove(sceneAddress);
Debug.Log($"场景卸载成功: {sceneInstance.Scene.name}");
onSceneUnloaded?.Invoke(sceneAddress);

Addressables.Release(handle);
}
catch (System.Exception e)
{
Debug.LogError($"场景卸载异常: {e.Message}");
}
}

/// <summary>
/// 根据标签加载场景
/// </summary>
public void LoadScenesByTag(string tag)
{
foreach (var config in sceneConfigs)
{
if (System.Array.IndexOf(config.tags, tag) >= 0)
{
LoadSceneAsync(config.sceneAddress);
}
}
}

/// <summary>
/// 根据优先级排序加载场景
/// </summary>
public async System.Threading.Tasks.Task LoadScenesByPriority(int minPriority = 0, int maxPriority = int.MaxValue)
{
var scenesToLoad = new List<SceneConfiguration>();

foreach (var config in sceneConfigs)
{
if (config.priority >= minPriority && config.priority <= maxPriority)
{
scenesToLoad.Add(config);
}
}

// 按优先级排序
scenesToLoad.Sort((a, b) => a.priority.CompareTo(b.priority));

foreach (var config in scenesToLoad)
{
await System.Threading.Tasks.Task.Delay(100); // 短暂延迟避免同时加载
LoadSceneAsync(config.sceneAddress);
}
}

/// <summary>
/// 获取场景状态
/// </summary>
public bool IsSceneLoaded(string sceneAddress)
{
return loadedScenes.ContainsKey(sceneAddress);
}

/// <summary>
/// 获取场景实例
/// </summary>
public SceneInstance GetSceneInstance(string sceneAddress)
{
if (loadedScenes.ContainsKey(sceneAddress))
{
return loadedScenes[sceneAddress];
}
return new SceneInstance();
}

/// <summary>
/// 获取所有已加载场景
/// </summary>
public SceneInstance[] GetLoadedSceneInstances()
{
var instances = new List<SceneInstance>();
foreach (var pair in loadedScenes)
{
instances.Add(pair.Value);
}
return instances.ToArray();
}

/// <summary>
/// 获取场景配置
/// </summary>
public SceneConfiguration GetSceneConfiguration(string sceneAddress)
{
if (sceneConfigMap.ContainsKey(sceneAddress))
{
return sceneConfigMap[sceneAddress];
}
return null;
}

/// <summary>
/// 卸载所有非必需场景
/// </summary>
public async System.Threading.Tasks.Task UnloadAllNonRequiredScenes()
{
var scenesToUnload = new List<string>();

foreach (var pair in loadedScenes)
{
if (sceneConfigMap.ContainsKey(pair.Key) && !sceneConfigMap[pair.Key].isRequired)
{
scenesToUnload.Add(pair.Key);
}
}

foreach (string sceneAddress in scenesToUnload)
{
await UnloadSceneAsync(sceneAddress);
}

Debug.Log($"已卸载 {scenesToUnload.Count} 个非必需场景");
}

void OnDestroy()
{
// 清理所有加载操作
foreach (var handle in activeLoads.Values)
{
Addressables.Release(handle);
}
activeLoads.Clear();

// 卸载所有场景
var allLoadedScenes = new List<string>(loadedScenes.Keys);
foreach (string sceneAddress in allLoadedScenes)
{
if (sceneConfigMap.ContainsKey(sceneAddress) && !sceneConfigMap[sceneAddress].isPersistent)
{
Addressables.UnloadSceneAsync(loadedScenes[sceneAddress]);
}
}
loadedScenes.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
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.SceneManagement;

public class LevelSelectionSystem : MonoBehaviour
{
[System.Serializable]
public class LevelInfo
{
public string levelAddress;
public string levelName;
public string description;
public int levelNumber;
public bool isUnlocked;
public bool isCompleted;
public string[] requiredLevels; // 需要前置关卡
public int stars; // 完成星级
public float bestTime; // 最佳时间
public int bestScore; // 最高分数
}

[Header("关卡数据")]
public LevelInfo[] levels;

[Header("UI引用")]
public Transform levelContainer;
public GameObject levelButtonPrefab;
public Slider loadingSlider;
public Text loadingText;
public Button loadButton;
public Button backButton;

[Header("场景配置")]
public string gameSceneAddress = "Assets/Scenes/GameScene.unity";
public string levelSelectScene = "Assets/Scenes/LevelSelect.unity";

private List<LevelButtonController> levelButtons = new List<LevelButtonController>();
private string selectedLevelAddress = "";
private AdvancedSceneManager sceneManager;

void Start()
{
sceneManager = FindObjectOfType<AdvancedSceneManager>();
if (sceneManager == null)
{
sceneManager = gameObject.AddComponent<AdvancedSceneManager>();
}

InitializeLevelSelection();
SetupUI();
}

private void InitializeLevelSelection()
{
// 加载关卡数据(在实际项目中可能从配置文件或服务器加载)
LoadLevelData();

// 创建关卡按钮
CreateLevelButtons();
}

private void LoadLevelData()
{
// 在实际项目中,这里可能从配置文件、数据库或服务器加载关卡数据
// 目前使用硬编码的示例数据

// 模拟加载玩家进度
LoadPlayerProgress();
}

private void LoadPlayerProgress()
{
// 从PlayerPrefs或其他存储加载玩家进度
for (int i = 0; i < levels.Length; i++)
{
string levelKey = $"Level_{i}_Completed";
levels[i].isCompleted = PlayerPrefs.GetInt(levelKey, 0) == 1;

// 解锁后续关卡
if (levels[i].isCompleted)
{
UnlockNextLevels(i);
}
}

// 第一关默认解锁
if (levels.Length > 0)
{
levels[0].isUnlocked = true;
}
}

private void UnlockNextLevels(int completedLevelIndex)
{
// 解锁下一关
if (completedLevelIndex + 1 < levels.Length)
{
levels[completedLevelIndex + 1].isUnlocked = true;
}
}

private void CreateLevelButtons()
{
if (levelContainer == null || levelButtonPrefab == null) return;

// 清理现有按钮
foreach (Transform child in levelContainer)
{
Destroy(child.gameObject);
}
levelButtons.Clear();

// 按关卡编号排序
Array.Sort(levels, (a, b) => a.levelNumber.CompareTo(b.levelNumber));

foreach (var level in levels)
{
GameObject buttonObj = Instantiate(levelButtonPrefab, levelContainer);
LevelButtonController buttonController = buttonObj.GetComponent<LevelButtonController>();

if (buttonController != null)
{
buttonController.Initialize(level, OnLevelSelected);
levelButtons.Add(buttonController);
}
}
}

private void SetupUI()
{
if (loadButton != null)
{
loadButton.onClick.AddListener(LoadSelectedLevel);
}

if (backButton != null)
{
backButton.onClick.AddListener(GoToLevelSelect);
}

if (loadingSlider != null)
{
loadingSlider.gameObject.SetActive(false);
}
}

private void OnLevelSelected(LevelInfo level)
{
selectedLevelAddress = level.levelAddress;

// 更新UI状态
UpdateLoadButtonState();

Debug.Log($"选择关卡: {level.levelName}, 地址: {level.levelAddress}");
}

private void UpdateLoadButtonState()
{
if (loadButton != null)
{
bool canLoad = !string.IsNullOrEmpty(selectedLevelAddress);
loadButton.interactable = canLoad;
}
}

private async void LoadSelectedLevel()
{
if (string.IsNullOrEmpty(selectedLevelAddress))
{
Debug.LogWarning("没有选择关卡");
return;
}

// 检查关卡是否已解锁
var selectedLevel = Array.Find(levels, l => l.levelAddress == selectedLevelAddress);
if (selectedLevel != null && !selectedLevel.isUnlocked)
{
Debug.LogWarning($"关卡 {selectedLevel.levelName} 未解锁");
return;
}

ShowLoadingUI(true);

try
{
// 卸载当前关卡(如果有的话)
await sceneManager.UnloadAllNonRequiredScenes();

// 加载游戏场景
sceneManager.onSceneLoadProgress += OnSceneLoadProgress;
sceneManager.onSceneLoadCompleted += OnSceneLoadComplete;
sceneManager.onSceneLoadFailed += OnSceneLoadFailed;

sceneManager.LoadSceneAsync(gameSceneAddress);

// 等待游戏场景加载完成后再加载关卡
await System.Threading.Tasks.Task.Delay(1000); // 等待基本场景加载

// 加载关卡场景
sceneManager.LoadSceneAsync(selectedLevelAddress);
}
catch (Exception e)
{
Debug.LogError($"加载关卡失败: {e.Message}");
ShowLoadingUI(false);
}
}

private void OnSceneLoadProgress(string sceneAddress, float progress)
{
if (loadingSlider != null)
{
loadingSlider.value = progress;
}

if (loadingText != null)
{
loadingText.text = $"加载中... {(progress * 100):F1}%";
}
}

private void OnSceneLoadComplete(string sceneAddress)
{
Debug.Log($"场景加载完成: {sceneAddress}");

if (sceneAddress == selectedLevelAddress)
{
// 关卡加载完成,隐藏加载UI
ShowLoadingUI(false);
Debug.Log($"关卡 {sceneAddress} 加载完成,开始游戏");
}
}

private void OnSceneLoadFailed(string sceneAddress, string error)
{
Debug.LogError($"场景加载失败: {sceneAddress}, 错误: {error}");
ShowLoadingUI(false);
}

private void ShowLoadingUI(bool show)
{
if (loadingSlider != null)
{
loadingSlider.gameObject.SetActive(show);
}

if (loadingText != null)
{
loadingText.gameObject.SetActive(show);
if (show)
{
loadingText.text = "加载中...";
}
}

if (loadButton != null)
{
loadButton.gameObject.SetActive(!show);
}
}

private async void GoToLevelSelect()
{
ShowLoadingUI(true);

// 卸载游戏场景,返回关卡选择场景
await sceneManager.UnloadAllNonRequiredScenes();

sceneManager.LoadSceneAsync(levelSelectScene);

ShowLoadingUI(false);
}

void OnDestroy()
{
if (sceneManager != null)
{
sceneManager.onSceneLoadProgress -= OnSceneLoadProgress;
sceneManager.onSceneLoadCompleted -= OnSceneLoadComplete;
sceneManager.onSceneLoadFailed -= OnSceneLoadFailed;
}
}
}

// 关卡按钮控制器
public class LevelButtonController : MonoBehaviour
{
[Header("UI引用")]
public Text levelNameText;
public Text levelNumberText;
public Text statusText;
public Image lockImage;
public GameObject starsContainer;
public GameObject[] starImages;
public Button button;

private LevelSelectionSystem.LevelInfo levelInfo;
private System.Action<LevelSelectionSystem.LevelInfo> onSelected;

public void Initialize(LevelSelectionSystem.LevelInfo level, System.Action<LevelSelectionSystem.LevelInfo> callback)
{
levelInfo = level;
onSelected = callback;

UpdateUI();

if (button != null)
{
button.onClick.AddListener(OnButtonClick);
button.interactable = level.isUnlocked;
}
}

private void UpdateUI()
{
if (levelNameText != null)
{
levelNameText.text = levelInfo.levelName;
}

if (levelNumberText != null)
{
levelNumberText.text = levelInfo.levelNumber.ToString();
}

if (statusText != null)
{
if (!levelInfo.isUnlocked)
{
statusText.text = "未解锁";
statusText.color = Color.gray;
}
else if (levelInfo.isCompleted)
{
statusText.text = "已完成";
statusText.color = Color.green;
}
else
{
statusText.text = "未完成";
statusText.color = Color.white;
}
}

if (lockImage != null)
{
lockImage.gameObject.SetActive(!levelInfo.isUnlocked);
}

if (starsContainer != null && starImages != null)
{
for (int i = 0; i < starImages.Length; i++)
{
if (i < levelInfo.stars)
{
starImages[i].SetActive(true);
}
else
{
starImages[i].SetActive(false);
}
}
}
}

private void OnButtonClick()
{
if (levelInfo.isUnlocked)
{
onSelected?.Invoke(levelInfo);
}
}
}

通过本章的学习,您已经全面掌握了Addressables场景管理的各个方面,包括场景加载、加载模式、依赖管理、进度追踪和多场景管理策略。下一章我们将探讨资源预加载与缓存机制。