342 lines
13 KiB
C#
342 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using UI;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using Bootstrap;
|
|
|
|
namespace Core
|
|
{
|
|
/// <summary>
|
|
/// Singleton service for loading and unloading Unity scenes asynchronously, with events for progress and completion.
|
|
/// </summary>
|
|
public class SceneManagerService : MonoBehaviour
|
|
{
|
|
private LoadingScreenController _loadingScreen;
|
|
private static SceneManagerService _instance;
|
|
private static bool _isQuitting = false;
|
|
|
|
/// <summary>
|
|
/// Singleton instance of the SceneManagerService. No longer creates an instance if one doesn't exist.
|
|
/// </summary>
|
|
public static SceneManagerService Instance => _instance;
|
|
|
|
// Events for scene lifecycle
|
|
public event Action<string> SceneLoadStarted;
|
|
public event Action<string, float> SceneLoadProgress;
|
|
public event Action<string> SceneLoadCompleted;
|
|
public event Action<string> SceneUnloadStarted;
|
|
public event Action<string, float> SceneUnloadProgress;
|
|
public event Action<string> SceneUnloadCompleted;
|
|
|
|
private readonly Dictionary<string, AsyncOperation> _activeLoads = new();
|
|
private readonly Dictionary<string, AsyncOperation> _activeUnloads = new();
|
|
private const string BootstrapSceneName = "BootstrapScene";
|
|
|
|
void Awake()
|
|
{
|
|
_instance = this;
|
|
// DontDestroyOnLoad(gameObject);
|
|
|
|
// Initialize current scene tracking immediately in Awake
|
|
InitializeCurrentSceneTracking();
|
|
|
|
// Register for post-boot initialization
|
|
BootCompletionService.RegisterInitAction(InitializePostBoot);
|
|
|
|
// Ensure BootstrapScene is loaded at startup
|
|
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
|
|
if (!bootstrap.isLoaded)
|
|
{
|
|
SceneManager.LoadScene(BootstrapSceneName, LoadSceneMode.Additive);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize current scene tracking immediately in Awake
|
|
/// This ensures scene management works correctly regardless of boot timing
|
|
/// </summary>
|
|
private void InitializeCurrentSceneTracking()
|
|
{
|
|
// Get the active scene and use it as the current gameplay scene
|
|
Scene activeScene = SceneManager.GetActiveScene();
|
|
|
|
if (activeScene.IsValid())
|
|
{
|
|
// If this is the MainMenu or another gameplay scene, track it
|
|
if (activeScene.name != BootstrapSceneName)
|
|
{
|
|
CurrentGameplayScene = activeScene.name;
|
|
Logging.Debug($"[SceneManagerService] Initialized with current scene: {CurrentGameplayScene}");
|
|
}
|
|
// Otherwise default to MainMenu
|
|
else
|
|
{
|
|
CurrentGameplayScene = "AppleHillsOverworld";
|
|
Logging.Debug($"[SceneManagerService] Initialized with default scene: {CurrentGameplayScene}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CurrentGameplayScene = "AppleHillsOverworld";
|
|
Logging.Debug($"[SceneManagerService] No valid active scene, defaulting to: {CurrentGameplayScene}");
|
|
}
|
|
}
|
|
|
|
private void InitializePostBoot()
|
|
{
|
|
// Set up loading screen reference and events after boot is complete
|
|
_loadingScreen = LoadingScreenController.Instance;
|
|
|
|
// Set up loading screen event handlers if available
|
|
SetupLoadingScreenEvents();
|
|
|
|
Logging.Debug($"[SceneManagerService] Post-boot initialization complete, current scene is: {CurrentGameplayScene}");
|
|
}
|
|
|
|
private void SetupLoadingScreenEvents()
|
|
{
|
|
if (_loadingScreen == null) return;
|
|
|
|
SceneLoadStarted += _ => _loadingScreen.ShowLoadingScreen(() => GetAggregateLoadProgress());
|
|
SceneLoadCompleted += _ => _loadingScreen.HideLoadingScreen();
|
|
}
|
|
|
|
void OnApplicationQuit()
|
|
{
|
|
_isQuitting = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load a single scene asynchronously (additive).
|
|
/// </summary>
|
|
/// <param name="sceneName">Name of the scene to load.</param>
|
|
/// <param name="progress">Optional progress reporter.</param>
|
|
public async Task LoadSceneAsync(string sceneName, IProgress<float> progress = null)
|
|
{
|
|
SceneLoadStarted?.Invoke(sceneName);
|
|
var op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
|
|
_activeLoads[sceneName] = op;
|
|
while (!op.isDone)
|
|
{
|
|
progress?.Report(op.progress);
|
|
SceneLoadProgress?.Invoke(sceneName, op.progress);
|
|
await Task.Yield();
|
|
}
|
|
_activeLoads.Remove(sceneName);
|
|
SceneLoadCompleted?.Invoke(sceneName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unload a single scene asynchronously.
|
|
/// </summary>
|
|
/// <param name="sceneName">Name of the scene to unload.</param>
|
|
/// <param name="progress">Optional progress reporter.</param>
|
|
public async Task UnloadSceneAsync(string sceneName, IProgress<float> progress = null)
|
|
{
|
|
var scene = SceneManager.GetSceneByName(sceneName);
|
|
if (!scene.isLoaded)
|
|
{
|
|
Logging.Warning($"SceneManagerService: Attempted to unload scene '{sceneName}', but it is not loaded.");
|
|
return;
|
|
}
|
|
SceneUnloadStarted?.Invoke(sceneName);
|
|
var op = SceneManager.UnloadSceneAsync(sceneName);
|
|
_activeUnloads[sceneName] = op;
|
|
while (!op.isDone)
|
|
{
|
|
progress?.Report(op.progress);
|
|
SceneUnloadProgress?.Invoke(sceneName, op.progress);
|
|
await Task.Yield();
|
|
}
|
|
_activeUnloads.Remove(sceneName);
|
|
SceneUnloadCompleted?.Invoke(sceneName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load multiple scenes asynchronously.
|
|
/// </summary>
|
|
/// <param name="sceneNames">Enumerable of scene names to load.</param>
|
|
/// <param name="progress">Optional progress reporter.</param>
|
|
public async Task LoadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null)
|
|
{
|
|
// Show loading screen at the start of multiple scene loading
|
|
if (_loadingScreen != null)
|
|
{
|
|
_loadingScreen.ShowLoadingScreen();
|
|
}
|
|
|
|
int total = 0;
|
|
int done = 0;
|
|
var ops = new List<AsyncOperation>();
|
|
foreach (var name in sceneNames)
|
|
{
|
|
total++;
|
|
var op = SceneManager.LoadSceneAsync(name, LoadSceneMode.Additive);
|
|
_activeLoads[name] = op;
|
|
ops.Add(op);
|
|
SceneLoadStarted?.Invoke(name);
|
|
}
|
|
|
|
while (done < total)
|
|
{
|
|
done = 0;
|
|
float aggregate = 0f;
|
|
foreach (var op in ops)
|
|
{
|
|
if (op.isDone) done++;
|
|
aggregate += op.progress;
|
|
}
|
|
float avgProgress = aggregate / total;
|
|
progress?.Report(avgProgress);
|
|
|
|
await Task.Yield();
|
|
}
|
|
|
|
foreach (var name in sceneNames)
|
|
{
|
|
_activeLoads.Remove(name);
|
|
SceneLoadCompleted?.Invoke(name);
|
|
}
|
|
|
|
// Hide loading screen after all scenes are loaded
|
|
if (_loadingScreen != null)
|
|
{
|
|
_loadingScreen.HideLoadingScreen();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unload multiple scenes asynchronously.
|
|
/// </summary>
|
|
/// <param name="sceneNames">Enumerable of scene names to unload.</param>
|
|
/// <param name="progress">Optional progress reporter.</param>
|
|
public async Task UnloadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null)
|
|
{
|
|
// Show loading screen at the start of multiple scene unloading
|
|
if (_loadingScreen != null)
|
|
{
|
|
_loadingScreen.ShowLoadingScreen();
|
|
}
|
|
|
|
int total = 0;
|
|
int done = 0;
|
|
var ops = new List<AsyncOperation>();
|
|
foreach (var name in sceneNames)
|
|
{
|
|
total++;
|
|
var op = SceneManager.UnloadSceneAsync(name);
|
|
_activeUnloads[name] = op;
|
|
ops.Add(op);
|
|
SceneUnloadStarted?.Invoke(name);
|
|
}
|
|
|
|
while (done < total)
|
|
{
|
|
done = 0;
|
|
float aggregate = 0f;
|
|
foreach (var op in ops)
|
|
{
|
|
aggregate += op.progress;
|
|
if (op.isDone) done++;
|
|
}
|
|
float avg = aggregate / total;
|
|
progress?.Report(avg);
|
|
|
|
await Task.Yield();
|
|
}
|
|
|
|
foreach (var name in sceneNames)
|
|
{
|
|
_activeUnloads.Remove(name);
|
|
SceneUnloadCompleted?.Invoke(name);
|
|
}
|
|
|
|
// Hide loading screen after all scenes are unloaded
|
|
if (_loadingScreen != null)
|
|
{
|
|
_loadingScreen.HideLoadingScreen();
|
|
}
|
|
}
|
|
|
|
// Optionally: expose current progress for all active operations
|
|
public float GetAggregateLoadProgress()
|
|
{
|
|
if (_activeLoads.Count == 0) return 1f;
|
|
float sum = 0f;
|
|
foreach (var op in _activeLoads.Values) sum += op.progress;
|
|
return sum / _activeLoads.Count;
|
|
}
|
|
public float GetAggregateUnloadProgress()
|
|
{
|
|
if (_activeUnloads.Count == 0) return 1f;
|
|
float sum = 0f;
|
|
foreach (var op in _activeUnloads.Values) sum += op.progress;
|
|
return sum / _activeUnloads.Count;
|
|
}
|
|
|
|
// Tracks the currently loaded gameplay scene (not persistent/bootstrapper)
|
|
public string CurrentGameplayScene { get; set; } = "AppleHillsOverworld";
|
|
|
|
public async Task ReloadCurrentScene(IProgress<float> progress = null, bool autoHideLoadingScreen = true)
|
|
{
|
|
await SwitchSceneAsync(CurrentGameplayScene, progress, autoHideLoadingScreen);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Switches from current gameplay scene to a new one
|
|
/// </summary>
|
|
/// <param name="newSceneName">Name of the scene to load</param>
|
|
/// <param name="progress">Optional progress reporter</param>
|
|
/// <param name="autoHideLoadingScreen">Whether to automatically hide the loading screen when complete. If false, caller must hide it manually.</param>
|
|
public async Task SwitchSceneAsync(string newSceneName, IProgress<float> progress = null, bool autoHideLoadingScreen = true)
|
|
{
|
|
// Show loading screen at the start (whether using auto-hide or not)
|
|
if (_loadingScreen != null && !_loadingScreen.IsActive)
|
|
{
|
|
_loadingScreen.ShowLoadingScreen();
|
|
}
|
|
|
|
// Remove all AstarPath (A* Pathfinder) singletons before loading the new scene
|
|
var astarPaths = FindObjectsByType<AstarPath>(FindObjectsSortMode.None);
|
|
foreach (var astar in astarPaths)
|
|
{
|
|
if (Application.isPlaying)
|
|
Destroy(astar.gameObject);
|
|
else
|
|
DestroyImmediate(astar.gameObject);
|
|
}
|
|
// Unload previous gameplay scene (if not BootstrapScene and not same as new)
|
|
if (!string.IsNullOrEmpty(CurrentGameplayScene)&& CurrentGameplayScene != BootstrapSceneName)
|
|
{
|
|
var prevScene = SceneManager.GetSceneByName(CurrentGameplayScene);
|
|
if (prevScene.isLoaded)
|
|
{
|
|
await UnloadSceneAsync(CurrentGameplayScene);
|
|
}
|
|
else
|
|
{
|
|
Logging.Warning($"SceneManagerService: Previous scene '{CurrentGameplayScene}' is not loaded, skipping unload.");
|
|
}
|
|
}
|
|
// Ensure BootstrapScene is loaded before loading new scene
|
|
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
|
|
if (!bootstrap.isLoaded)
|
|
{
|
|
SceneManager.LoadScene(BootstrapSceneName, LoadSceneMode.Additive);
|
|
}
|
|
// Load new gameplay scene
|
|
await LoadSceneAsync(newSceneName, progress);
|
|
// Update tracker
|
|
CurrentGameplayScene = newSceneName;
|
|
|
|
// Only hide the loading screen if autoHideLoadingScreen is true
|
|
if (autoHideLoadingScreen && _loadingScreen != null)
|
|
{
|
|
_loadingScreen.HideLoadingScreen();
|
|
}
|
|
}
|
|
}
|
|
}
|