## ManagedBehaviour System Refactor - **Sealed `Awake()`** to prevent override mistakes that break singleton registration - **Added `OnManagedAwake()`** for early initialization (fires during registration) - **Renamed lifecycle hook:** `OnManagedAwake()` → `OnManagedStart()` (fires after boot, mirrors Unity's Awake→Start) - **40 files migrated** to new pattern (2 core, 38 components) - Eliminated all fragile `private new void Awake()` patterns - Zero breaking changes - backward compatible ## Centralized Logging System - **Automatic tagging** via `CallerMemberName` and `CallerFilePath` - logs auto-tagged as `[ClassName][MethodName] message` - **Unified API:** Single `Logging.Debug/Info/Warning/Error()` replaces custom `LogDebugMessage()` implementations - **~90 logging call sites** migrated across 10 files - **10 redundant helper methods** removed - All logs broadcast via `Logging.OnLogEntryAdded` event for real-time monitoring ## Custom Log Console (Editor Window) - **Persistent filter popups** for multi-selection (classes, methods, log levels) - windows stay open during selection - **Search** across class names, methods, and message content - **Time range filter** with MinMaxSlider - **Export** filtered logs to timestamped `.txt` files - **Right-click context menu** for quick filtering and copy actions - **Visual improvements:** White text, alternating row backgrounds, color-coded log levels - **Multiple instances** supported for simultaneous system monitoring - Open via `AppleHills > Custom Log Console` Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Reviewed-on: #56
286 lines
12 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|