第6章 场景管理 发表于 2026-01-03 更新于 2026-01-03
第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; 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); } } 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(); } } 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} " ); } } 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; public async void LoadSingleScene (string sceneAddress ) { try { Debug.Log($"开始加载单场景: {sceneAddress} " ); 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); } } 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; public async void LoadAdditiveScene (string sceneAddress ) { if (activeLoadHandles.ContainsKey(sceneAddress)) { Debug.LogWarning($"场景 {sceneAddress} 正在加载中" ); return ; } try { Debug.Log($"开始加载叠加场景: {sceneAddress} " ); 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); } } } 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); } } 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)); } public LoadedSceneInfo[] GetLoadedScenes () { return loadedScenes.ToArray(); } 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)); } } } 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} 加载完成" ); } 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} 卸载完成" ); } 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} 个非必需场景" ); } public bool IsSceneLoaded (string sceneAddress ) { return loadedScenes.ContainsKey(sceneAddress); } 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)} ]" ); } 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); } } 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; } 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 ; } 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; public System.Action<string > onSceneLoadComplete; public System.Action<string , string > onSceneLoadError; 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} 及其依赖加载完成" ); } 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 ; } public bool IsSceneLoaded (string sceneAddress ) { return loadedScenes.ContainsKey(sceneAddress); } 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; public System.Action<string > onSceneComplete; public System.Action<string > onSceneError; public System.Action<float > onOverallProgress; 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} " ); } public SceneLoadProgress GetSceneProgress (string sceneAddress ) { if (progressTracker.ContainsKey(sceneAddress)) { return progressTracker[sceneAddress]; } return null ; } 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; } 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} " ; } 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); } 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); } 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); } } } 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} " ); } } public void LoadScenesByTag (string tag ) { foreach (var config in sceneConfigs) { if (System.Array.IndexOf(config.tags, tag) >= 0 ) { LoadSceneAsync(config.sceneAddress); } } } 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); } } public bool IsSceneLoaded (string sceneAddress ) { return loadedScenes.ContainsKey(sceneAddress); } public SceneInstance GetSceneInstance (string sceneAddress ) { if (loadedScenes.ContainsKey(sceneAddress)) { return loadedScenes[sceneAddress]; } return new SceneInstance(); } public SceneInstance[] GetLoadedSceneInstances () { var instances = new List<SceneInstance>(); foreach (var pair in loadedScenes) { instances.Add(pair.Value); } return instances.ToArray(); } public SceneConfiguration GetSceneConfiguration (string sceneAddress ) { if (sceneConfigMap.ContainsKey(sceneAddress)) { return sceneConfigMap[sceneAddress]; } return null ; } 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 () { 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; 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) { 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场景管理的各个方面,包括场景加载、加载模式、依赖管理、进度追踪和多场景管理策略。下一章我们将探讨资源预加载与缓存机制。