UnloadSceneAsync 默认 autoReleaseHandle 为 true,yield 完成后访问 Status 会抛异常。改为手动管理句柄生命周期,并将 FixSystemCenter 与 ResourceSystem 的清理移到卸载成功之后。
224 lines
7.5 KiB
C#
224 lines
7.5 KiB
C#
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<SceneLoader>
|
|
{
|
|
private AsyncOperationHandle<SceneInstance> _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<SceneOperationResult> 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<SceneInstance> 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<SceneOperationResult> 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<SceneOperationResult> completed)
|
|
{
|
|
if (!_loadHandle.IsValid())
|
|
{
|
|
_currentSceneName = null;
|
|
_currentScene = default;
|
|
completed?.Invoke(Success(null));
|
|
yield break;
|
|
}
|
|
|
|
DOTween.KillAll();
|
|
|
|
AsyncOperationHandle<SceneInstance> 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
|
|
};
|
|
}
|
|
}
|
|
}
|