using System; using System.Collections; using AibisDream.FixSystem; using AibisDream.Framework; using AibisDream.Kit; using AibisDream.UI; using DG.Tweening; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; namespace AibisDream { public sealed class SceneOperationResult { public bool Success { get; internal set; } public string SceneName { get; internal set; } public string Error { get; internal set; } } public class SceneLoader : Singleton { private AsyncOperationHandle _loadHandle; private string _currentSceneName; private Scene _currentScene; private bool _isLoading; public string CurrentSceneName => _currentSceneName; public Scene CurrentScene => _currentScene; public bool IsLoading => _isLoading; public IEnumerator LoadSceneAsync( string targetScene, Action completed = null) { if (_isLoading) { completed?.Invoke(Failure(targetScene, "SceneLoader is busy.")); yield break; } if (string.IsNullOrWhiteSpace(targetScene)) { completed?.Invoke(Failure(targetScene, "Scene key is empty.")); yield break; } _isLoading = true; SceneOperationResult result = null; UIManager.Instance?.ShowPreferredLoading(); try { if (_loadHandle.IsValid()) { SceneOperationResult unloadResult = null; yield return UnloadCurrentSceneCore(value => unloadResult = value); if (unloadResult?.Success != true) { result = unloadResult ?? Failure(_currentSceneName, "Current scene unload did not return a result."); yield break; } } ResourceSystem.CreateSceneLoader(); AsyncOperationHandle pendingHandle; try { pendingHandle = Addressables.LoadSceneAsync(targetScene, LoadSceneMode.Additive); } catch (Exception ex) { ResourceSystem.ReleaseSceneLoader(); result = Failure(targetScene, ex.Message); yield break; } while (!pendingHandle.IsDone) { UIManager.Instance?.SetPreferredLoadingProgress(pendingHandle.PercentComplete); yield return null; } UIManager.Instance?.SetPreferredLoadingProgress(1f); if (pendingHandle.Status != AsyncOperationStatus.Succeeded) { var error = pendingHandle.OperationException?.Message ?? "Addressables scene load failed."; if (pendingHandle.IsValid()) { Addressables.Release(pendingHandle); } ResourceSystem.ReleaseSceneLoader(); result = Failure(targetScene, error); yield break; } var loadedScene = pendingHandle.Result.Scene; if (!loadedScene.IsValid() || !loadedScene.isLoaded) { if (pendingHandle.IsValid()) { yield return Addressables.UnloadSceneAsync(pendingHandle); } ResourceSystem.ReleaseSceneLoader(); result = Failure(targetScene, "Addressables returned an invalid SceneInstance."); yield break; } _loadHandle = pendingHandle; _currentSceneName = targetScene; _currentScene = loadedScene; EnumEventSystem.Global.Send(EventEnum.SceneLoad, targetScene); result = Success(targetScene); } finally { _isLoading = false; UIManager.Instance?.HidePreferredLoading(); completed?.Invoke(result ?? Failure(targetScene, "Scene load was interrupted.")); } } public IEnumerator UnloadSceneAsync(Action completed = null) { if (_isLoading) { completed?.Invoke(Failure(_currentSceneName, "SceneLoader is busy.")); yield break; } _isLoading = true; SceneOperationResult result = null; try { yield return UnloadCurrentSceneCore(value => result = value); } finally { _isLoading = false; completed?.Invoke(result ?? Failure(_currentSceneName, "Scene unload was interrupted.")); } } private IEnumerator UnloadCurrentSceneCore(Action completed) { if (!_loadHandle.IsValid()) { _currentSceneName = null; _currentScene = default; completed?.Invoke(Success(null)); yield break; } DOTween.KillAll(); AsyncOperationHandle unloadHandle; try { // Keep the operation handle alive until its result has been inspected. // Addressables defaults autoReleaseHandle to true, which makes Status throw // after the yielded operation completes. unloadHandle = Addressables.UnloadSceneAsync(_loadHandle, autoReleaseHandle: false); } catch (Exception ex) { completed?.Invoke(Failure(_currentSceneName, ex.Message)); yield break; } yield return unloadHandle; var unloadSucceeded = unloadHandle.Status == AsyncOperationStatus.Succeeded; var unloadError = unloadSucceeded ? null : unloadHandle.OperationException?.Message ?? "Addressables scene unload failed."; if (unloadHandle.IsValid()) { Addressables.Release(unloadHandle); } if (!unloadSucceeded) { completed?.Invoke(Failure(_currentSceneName, unloadError)); yield break; } var unloadedSceneName = _currentSceneName; _loadHandle = default; _currentSceneName = null; _currentScene = default; FixSystemCenter.SystemDic?.Clear(); ResourceSystem.ReleaseSceneLoader(); completed?.Invoke(Success(unloadedSceneName)); } private static SceneOperationResult Success(string sceneName) { return new SceneOperationResult { Success = true, SceneName = sceneName }; } private static SceneOperationResult Failure(string sceneName, string error) { return new SceneOperationResult { Success = false, SceneName = sceneName, Error = error }; } } }