Revamp the prompt system, the bootstrapper system, the starting cinematic

This commit is contained in:
Michal Pikulski
2025-10-16 19:43:19 +02:00
parent df604fbc03
commit 50448c5bd3
89 changed files with 3964 additions and 677 deletions

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using AppleHills.Core.Interfaces;
using Core;
using UI;
using Bootstrap;
/// <summary>
/// Singleton manager for global game state and settings. Provides accessors for various gameplay parameters.
@@ -15,25 +16,9 @@ public class GameManager : MonoBehaviour
private static bool _isQuitting = false;
/// <summary>
/// Singleton instance of the GameManager.
/// Singleton instance of the GameManager. No longer creates an instance if one doesn't exist.
/// </summary>
public static GameManager Instance
{
get
{
if (_instance == null && Application.isPlaying && !_isQuitting)
{
_instance = FindAnyObjectByType<GameManager>();
if (_instance == null)
{
var go = new GameObject("GameManager");
_instance = go.AddComponent<GameManager>();
// DontDestroyOnLoad(go);
}
}
return _instance;
}
}
public static GameManager Instance => _instance;
[Header("Settings Status")]
[SerializeField] private bool _settingsLoaded = false;
@@ -70,10 +55,13 @@ public class GameManager : MonoBehaviour
InitializeSettings();
InitializeDeveloperSettings();
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
// DontDestroyOnLoad(gameObject);
}
private void Start()
private void InitializePostBoot()
{
// Find and subscribe to PauseMenu events
PauseMenu pauseMenu = PauseMenu.Instance;

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using UnityEngine;
using Interactions;
using Bootstrap;
namespace Core
{
@@ -14,23 +15,10 @@ namespace Core
private static ItemManager _instance;
private static bool _isQuitting;
public static ItemManager Instance
{
get
{
if (_instance == null && Application.isPlaying && !_isQuitting)
{
_instance = FindAnyObjectByType<ItemManager>();
if (_instance == null)
{
var go = new GameObject("ItemManager");
_instance = go.AddComponent<ItemManager>();
// DontDestroyOnLoad(go);
}
}
return _instance;
}
}
/// <summary>
/// Singleton instance of the ItemManager. No longer creates an instance if one doesn't exist.
/// </summary>
public static ItemManager Instance => _instance;
private readonly HashSet<Pickup> _pickups = new HashSet<Pickup>();
private readonly HashSet<ItemSlot> _itemSlots = new HashSet<ItemSlot>();
@@ -63,13 +51,16 @@ namespace Core
void Awake()
{
_instance = this;
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
}
void Start()
private void InitializePostBoot()
{
// Subscribe to scene load completed so we can clear registrations when scenes change
// Access Instance directly to ensure the service is initialized and we get the event hookup.
SceneManagerService.Instance.SceneLoadStarted += OnSceneLoadStarted;
Logging.Debug("[ItemManager] Subscribed to SceneManagerService events");
}
void OnDestroy()

View File

@@ -18,23 +18,11 @@ namespace AppleHills.Core
private static QuickAccess _instance;
private static bool _isQuitting = false;
public static QuickAccess Instance
{
get
{
if (_instance == null && Application.isPlaying && !_isQuitting)
{
_instance = FindAnyObjectByType<QuickAccess>();
if (_instance == null)
{
var go = new GameObject("QuickAccess");
_instance = go.AddComponent<QuickAccess>();
}
}
return _instance;
}
}
/// <summary>
/// Singleton instance of QuickAccess. No longer creates an instance if one doesn't exist.
/// </summary>
public static QuickAccess Instance => _instance;
void OnApplicationQuit()
{
_isQuitting = true;
@@ -146,6 +134,8 @@ namespace AppleHills.Core
private void Awake()
{
_instance = this;
if (!_initialized)
{
// Subscribe to scene changes

View File

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using UI;
using UnityEngine;
using UnityEngine.SceneManagement;
using Bootstrap;
namespace Core
{
@@ -15,26 +16,11 @@ namespace Core
private LoadingScreenController _loadingScreen;
private static SceneManagerService _instance;
private static bool _isQuitting = false;
/// <summary>
/// Singleton instance of the SceneManagerService.
/// Singleton instance of the SceneManagerService. No longer creates an instance if one doesn't exist.
/// </summary>
public static SceneManagerService Instance
{
get
{
if (_instance == null && Application.isPlaying && !_isQuitting)
{
_instance = FindAnyObjectByType<SceneManagerService>();
if (_instance == null)
{
var go = new GameObject("SceneManagerService");
_instance = go.AddComponent<SceneManagerService>();
// DontDestroyOnLoad(go);
}
}
return _instance;
}
}
public static SceneManagerService Instance => _instance;
// Events for scene lifecycle
public event Action<string> SceneLoadStarted;
@@ -48,30 +34,17 @@ namespace Core
private readonly Dictionary<string, AsyncOperation> _activeUnloads = new();
private const string BootstrapSceneName = "BootstrapScene";
void Start()
{
_loadingScreen = LoadingScreenController.Instance;
// Set up loading screen event handlers
SetupLoadingScreenEvents();
}
void Awake()
{
_instance = this;
// DontDestroyOnLoad(gameObject);
#if UNITY_EDITOR
// In Editor, set CurrentGameplayScene to the currently open scene at play start
if (Application.isPlaying)
{
var activeScene = SceneManager.GetActiveScene();
if (activeScene.IsValid())
{
CurrentGameplayScene = activeScene.name;
}
}
#endif
// 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)
@@ -79,6 +52,48 @@ namespace Core
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 = "MainMenu";
Logging.Debug($"[SceneManagerService] Initialized with default scene: {CurrentGameplayScene}");
}
}
else
{
CurrentGameplayScene = "MainMenu";
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()
{
@@ -262,16 +277,27 @@ namespace Core
}
// Tracks the currently loaded gameplay scene (not persistent/bootstrapper)
public string CurrentGameplayScene { get; private set; } = "MainMenu";
public string CurrentGameplayScene { get; set; } = "MainMenu";
public async Task ReloadCurrentScene(IProgress<float> progress = null)
public async Task ReloadCurrentScene(IProgress<float> progress = null, bool autoHideLoadingScreen = true)
{
await SwitchSceneAsync(CurrentGameplayScene, progress);
await SwitchSceneAsync(CurrentGameplayScene, progress, autoHideLoadingScreen);
}
// Switches from current gameplay scene to a new one
public async Task SwitchSceneAsync(string newSceneName, IProgress<float> progress = null)
/// <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)
@@ -304,6 +330,12 @@ namespace Core
await LoadSceneAsync(newSceneName, progress);
// Update tracker
CurrentGameplayScene = newSceneName;
// Only hide the loading screen if autoHideLoadingScreen is true
if (autoHideLoadingScreen && _loadingScreen != null)
{
_loadingScreen.HideLoadingScreen();
}
}
}
}

View File

@@ -5,6 +5,8 @@ using Input;
using Settings;
using System.Collections;
using Minigames.DivingForPictures;
using Bootstrap;
using Core;
namespace Utility
{
@@ -12,23 +14,11 @@ namespace Utility
{
private static SceneOrientationEnforcer _instance;
private static bool _isQuitting;
public static SceneOrientationEnforcer Instance
{
get
{
if (_instance == null && Application.isPlaying && !_isQuitting)
{
_instance = FindAnyObjectByType<SceneOrientationEnforcer>();
if (_instance == null)
{
var go = new GameObject("SceneOrientationEnforcer");
_instance = go.AddComponent<SceneOrientationEnforcer>();
// DontDestroyOnLoad(go); // Uncomment if you want persistence
}
}
return _instance;
}
}
/// <summary>
/// Singleton instance of the SceneOrientationEnforcer. No longer creates an instance if one doesn't exist.
/// </summary>
public static SceneOrientationEnforcer Instance => _instance;
[Header("Config")]
public SceneOrientationConfig orientationConfig;
@@ -48,10 +38,16 @@ namespace Utility
{
_instance = this;
OnOrientationCorrect += HandleOrientationCorrect;
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
}
void Start()
private void InitializePostBoot()
{
// Initialize any dependencies that require other services to be ready
Logging.Debug("[SceneOrientationEnforcer] Post-boot initialization complete");
// Subscribe to sceneLoaded event
SceneManager.sceneLoaded += OnSceneLoaded;
// Manually invoke for the first scene (unless it's Main Menu)
@@ -74,6 +70,8 @@ namespace Utility
return Screen.orientation == ScreenOrientation.Portrait || Screen.orientation == ScreenOrientation.PortraitUpsideDown;
case ScreenOrientationRequirement.Landscape:
return Screen.orientation == ScreenOrientation.LandscapeLeft || Screen.orientation == ScreenOrientation.LandscapeRight;
case ScreenOrientationRequirement.NotApplicable:
return true;
default:
return true;
}
@@ -98,7 +96,7 @@ namespace Utility
_isDivingMinigame = IsDivingMinigameScene(scene);
_requiredOrientation = orientationConfig != null ? orientationConfig.GetRequirementForScene(scene.name) : ScreenOrientationRequirement.Portrait;
_requiredOrientation = orientationConfig != null ? orientationConfig.GetRequirementForScene(scene.name) : ScreenOrientationRequirement.NotApplicable;
_orientationCorrect = IsOrientationCorrect();
if (!_orientationCorrect)
@@ -186,7 +184,7 @@ namespace Utility
_orientationCheckCoroutine = null;
}
InputManager.Instance.SetInputMode(InputMode.Game);
InputManager.Instance.SetInputMode(InputMode.GameAndUI);
}
private void CleanupPromptAndCoroutine()

View File

@@ -24,7 +24,7 @@ namespace AppleHills.Core.Settings
if (_instance == null && Application.isPlaying)
{
_instance = FindFirstObjectByType<DeveloperSettingsProvider>();
if (_instance == null)
{
GameObject go = new GameObject("DeveloperSettingsProvider");
@@ -33,7 +33,7 @@ namespace AppleHills.Core.Settings
DontDestroyOnLoad(go);
}
}
return _instance;
}
}