Rework of base interactables and managed behaviors
This commit is contained in:
committed by
Michal Pikulski
parent
00e1746ac4
commit
f88bd0e2c9
@@ -2,17 +2,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using AppleHills.Core.Settings;
|
||||
using Core.Lifecycle;
|
||||
using Core.SaveLoad;
|
||||
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
|
||||
public class SceneManagerService : ManagedBehaviour
|
||||
{
|
||||
private LoadingScreenController _loadingScreen;
|
||||
private static SceneManagerService _instance;
|
||||
@@ -23,29 +24,39 @@ namespace Core
|
||||
public static SceneManagerService Instance => _instance;
|
||||
|
||||
// Events for scene lifecycle
|
||||
// NOTE: Most components should use lifecycle hooks (OnSceneReady, OnSceneUnloading)
|
||||
// instead of subscribing to these events. Events are primarily for orchestration.
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a scene starts loading. Used by loading screen orchestration.
|
||||
/// </summary>
|
||||
public event Action<string> SceneLoadStarted;
|
||||
public event Action<string, float> SceneLoadProgress;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a scene finishes loading.
|
||||
/// Used by loading screen orchestration and cross-scene components (e.g., PauseMenu).
|
||||
/// For component initialization, use OnSceneReady() lifecycle hook instead.
|
||||
/// </summary>
|
||||
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 LogVerbosity _logVerbosity = LogVerbosity.Debug;
|
||||
private const string BootstrapSceneName = "BootstrapScene";
|
||||
|
||||
void Awake()
|
||||
// ManagedBehaviour configuration
|
||||
public override int ManagedAwakePriority => 15; // Core infrastructure, after GameManager
|
||||
|
||||
private new void Awake()
|
||||
{
|
||||
base.Awake(); // CRITICAL: Register with LifecycleManager!
|
||||
|
||||
// Set instance immediately so it's available before OnManagedAwake() is called
|
||||
_instance = this;
|
||||
// DontDestroyOnLoad(gameObject);
|
||||
|
||||
// Initialize current scene tracking immediately in Awake
|
||||
// Initialize current scene tracking - critical for scene management
|
||||
InitializeCurrentSceneTracking();
|
||||
|
||||
// Register for post-boot initialization
|
||||
BootCompletionService.RegisterInitAction(InitializePostBoot);
|
||||
|
||||
// Ensure BootstrapScene is loaded at startup
|
||||
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
|
||||
if (!bootstrap.isLoaded)
|
||||
@@ -54,9 +65,17 @@ namespace Core
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
protected override void OnManagedAwake()
|
||||
{
|
||||
// Set up loading screen reference and events
|
||||
// This must happen in ManagedAwake because LoadingScreenController instance needs to be set first
|
||||
_loadingScreen = LoadingScreenController.Instance;
|
||||
SetupLoadingScreenEvents();
|
||||
|
||||
// Load verbosity settings
|
||||
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().sceneLogVerbosity;
|
||||
|
||||
LogDebugMessage($"SceneManagerService initialized, current scene is: {CurrentGameplayScene}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,17 +108,6 @@ namespace Core
|
||||
LogDebugMessage($"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();
|
||||
|
||||
LogDebugMessage($"Post-boot initialization complete, current scene is: {CurrentGameplayScene}");
|
||||
}
|
||||
|
||||
private void SetupLoadingScreenEvents()
|
||||
{
|
||||
@@ -122,7 +130,6 @@ namespace Core
|
||||
while (!op.isDone)
|
||||
{
|
||||
progress?.Report(op.progress);
|
||||
SceneLoadProgress?.Invoke(sceneName, op.progress);
|
||||
await Task.Yield();
|
||||
}
|
||||
_activeLoads.Remove(sceneName);
|
||||
@@ -142,17 +149,15 @@ namespace Core
|
||||
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>
|
||||
@@ -230,7 +235,6 @@ namespace Core
|
||||
var op = SceneManager.UnloadSceneAsync(name);
|
||||
_activeUnloads[name] = op;
|
||||
ops.Add(op);
|
||||
SceneUnloadStarted?.Invoke(name);
|
||||
}
|
||||
|
||||
while (done < total)
|
||||
@@ -251,7 +255,6 @@ namespace Core
|
||||
foreach (var name in sceneNames)
|
||||
{
|
||||
_activeUnloads.Remove(name);
|
||||
SceneUnloadCompleted?.Invoke(name);
|
||||
}
|
||||
|
||||
// Hide loading screen after all scenes are unloaded
|
||||
@@ -293,13 +296,35 @@ namespace Core
|
||||
/// <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)
|
||||
string oldSceneName = CurrentGameplayScene;
|
||||
|
||||
// PHASE 1: Show loading screen at the start
|
||||
// Use explicit progress provider to combine unload + load progress
|
||||
if (_loadingScreen != null)
|
||||
{
|
||||
_loadingScreen.ShowLoadingScreen();
|
||||
_loadingScreen.ShowLoadingScreen(() => GetAggregateLoadProgress());
|
||||
}
|
||||
|
||||
// Remove all AstarPath (A* Pathfinder) singletons before loading the new scene
|
||||
// PHASE 2: Broadcast scene unloading - notify components to cleanup
|
||||
LogDebugMessage($"Broadcasting OnSceneUnloading for: {oldSceneName}");
|
||||
LifecycleManager.Instance?.BroadcastSceneUnloading(oldSceneName);
|
||||
|
||||
// PHASE 3: Broadcast save request - components save their level-specific data
|
||||
LogDebugMessage($"Broadcasting OnSaveRequested for: {oldSceneName}");
|
||||
LifecycleManager.Instance?.BroadcastSaveRequested(oldSceneName);
|
||||
|
||||
// PHASE 4: Trigger global save if save system is enabled
|
||||
if (SaveLoadManager.Instance != null)
|
||||
{
|
||||
var debugSettings = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>();
|
||||
if (debugSettings.useSaveLoadSystem)
|
||||
{
|
||||
LogDebugMessage("Saving global game state");
|
||||
SaveLoadManager.Instance.Save();
|
||||
}
|
||||
}
|
||||
|
||||
// PHASE 5: Remove all AstarPath (A* Pathfinder) singletons before loading the new scene
|
||||
var astarPaths = FindObjectsByType<AstarPath>(FindObjectsSortMode.None);
|
||||
foreach (var astar in astarPaths)
|
||||
{
|
||||
@@ -308,31 +333,41 @@ namespace Core
|
||||
else
|
||||
DestroyImmediate(astar.gameObject);
|
||||
}
|
||||
// Unload previous gameplay scene (if not BootstrapScene and not same as new)
|
||||
if (!string.IsNullOrEmpty(CurrentGameplayScene)&& CurrentGameplayScene != BootstrapSceneName)
|
||||
|
||||
// PHASE 6: Unload previous gameplay scene (Unity will call OnDestroy → OnManagedDestroy)
|
||||
if (!string.IsNullOrEmpty(oldSceneName) && oldSceneName != BootstrapSceneName)
|
||||
{
|
||||
var prevScene = SceneManager.GetSceneByName(CurrentGameplayScene);
|
||||
var prevScene = SceneManager.GetSceneByName(oldSceneName);
|
||||
if (prevScene.isLoaded)
|
||||
{
|
||||
await UnloadSceneAsync(CurrentGameplayScene);
|
||||
await UnloadSceneAsync(oldSceneName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warning($"[SceneManagerService] Previous scene '{CurrentGameplayScene}' is not loaded, skipping unload.");
|
||||
Logging.Warning($"[SceneManagerService] Previous scene '{oldSceneName}' is not loaded, skipping unload.");
|
||||
}
|
||||
}
|
||||
// Ensure BootstrapScene is loaded before loading new scene
|
||||
|
||||
// PHASE 7: 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
|
||||
|
||||
// PHASE 8: Load new gameplay scene
|
||||
await LoadSceneAsync(newSceneName, progress);
|
||||
// Update tracker
|
||||
CurrentGameplayScene = newSceneName;
|
||||
|
||||
// Only hide the loading screen if autoHideLoadingScreen is true
|
||||
// PHASE 9: Broadcast scene ready - components can now initialize scene-specific state
|
||||
LogDebugMessage($"Broadcasting OnSceneReady for: {newSceneName}");
|
||||
LifecycleManager.Instance?.BroadcastSceneReady(newSceneName);
|
||||
|
||||
// PHASE 10: Broadcast restore request - components restore their level-specific data
|
||||
LogDebugMessage($"Broadcasting OnRestoreRequested for: {newSceneName}");
|
||||
LifecycleManager.Instance?.BroadcastRestoreRequested(newSceneName);
|
||||
|
||||
// PHASE 11: Only hide the loading screen if autoHideLoadingScreen is true
|
||||
if (autoHideLoadingScreen && _loadingScreen != null)
|
||||
{
|
||||
_loadingScreen.HideLoadingScreen();
|
||||
|
||||
Reference in New Issue
Block a user