Revamp the prompt system, the bootstrapper system, the starting cinematic
This commit is contained in:
150
Assets/Scripts/Bootstrap/BootCompletionService.cs
Normal file
150
Assets/Scripts/Bootstrap/BootCompletionService.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Bootstrap
|
||||
{
|
||||
/// <summary>
|
||||
/// Service that provides notification and management of boot completion status.
|
||||
/// Allows systems to subscribe to boot completion events, register initialization actions with priorities,
|
||||
/// or await boot completion asynchronously.
|
||||
/// </summary>
|
||||
public static class BootCompletionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates if the boot process has completed
|
||||
/// </summary>
|
||||
public static bool IsBootComplete { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when boot completes
|
||||
/// </summary>
|
||||
public static event Action OnBootComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an initialization action with priority
|
||||
/// </summary>
|
||||
private class InitializationAction
|
||||
{
|
||||
public Action Action { get; }
|
||||
public int Priority { get; }
|
||||
public string Name { get; }
|
||||
|
||||
public InitializationAction(Action action, int priority, string name)
|
||||
{
|
||||
Action = action;
|
||||
Priority = priority;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
// List of initialization actions to be executed once boot completes
|
||||
private static List<InitializationAction> _initializationActions = new List<InitializationAction>();
|
||||
|
||||
// TaskCompletionSource for async await pattern
|
||||
private static TaskCompletionSource<bool> _bootCompletionTask = new TaskCompletionSource<bool>();
|
||||
|
||||
/// <summary>
|
||||
/// Called by CustomBoot when the boot process is complete
|
||||
/// </summary>
|
||||
internal static void HandleBootCompleted()
|
||||
{
|
||||
if (IsBootComplete)
|
||||
return;
|
||||
|
||||
IsBootComplete = true;
|
||||
|
||||
Debug.Log("[BootCompletionService] Boot process completed, executing initialization actions");
|
||||
|
||||
// Execute initialization actions in priority order (lower number = higher priority)
|
||||
ExecuteInitializationActions();
|
||||
|
||||
// Trigger the event
|
||||
OnBootComplete?.Invoke();
|
||||
|
||||
// Complete the task for async waiters
|
||||
_bootCompletionTask.TrySetResult(true);
|
||||
|
||||
Debug.Log("[BootCompletionService] All boot completion handlers executed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an action to be executed when boot completes.
|
||||
/// Lower priority numbers run first.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to execute</param>
|
||||
/// <param name="priority">Priority (lower numbers run first)</param>
|
||||
/// <param name="name">Name for debugging</param>
|
||||
public static void RegisterInitAction(Action action, int priority = 100, string name = null)
|
||||
{
|
||||
if (action == null)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
name = $"Action_{_initializationActions.Count}";
|
||||
|
||||
var initAction = new InitializationAction(action, priority, name);
|
||||
|
||||
if (IsBootComplete)
|
||||
{
|
||||
// If boot is already complete, execute immediately
|
||||
Debug.Log($"[BootCompletionService] Executing late registration: {name} (Priority: {priority})");
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[BootCompletionService] Error executing init action '{name}': {ex}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise add to the queue
|
||||
_initializationActions.Add(initAction);
|
||||
Debug.Log($"[BootCompletionService] Registered init action: {name} (Priority: {priority})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait asynchronously for boot completion
|
||||
/// </summary>
|
||||
/// <returns>Task that completes when boot is complete</returns>
|
||||
public static Task WaitForBootCompletionAsync()
|
||||
{
|
||||
if (IsBootComplete)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return _bootCompletionTask.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute all registered initialization actions in priority order
|
||||
/// </summary>
|
||||
private static void ExecuteInitializationActions()
|
||||
{
|
||||
// Sort by priority (lowest first)
|
||||
var sortedActions = _initializationActions
|
||||
.OrderBy(a => a.Priority)
|
||||
.ToList();
|
||||
|
||||
foreach (var action in sortedActions)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.Log($"[BootCompletionService] Executing: {action.Name} (Priority: {action.Priority})");
|
||||
action.Action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[BootCompletionService] Error executing init action '{action.Name}': {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the list after execution
|
||||
_initializationActions.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Bootstrap/BootCompletionService.cs.meta
Normal file
3
Assets/Scripts/Bootstrap/BootCompletionService.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa0228cf33a64515bc166b7a9bc8c0b9
|
||||
timeCreated: 1760606319
|
||||
269
Assets/Scripts/Bootstrap/BootSceneController.cs
Normal file
269
Assets/Scripts/Bootstrap/BootSceneController.cs
Normal file
@@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UI;
|
||||
using Core;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Cinematics;
|
||||
|
||||
namespace Bootstrap
|
||||
{
|
||||
/// <summary>
|
||||
/// Controller for the boot scene that coordinates bootstrap initialization with loading screen
|
||||
/// </summary>
|
||||
public class BootSceneController : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private string mainMenuSceneName = "MainMenu";
|
||||
[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 void Start()
|
||||
{
|
||||
Debug.Log("[BootSceneController] Boot scene started");
|
||||
|
||||
// Ensure the initial loading screen exists
|
||||
if (initialLoadingScreen == null)
|
||||
{
|
||||
Debug.LogError("[BootSceneController] No InitialLoadingScreen assigned! Please assign it in the inspector.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Subscribe to the loading screen completion event
|
||||
initialLoadingScreen.OnLoadingScreenFullyHidden += OnInitialLoadingComplete;
|
||||
|
||||
// Show the loading screen immediately with our combined progress provider
|
||||
initialLoadingScreen.ShowLoadingScreen(GetCombinedProgress);
|
||||
|
||||
// Subscribe to boot progress events
|
||||
CustomBoot.OnBootProgressChanged += OnBootProgressChanged;
|
||||
|
||||
// Register our boot completion handler with the BootCompletionService
|
||||
// This will execute either immediately if boot is already complete,
|
||||
// or when the boot process completes
|
||||
BootCompletionService.RegisterInitAction(
|
||||
OnBootCompleted,
|
||||
50, // Higher priority (lower number)
|
||||
"BootSceneController.OnBootCompleted"
|
||||
);
|
||||
|
||||
// In debug mode, log additional information
|
||||
if (debugMode)
|
||||
{
|
||||
InvokeRepeating(nameof(LogDebugInfo), 0.1f, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the initial loading screen is fully hidden
|
||||
/// </summary>
|
||||
private void OnInitialLoadingComplete()
|
||||
{
|
||||
Debug.Log("[BootSceneController] Initial loading screen fully hidden, boot sequence completed");
|
||||
|
||||
// Play the intro cinematic if available
|
||||
if (CinematicsManager.Instance != null)
|
||||
{
|
||||
Debug.Log("[BootSceneController] Attempting to play intro cinematic");
|
||||
|
||||
// Use LoadAndPlayCinematic to play the intro sequence
|
||||
CinematicsManager.Instance.LoadAndPlayCinematic("IntroSequence");
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Clean up event subscriptions
|
||||
CustomBoot.OnBootCompleted -= OnBootCompleted;
|
||||
CustomBoot.OnBootProgressChanged -= OnBootProgressChanged;
|
||||
|
||||
if (initialLoadingScreen != null)
|
||||
{
|
||||
initialLoadingScreen.OnLoadingScreenFullyHidden -= OnInitialLoadingComplete;
|
||||
}
|
||||
|
||||
if (debugMode)
|
||||
{
|
||||
CancelInvoke(nameof(LogDebugInfo));
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
Debug.Log($"[BootSceneController] Bootstrap progress: {progress:P0}, Combined: {GetCombinedProgress():P0}");
|
||||
}
|
||||
}
|
||||
|
||||
private void LogDebugInfo()
|
||||
{
|
||||
Debug.Log($"[BootSceneController] Debug - Phase: {_currentPhase}, Bootstrap: {CustomBoot.CurrentProgress:P0}, " +
|
||||
$"Scene: {_sceneLoadingProgress:P0}, Combined: {GetCombinedProgress():P0}, Boot Complete: {_bootComplete}");
|
||||
}
|
||||
|
||||
private void OnBootCompleted()
|
||||
{
|
||||
// Unsubscribe to prevent duplicate calls
|
||||
CustomBoot.OnBootCompleted -= OnBootCompleted;
|
||||
|
||||
Debug.Log("[BootSceneController] Boot process completed");
|
||||
_bootComplete = true;
|
||||
|
||||
// After a small delay, start loading the main menu
|
||||
// This prevents jerky transitions if boot happens very quickly
|
||||
Invoke(nameof(StartLoadingMainMenu), minDelayAfterBoot);
|
||||
}
|
||||
|
||||
private void StartLoadingMainMenu()
|
||||
{
|
||||
if (_hasStartedLoading)
|
||||
return;
|
||||
|
||||
_hasStartedLoading = true;
|
||||
_currentPhase = LoadingPhase.SceneLoading;
|
||||
LoadMainMenu();
|
||||
}
|
||||
|
||||
private async void LoadMainMenu()
|
||||
{
|
||||
Debug.Log($"[BootSceneController] Loading main menu scene: {mainMenuSceneName}");
|
||||
|
||||
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)
|
||||
{
|
||||
Debug.Log($"[BootSceneController] 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(mainMenuSceneName, 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 = mainMenuSceneName;
|
||||
|
||||
// Ensure progress is complete
|
||||
_sceneLoadingProgress = 1f;
|
||||
|
||||
// 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;
|
||||
|
||||
Debug.Log($"[BootSceneController] Unloading StartingScene: {startingSceneName}");
|
||||
|
||||
// Unload the StartingScene
|
||||
await SceneManager.UnloadSceneAsync(startingSceneName);
|
||||
|
||||
// Set the main menu scene as the active scene
|
||||
Scene mainMenuScene = SceneManager.GetSceneByName(mainMenuSceneName);
|
||||
SceneManager.SetActiveScene(mainMenuScene);
|
||||
|
||||
Debug.Log($"[BootSceneController] Transition complete: {startingSceneName} unloaded, {mainMenuSceneName} is now active");
|
||||
|
||||
// Destroy the boot scene controller since its job is done
|
||||
Destroy(gameObject);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Warning($"[BootSceneController] 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Bootstrap/BootSceneController.cs.meta
Normal file
3
Assets/Scripts/Bootstrap/BootSceneController.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdb797d6fcdc469bb9bfb9ad3c5f51b5
|
||||
timeCreated: 1760604860
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
@@ -14,9 +15,24 @@ namespace Bootstrap
|
||||
/// Current initialisation status
|
||||
/// </summary>
|
||||
public static bool Initialised { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when boot progress changes
|
||||
/// </summary>
|
||||
public static event Action<float> OnBootProgressChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when boot process completes
|
||||
/// </summary>
|
||||
public static event Action OnBootCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Current progress of the boot process (0-1)
|
||||
/// </summary>
|
||||
public static float CurrentProgress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
// Called as soon as the game begins
|
||||
/// Called as soon as the game begins
|
||||
/// </summary>
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)]
|
||||
private static void Initialise()
|
||||
@@ -32,6 +48,9 @@ namespace Bootstrap
|
||||
/// </summary>
|
||||
public static void PerformInitialisation()
|
||||
{
|
||||
//Reset progress
|
||||
CurrentProgress = 0f;
|
||||
|
||||
//In editor, perform initialisation synchronously
|
||||
if (Application.isEditor)
|
||||
{
|
||||
@@ -72,6 +91,17 @@ namespace Bootstrap
|
||||
{
|
||||
await LoadCustomBootSettings();
|
||||
Initialised = true;
|
||||
CurrentProgress = 1f;
|
||||
OnBootProgressChanged?.Invoke(1f);
|
||||
OnBootCompleted?.Invoke();
|
||||
|
||||
// Notify the BootCompletionService that boot is complete
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
// Direct call to boot completion service
|
||||
Debug.Log("[CustomBoot] Calling BootCompletionService.HandleBootCompleted()");
|
||||
BootCompletionService.HandleBootCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,6 +111,17 @@ namespace Bootstrap
|
||||
{
|
||||
LoadCustomBootSettingsSync();
|
||||
Initialised = true;
|
||||
CurrentProgress = 1f;
|
||||
OnBootProgressChanged?.Invoke(1f);
|
||||
OnBootCompleted?.Invoke();
|
||||
|
||||
// Notify the BootCompletionService that boot is complete
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
// Direct call to boot completion service
|
||||
Debug.Log("[CustomBoot] Calling BootCompletionService.HandleBootCompleted()");
|
||||
BootCompletionService.HandleBootCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -177,5 +218,16 @@ namespace Bootstrap
|
||||
result.InitialiseSync();
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the current progress value and triggers the progress event
|
||||
/// </summary>
|
||||
/// <param name="progress">Progress value between 0-1</param>
|
||||
internal static void UpdateProgress(float progress)
|
||||
{
|
||||
CurrentProgress = Mathf.Clamp01(progress);
|
||||
OnBootProgressChanged?.Invoke(CurrentProgress);
|
||||
Debug.Log($"[CustomBoot] Progress: {CurrentProgress:P0}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,15 +31,37 @@ namespace Bootstrap
|
||||
RuntimeContainer = new GameObject($"{name}_Container");
|
||||
DontDestroyOnLoad(RuntimeContainer);
|
||||
Instances = new GameObject[BootPrefabs.Length];
|
||||
|
||||
// Calculate total prefabs for progress tracking
|
||||
int totalPrefabs = BootPrefabs.Length;
|
||||
float progressPerPrefab = totalPrefabs > 0 ? 1f / totalPrefabs : 1f;
|
||||
float currentProgress = 0f;
|
||||
|
||||
for (var i = 0; i < BootPrefabs.Length; i++)
|
||||
{
|
||||
if (!BootPrefabs[i]) continue;
|
||||
if (!BootPrefabs[i])
|
||||
{
|
||||
// Report incremental progress even for null prefabs
|
||||
currentProgress = (i + 1) * progressPerPrefab;
|
||||
CustomBoot.UpdateProgress(currentProgress);
|
||||
continue;
|
||||
}
|
||||
|
||||
var instance = GameObject.InstantiateAsync(BootPrefabs[i], RuntimeContainer.transform);
|
||||
while (!instance.isDone)
|
||||
{
|
||||
// Report partial progress while waiting
|
||||
float progressInStep = instance.progress * progressPerPrefab;
|
||||
float overallProgress = i * progressPerPrefab + progressInStep;
|
||||
CustomBoot.UpdateProgress(overallProgress);
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
Instances[i] = instance.Result[0];
|
||||
|
||||
// Report completion of this step
|
||||
currentProgress = (i + 1) * progressPerPrefab;
|
||||
CustomBoot.UpdateProgress(currentProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,12 +77,28 @@ namespace Bootstrap
|
||||
}
|
||||
|
||||
Instances = new GameObject[BootPrefabs.Length];
|
||||
|
||||
// Calculate total prefabs for progress tracking
|
||||
int totalPrefabs = BootPrefabs.Length;
|
||||
float progressPerPrefab = totalPrefabs > 0 ? 1f / totalPrefabs : 1f;
|
||||
float currentProgress = 0f;
|
||||
|
||||
for (var i = 0; i < BootPrefabs.Length; i++)
|
||||
{
|
||||
if (!BootPrefabs[i]) continue;
|
||||
if (!BootPrefabs[i])
|
||||
{
|
||||
// Report incremental progress even for null prefabs
|
||||
currentProgress = (i + 1) * progressPerPrefab;
|
||||
CustomBoot.UpdateProgress(currentProgress);
|
||||
continue;
|
||||
}
|
||||
|
||||
var instance = GameObject.Instantiate(BootPrefabs[i], RuntimeContainer.transform);
|
||||
Instances[i] = instance;
|
||||
|
||||
// Report completion of this step
|
||||
currentProgress = (i + 1) * progressPerPrefab;
|
||||
CustomBoot.UpdateProgress(currentProgress);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
240
Assets/Scripts/Bootstrap/InitialLoadingScreen.cs
Normal file
240
Assets/Scripts/Bootstrap/InitialLoadingScreen.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Core;
|
||||
|
||||
namespace Bootstrap
|
||||
{
|
||||
/// <summary>
|
||||
/// Specialized loading screen controller specifically for the initial boot sequence.
|
||||
/// This handles the combined progress of bootstrap initialization and main menu loading.
|
||||
/// </summary>
|
||||
public class InitialLoadingScreen : MonoBehaviour
|
||||
{
|
||||
[Header("UI References")]
|
||||
[SerializeField] private GameObject loadingScreenContainer;
|
||||
[SerializeField] private Image progressBarImage;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private float minimumDisplayTime = 1.0f;
|
||||
[SerializeField] private float progressUpdateInterval = 0.1f;
|
||||
|
||||
private float _displayStartTime;
|
||||
private Coroutine _progressCoroutine;
|
||||
private bool _loadingComplete = false;
|
||||
private bool _animationComplete = false;
|
||||
private Action _onLoadingScreenFullyHidden;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when the loading screen is fully hidden (both loading and animation completed)
|
||||
/// </summary>
|
||||
public event Action OnLoadingScreenFullyHidden;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for providing progress values from different sources
|
||||
/// </summary>
|
||||
public delegate float ProgressProvider();
|
||||
|
||||
/// <summary>
|
||||
/// Current progress provider being used for the loading screen
|
||||
/// </summary>
|
||||
private ProgressProvider _currentProgressProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Default progress provider that returns 0 (or 1 if loading is complete)
|
||||
/// </summary>
|
||||
private float DefaultProgressProvider() => _loadingComplete ? 1f : 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the loading screen is currently active
|
||||
/// </summary>
|
||||
public bool IsActive => loadingScreenContainer != null && loadingScreenContainer.activeSelf;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (loadingScreenContainer == null)
|
||||
loadingScreenContainer = gameObject;
|
||||
|
||||
// Ensure the loading screen is initially hidden
|
||||
if (loadingScreenContainer != null)
|
||||
{
|
||||
loadingScreenContainer.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the loading screen and resets the progress bar to zero
|
||||
/// </summary>
|
||||
/// <param name="progressProvider">Optional delegate to provide progress values (0-1). If null, uses default provider.</param>
|
||||
/// <param name="onComplete">Optional callback when loading screen is fully hidden</param>
|
||||
public void ShowLoadingScreen(ProgressProvider progressProvider = null, Action onComplete = null)
|
||||
{
|
||||
// Store the completion callback
|
||||
_onLoadingScreenFullyHidden = onComplete;
|
||||
|
||||
// Set the progress provider, use default if none provided
|
||||
_currentProgressProvider = progressProvider ?? DefaultProgressProvider;
|
||||
|
||||
// Stop any existing progress coroutine
|
||||
if (_progressCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_progressCoroutine);
|
||||
_progressCoroutine = null;
|
||||
}
|
||||
|
||||
_displayStartTime = Time.time;
|
||||
_loadingComplete = false;
|
||||
_animationComplete = false;
|
||||
|
||||
if (progressBarImage != null)
|
||||
{
|
||||
progressBarImage.fillAmount = 0f;
|
||||
}
|
||||
|
||||
if (loadingScreenContainer != null)
|
||||
{
|
||||
loadingScreenContainer.SetActive(true);
|
||||
}
|
||||
|
||||
// Start the progress filling coroutine
|
||||
_progressCoroutine = StartCoroutine(AnimateProgressBar());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animates the progress bar at a steady pace over the minimum display time,
|
||||
/// while also checking actual loading progress from the current progress provider
|
||||
/// </summary>
|
||||
private IEnumerator AnimateProgressBar()
|
||||
{
|
||||
float startTime = Time.time;
|
||||
|
||||
// Continue until both animation and loading are complete
|
||||
while (!_animationComplete)
|
||||
{
|
||||
// Calculate the steady progress based on elapsed time
|
||||
float elapsedTime = Time.time - startTime;
|
||||
float steadyProgress = Mathf.Clamp01(elapsedTime / minimumDisplayTime);
|
||||
|
||||
// Get the actual loading progress from the current provider
|
||||
float actualProgress = _currentProgressProvider();
|
||||
|
||||
// If loading is complete, actualProgress should be 1.0
|
||||
if (_loadingComplete)
|
||||
{
|
||||
actualProgress = 1.0f;
|
||||
}
|
||||
|
||||
// Use the minimum of steady progress and actual progress
|
||||
// This ensures we don't show more progress than actual loading
|
||||
float displayProgress = Mathf.Min(steadyProgress, actualProgress);
|
||||
|
||||
// Log the progress values for debugging
|
||||
Debug.Log($"[InitialLoadingScreen] Progress - Default: {steadyProgress:F2}, Actual: {actualProgress:F2}, Display: {displayProgress:F2}");
|
||||
|
||||
// Directly set the progress bar fill amount without smoothing
|
||||
if (progressBarImage != null)
|
||||
{
|
||||
progressBarImage.fillAmount = displayProgress;
|
||||
}
|
||||
|
||||
// Check if the animation has completed
|
||||
// Animation is complete when we've reached the minimum display time AND we're at 100% progress
|
||||
if (steadyProgress >= 1.0f && displayProgress >= 1.0f)
|
||||
{
|
||||
_animationComplete = true;
|
||||
Debug.Log("[InitialLoadingScreen] Animation complete");
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait for the configured interval before updating again
|
||||
yield return new WaitForSeconds(progressUpdateInterval);
|
||||
}
|
||||
|
||||
// Ensure we end at 100% progress
|
||||
if (progressBarImage != null)
|
||||
{
|
||||
progressBarImage.fillAmount = 1.0f;
|
||||
Debug.Log("[InitialLoadingScreen] Final progress set to 1.0");
|
||||
}
|
||||
|
||||
// Hide the screen if loading is also complete
|
||||
if (_loadingComplete)
|
||||
{
|
||||
if (loadingScreenContainer != null)
|
||||
{
|
||||
loadingScreenContainer.SetActive(false);
|
||||
Debug.Log("[InitialLoadingScreen] Animation AND loading complete, hiding screen");
|
||||
|
||||
// Invoke the callback when fully hidden
|
||||
_onLoadingScreenFullyHidden?.Invoke();
|
||||
OnLoadingScreenFullyHidden?.Invoke();
|
||||
_onLoadingScreenFullyHidden = null;
|
||||
}
|
||||
}
|
||||
|
||||
_progressCoroutine = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the actual loading process is complete
|
||||
/// </summary>
|
||||
public void HideLoadingScreen()
|
||||
{
|
||||
Debug.Log("[InitialLoadingScreen] Loading complete, marking loading as finished");
|
||||
|
||||
// Mark that loading is complete
|
||||
_loadingComplete = true;
|
||||
|
||||
// If animation is already complete, we can hide the screen now
|
||||
if (_animationComplete)
|
||||
{
|
||||
if (loadingScreenContainer != null)
|
||||
{
|
||||
loadingScreenContainer.SetActive(false);
|
||||
Debug.Log("[InitialLoadingScreen] Animation already complete, hiding screen immediately");
|
||||
|
||||
// Invoke the callback when fully hidden
|
||||
_onLoadingScreenFullyHidden?.Invoke();
|
||||
OnLoadingScreenFullyHidden?.Invoke();
|
||||
_onLoadingScreenFullyHidden = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[InitialLoadingScreen] Animation still in progress, waiting for it to complete");
|
||||
// The coroutine will handle hiding when animation completes
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits until the loading screen is fully hidden before continuing
|
||||
/// </summary>
|
||||
/// <returns>Task that completes when the loading screen is hidden</returns>
|
||||
public System.Threading.Tasks.Task WaitForLoadingScreenToHideAsync()
|
||||
{
|
||||
var tcs = new System.Threading.Tasks.TaskCompletionSource<bool>();
|
||||
|
||||
// If the loading screen is not active, complete immediately
|
||||
if (!IsActive)
|
||||
{
|
||||
tcs.SetResult(true);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
// Store existing callback to chain it
|
||||
Action existingCallback = _onLoadingScreenFullyHidden;
|
||||
|
||||
// Set new callback
|
||||
_onLoadingScreenFullyHidden = () => {
|
||||
// Call existing callback if any
|
||||
existingCallback?.Invoke();
|
||||
|
||||
// Complete the task
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Bootstrap/InitialLoadingScreen.cs.meta
Normal file
3
Assets/Scripts/Bootstrap/InitialLoadingScreen.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8968b564891a474baae157792b88e75f
|
||||
timeCreated: 1760613320
|
||||
Reference in New Issue
Block a user