Files
AppleHillsProduction/Assets/Scripts/Bootstrap/BootSceneController.cs
2025-11-11 09:00:05 +01:00

286 lines
12 KiB
C#

using System;
using AppleHills.Core.Settings;
using UnityEngine;
using Core;
using Core.Lifecycle;
using UnityEngine.SceneManagement;
using Cinematics;
using Core.SaveLoad;
namespace Bootstrap
{
/// <summary>
/// Controller for the boot scene that coordinates bootstrap initialization with loading screen
/// </summary>
public class BootSceneController : ManagedBehaviour
{
[SerializeField] private string mainSceneName = "AppleHillsOverworld";
[SerializeField] private float minDelayAfterBoot = 0.5f; // Small delay after boot to ensure smooth transition
[SerializeField] private bool debugMode = false;
[SerializeField] private InitialLoadingScreen initialLoadingScreen; // Reference to our specialized loading screen
// Progress distribution between bootstrap and scene loading
[SerializeField, Range(0.1f, 0.9f)] private float bootProgressWeight = 0.5f; // Default 50/50 split
private enum LoadingPhase { Bootstrap, SceneLoading }
private LoadingPhase _currentPhase = LoadingPhase.Bootstrap;
private bool _bootComplete = false;
private bool _hasStartedLoading = false;
private float _sceneLoadingProgress = 0f;
private LogVerbosity _logVerbosity = LogVerbosity.Warning;
// Run very early - need to set up loading screen before other systems initialize
public override int ManagedAwakePriority => 5;
internal override void OnManagedAwake()
{
Logging.Debug("BootSceneController.Awake() - Initializing loading screen DURING bootstrap");
// Validate loading screen exists
if (initialLoadingScreen == null)
{
Debug.LogError("[BootSceneController] No InitialLoadingScreen assigned! Please assign it in the inspector.");
return;
}
// Show the loading screen immediately with our combined progress provider
// This needs to happen DURING bootstrap to show progress
initialLoadingScreen.ShowLoadingScreen(GetCombinedProgress);
// Subscribe to loading screen completion event
initialLoadingScreen.OnLoadingScreenFullyHidden += OnInitialLoadingComplete;
// Subscribe to boot progress for real-time updates during bootstrap
CustomBoot.OnBootProgressChanged += OnBootProgressChanged;
#if UNITY_EDITOR
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().bootstrapLogVerbosity;
#elif DEVELOPMENT_BUILD
_logVerbosity = LogVerbosity.Debug;
#elif RELEASE_BUILD
_logVerbosity = LogVerbosity.Warning;
#endif
// In debug mode, log additional information
if (debugMode)
{
InvokeRepeating(nameof(LogDebugInfo), 0.1f, 0.5f);
}
}
internal override void OnManagedStart()
{
Logging.Debug("BootSceneController.OnManagedStart() - Boot is GUARANTEED complete, starting scene loading");
// Boot is GUARANTEED complete at this point - that's the whole point of OnManagedStart!
// No need to subscribe to OnBootCompleted or check CustomBoot.Initialised
_bootComplete = true;
_currentPhase = LoadingPhase.SceneLoading;
// Start loading the main scene after a small delay
// This prevents jerky transitions if boot happens very quickly
Invoke(nameof(StartLoadingMainMenu), minDelayAfterBoot);
}
protected override void OnDestroy()
{
base.OnDestroy();
// Manual cleanup for events
if (initialLoadingScreen != null)
{
initialLoadingScreen.OnLoadingScreenFullyHidden -= OnInitialLoadingComplete;
}
CustomBoot.OnBootProgressChanged -= OnBootProgressChanged;
}
/// <summary>
/// Called when the initial loading screen is fully hidden
/// </summary>
private void OnInitialLoadingComplete()
{
Logging.Debug("Initial loading screen fully hidden, boot sequence completed");
// Play the intro cinematic if available
if (CinematicsManager.Instance != null)
{
Logging.Debug("Attempting to play intro cinematic");
// Use LoadAndPlayCinematic to play the intro sequence
CinematicsManager.Instance.LoadAndPlayCinematic("IntroSequence", false);
CinematicsManager.Instance.ChangeCinematicBackgroundColour(new Color(0f, 0f, 0f, 1f));
//PlayerHudManager.Instance.ResizeCinematicPlayer();
// Immediately unload the StartingScene - no need to wait for cinematic to finish
// since CinematicsManager is bootstrapped and won't be unloaded
UnloadStartingScene();
}
else
{
// If no cinematics manager, unload the StartingScene directly
UnloadStartingScene();
}
}
/// <summary>
/// Progress provider that combines bootstrap and scene loading progress
/// </summary>
private float GetCombinedProgress()
{
switch (_currentPhase)
{
case LoadingPhase.Bootstrap:
// Scale bootstrap progress from 0 to bootProgressWeight
return CustomBoot.CurrentProgress * bootProgressWeight;
case LoadingPhase.SceneLoading:
// Scale scene loading progress from bootProgressWeight to 1.0
return bootProgressWeight + (_sceneLoadingProgress * (1f - bootProgressWeight));
default:
return 0f;
}
}
private void OnBootProgressChanged(float progress)
{
if (debugMode)
{
Logging.Debug($"Bootstrap progress: {progress:P0}, Combined: {GetCombinedProgress():P0}");
}
}
private void LogDebugInfo()
{
Logging.Debug($"Debug - Phase: {_currentPhase}, Bootstrap: {CustomBoot.CurrentProgress:P0}, " +
$"Scene: {_sceneLoadingProgress:P0}, Combined: {GetCombinedProgress():P0}, Boot Complete: {_bootComplete}");
}
private void StartLoadingMainMenu()
{
if (_hasStartedLoading)
return;
_hasStartedLoading = true;
_currentPhase = LoadingPhase.SceneLoading;
LoadMainScene();
}
private async void LoadMainScene()
{
Logging.Debug($"Loading main menu scene: {mainSceneName}");
try
{
// Initialize scene loading progress to 0 to ensure proper remapping
_sceneLoadingProgress = 0f;
// Create a custom progress reporter using a custom class
var progressHandler = new ProgressHandler(value => {
// Store the raw scene loading progress (0-1)
_sceneLoadingProgress = value;
if (debugMode)
{
Logging.Debug($"Scene loading raw: {value:P0}, Combined: {GetCombinedProgress():P0}");
}
});
// Step 1: Additively load the main menu scene - don't unload StartingScene yet
var op = SceneManager.LoadSceneAsync(mainSceneName, LoadSceneMode.Additive);
// Disable scene activation until we're ready to show it
op.allowSceneActivation = true;
// Track progress while loading
while (!op.isDone)
{
progressHandler.ReportProgress(op.progress);
await System.Threading.Tasks.Task.Yield();
}
// Update the current gameplay scene in SceneManagerService
SceneManagerService.Instance.CurrentGameplayScene = mainSceneName;
// Ensure progress is complete
_sceneLoadingProgress = 1f;
// CRITICAL: Broadcast lifecycle events so components get their OnSceneReady callbacks
Logging.Debug($"Broadcasting OnSceneReady for: {mainSceneName}");
LifecycleManager.Instance?.BroadcastSceneReady(mainSceneName);
// Restore scene data for the main menu
if (SaveLoadManager.Instance != null)
{
Logging.Debug($"Restoring scene data for: {mainSceneName}");
SaveLoadManager.Instance.RestoreSceneData();
}
// Step 2: Scene is fully loaded, now hide the loading screen
// This will trigger OnInitialLoadingComplete via the event when animation completes
initialLoadingScreen.HideLoadingScreen();
// Step 3: The OnInitialLoadingComplete method will handle playing the intro cinematic
// Step 4: StartingScene will be unloaded after the cinematic completes in OnIntroCinematicFinished
}
catch (Exception e)
{
Debug.LogError($"[BootSceneController] Error loading main menu: {e.Message}");
// Still try to hide the loading screen even if there was an error
initialLoadingScreen.HideLoadingScreen();
}
}
/// <summary>
/// Unloads the StartingScene, completing the transition to the main menu
/// </summary>
private async void UnloadStartingScene()
{
try
{
// Get the current scene (StartingScene)
Scene currentScene = SceneManager.GetActiveScene();
string startingSceneName = currentScene.name;
Logging.Debug($"Unloading StartingScene: {startingSceneName}");
// Unload the StartingScene
await SceneManager.UnloadSceneAsync(startingSceneName);
// Set the main menu scene as the active scene
Scene mainMenuScene = SceneManager.GetSceneByName(mainSceneName);
SceneManager.SetActiveScene(mainMenuScene);
Logging.Debug($"Transition complete: {startingSceneName} unloaded, {mainSceneName} is now active");
// Destroy the boot scene controller since its job is done
Destroy(gameObject);
}
catch (Exception e)
{
Logging.Warning($"Error unloading StartingScene: {e.Message}");
}
}
/// <summary>
/// Helper class to handle progress reporting without running into explicit interface implementation issues
/// </summary>
private class ProgressHandler
{
private Action<float> _progressAction;
public ProgressHandler(Action<float> progressAction)
{
_progressAction = progressAction;
}
public void ReportProgress(float value)
{
_progressAction?.Invoke(value);
}
}
}
}