Merge branch 'main' into DamianBranch

This commit is contained in:
2025-10-13 10:17:54 +00:00
24 changed files with 4634 additions and 2074 deletions

View File

@@ -1,12 +1,8 @@
using UnityEngine;
using Interactions;
using System.Collections.Generic;
using AppleHills.Core.Settings;
using AppleHills.Data.CardSystem;
using CinematicsM;
using Cinematics;
using Core;
using Input;
using Minigames.DivingForPictures;
using PuzzleS;
namespace AppleHills.Core

View File

@@ -1,255 +1,299 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UI;
using UnityEngine;
using UnityEngine.SceneManagement;
/// <summary>
/// Singleton service for loading and unloading Unity scenes asynchronously, with events for progress and completion.
/// </summary>
public class SceneManagerService : MonoBehaviour
namespace Core
{
private static SceneManagerService _instance;
private static bool _isQuitting = false;
/// <summary>
/// Singleton instance of the SceneManagerService.
/// Singleton service for loading and unloading Unity scenes asynchronously, with events for progress and completion.
/// </summary>
public static SceneManagerService Instance
public class SceneManagerService : MonoBehaviour
{
get
[SerializeField] private LoadingScreenController loadingScreen;
private static SceneManagerService _instance;
private static bool _isQuitting = false;
/// <summary>
/// Singleton instance of the SceneManagerService.
/// </summary>
public static SceneManagerService Instance
{
if (_instance == null && Application.isPlaying && !_isQuitting)
get
{
_instance = FindAnyObjectByType<SceneManagerService>();
if (_instance == null)
if (_instance == null && Application.isPlaying && !_isQuitting)
{
var go = new GameObject("SceneManagerService");
_instance = go.AddComponent<SceneManagerService>();
// DontDestroyOnLoad(go);
_instance = FindAnyObjectByType<SceneManagerService>();
if (_instance == null)
{
var go = new GameObject("SceneManagerService");
_instance = go.AddComponent<SceneManagerService>();
// DontDestroyOnLoad(go);
}
}
return _instance;
}
}
// Events for scene lifecycle
public event Action<string> SceneLoadStarted;
public event Action<string, float> SceneLoadProgress;
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 const string BootstrapSceneName = "BootstrapScene";
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;
}
}
return _instance;
}
}
// Events for scene lifecycle
public event Action<string> SceneLoadStarted;
public event Action<string, float> SceneLoadProgress;
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 const string BootstrapSceneName = "BootstrapScene";
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
// Ensure BootstrapScene is loaded at startup
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
if (!bootstrap.isLoaded)
{
SceneManager.LoadScene(BootstrapSceneName, LoadSceneMode.Additive);
}
}
void OnApplicationQuit()
{
_isQuitting = true;
}
/// <summary>
/// Load a single scene asynchronously (additive).
/// </summary>
/// <param name="sceneName">Name of the scene to load.</param>
/// <param name="progress">Optional progress reporter.</param>
public async Task LoadSceneAsync(string sceneName, IProgress<float> progress = null)
{
SceneLoadStarted?.Invoke(sceneName);
var op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
_activeLoads[sceneName] = op;
while (!op.isDone)
{
progress?.Report(op.progress);
SceneLoadProgress?.Invoke(sceneName, op.progress);
await Task.Yield();
}
_activeLoads.Remove(sceneName);
SceneLoadCompleted?.Invoke(sceneName);
}
/// <summary>
/// Unload a single scene asynchronously.
/// </summary>
/// <param name="sceneName">Name of the scene to unload.</param>
/// <param name="progress">Optional progress reporter.</param>
public async Task UnloadSceneAsync(string sceneName, IProgress<float> progress = null)
{
var scene = SceneManager.GetSceneByName(sceneName);
if (!scene.isLoaded)
{
Debug.LogWarning($"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>
/// Load multiple scenes asynchronously.
/// </summary>
/// <param name="sceneNames">Enumerable of scene names to load.</param>
/// <param name="progress">Optional progress reporter.</param>
public async Task LoadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null)
{
int total = 0;
int done = 0;
var ops = new List<AsyncOperation>();
foreach (var name in sceneNames)
{
total++;
var op = SceneManager.LoadSceneAsync(name, LoadSceneMode.Additive);
_activeLoads[name] = op;
ops.Add(op);
SceneLoadStarted?.Invoke(name);
}
while (done < total)
{
done = 0;
float aggregate = 0f;
foreach (var op in ops)
// Set up loading screen event handlers
SetupLoadingScreenEvents();
// Ensure BootstrapScene is loaded at startup
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
if (!bootstrap.isLoaded)
{
if (op.isDone) done++;
aggregate += op.progress;
SceneManager.LoadScene(BootstrapSceneName, LoadSceneMode.Additive);
}
float avgProgress = aggregate / total;
progress?.Report(avgProgress);
// Optionally, could invoke SceneLoadProgress for each scene
await Task.Yield();
}
foreach (var name in sceneNames)
private void SetupLoadingScreenEvents()
{
_activeLoads.Remove(name);
SceneLoadCompleted?.Invoke(name);
if (loadingScreen == null) return;
SceneLoadStarted += _ => loadingScreen.ShowLoadingScreen();
SceneLoadCompleted += _ => loadingScreen.HideLoadingScreen();
}
}
/// <summary>
/// Unload multiple scenes asynchronously.
/// </summary>
/// <param name="sceneNames">Enumerable of scene names to unload.</param>
/// <param name="progress">Optional progress reporter.</param>
public async Task UnloadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null)
{
int total = 0;
int done = 0;
var ops = new List<AsyncOperation>();
foreach (var name in sceneNames)
void OnApplicationQuit()
{
total++;
var op = SceneManager.UnloadSceneAsync(name);
_activeUnloads[name] = op;
ops.Add(op);
SceneUnloadStarted?.Invoke(name);
_isQuitting = true;
}
while (done < total)
/// <summary>
/// Load a single scene asynchronously (additive).
/// </summary>
/// <param name="sceneName">Name of the scene to load.</param>
/// <param name="progress">Optional progress reporter.</param>
public async Task LoadSceneAsync(string sceneName, IProgress<float> progress = null)
{
done = 0;
float aggregate = 0f;
foreach (var op in ops)
SceneLoadStarted?.Invoke(sceneName);
var op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
_activeLoads[sceneName] = op;
while (!op.isDone)
{
aggregate += op.progress;
if (op.isDone) done++;
progress?.Report(op.progress);
SceneLoadProgress?.Invoke(sceneName, op.progress);
await Task.Yield();
}
float avg = aggregate / total;
progress?.Report(avg);
_activeLoads.Remove(sceneName);
SceneLoadCompleted?.Invoke(sceneName);
}
/// <summary>
/// Unload a single scene asynchronously.
/// </summary>
/// <param name="sceneName">Name of the scene to unload.</param>
/// <param name="progress">Optional progress reporter.</param>
public async Task UnloadSceneAsync(string sceneName, IProgress<float> progress = null)
{
var scene = SceneManager.GetSceneByName(sceneName);
if (!scene.isLoaded)
{
Debug.LogWarning($"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>
/// Load multiple scenes asynchronously.
/// </summary>
/// <param name="sceneNames">Enumerable of scene names to load.</param>
/// <param name="progress">Optional progress reporter.</param>
public async Task LoadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null)
{
// Show loading screen at the start of multiple scene loading
if (loadingScreen != null)
{
loadingScreen.ShowLoadingScreen();
}
int total = 0;
int done = 0;
var ops = new List<AsyncOperation>();
foreach (var name in sceneNames)
SceneUnloadProgress?.Invoke(name, avg);
await Task.Yield();
}
foreach (var name in sceneNames)
{
_activeUnloads.Remove(name);
SceneUnloadCompleted?.Invoke(name);
}
}
// Optionally: expose current progress for all active operations
public float GetAggregateLoadProgress()
{
if (_activeLoads.Count == 0) return 1f;
float sum = 0f;
foreach (var op in _activeLoads.Values) sum += op.progress;
return sum / _activeLoads.Count;
}
public float GetAggregateUnloadProgress()
{
if (_activeUnloads.Count == 0) return 1f;
float sum = 0f;
foreach (var op in _activeUnloads.Values) sum += op.progress;
return sum / _activeUnloads.Count;
}
// Tracks the currently loaded gameplay scene (not persistent/bootstrapper)
public string CurrentGameplayScene { get; private set; } = "MainMenu";
// Switches from current gameplay scene to a new one
public async Task SwitchSceneAsync(string newSceneName, IProgress<float> progress = null)
{
// Remove all AstarPath (A* Pathfinder) singletons before loading the new scene
var astarPaths = FindObjectsByType<AstarPath>(FindObjectsSortMode.None);
foreach (var astar in astarPaths)
{
if (Application.isPlaying)
Destroy(astar.gameObject);
else
DestroyImmediate(astar.gameObject);
}
// Unload previous gameplay scene (if not BootstrapScene and not same as new)
if (!string.IsNullOrEmpty(CurrentGameplayScene) && CurrentGameplayScene != newSceneName && CurrentGameplayScene != BootstrapSceneName)
{
var prevScene = SceneManager.GetSceneByName(CurrentGameplayScene);
if (prevScene.isLoaded)
{
await UnloadSceneAsync(CurrentGameplayScene);
total++;
var op = SceneManager.LoadSceneAsync(name, LoadSceneMode.Additive);
_activeLoads[name] = op;
ops.Add(op);
SceneLoadStarted?.Invoke(name);
}
else
while (done < total)
{
Debug.LogWarning($"SceneManagerService: Previous scene '{CurrentGameplayScene}' is not loaded, skipping unload.");
done = 0;
float aggregate = 0f;
foreach (var op in ops)
{
if (op.isDone) done++;
aggregate += op.progress;
}
float avgProgress = aggregate / total;
progress?.Report(avgProgress);
await Task.Yield();
}
foreach (var name in sceneNames)
{
_activeLoads.Remove(name);
SceneLoadCompleted?.Invoke(name);
}
// Hide loading screen after all scenes are loaded
if (loadingScreen != null)
{
loadingScreen.HideLoadingScreen();
}
}
// Ensure BootstrapScene is loaded before loading new scene
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
if (!bootstrap.isLoaded)
/// <summary>
/// Unload multiple scenes asynchronously.
/// </summary>
/// <param name="sceneNames">Enumerable of scene names to unload.</param>
/// <param name="progress">Optional progress reporter.</param>
public async Task UnloadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null)
{
SceneManager.LoadScene(BootstrapSceneName, LoadSceneMode.Additive);
// Show loading screen at the start of multiple scene unloading
if (loadingScreen != null)
{
loadingScreen.ShowLoadingScreen();
}
int total = 0;
int done = 0;
var ops = new List<AsyncOperation>();
foreach (var name in sceneNames)
{
total++;
var op = SceneManager.UnloadSceneAsync(name);
_activeUnloads[name] = op;
ops.Add(op);
SceneUnloadStarted?.Invoke(name);
}
while (done < total)
{
done = 0;
float aggregate = 0f;
foreach (var op in ops)
{
aggregate += op.progress;
if (op.isDone) done++;
}
float avg = aggregate / total;
progress?.Report(avg);
await Task.Yield();
}
foreach (var name in sceneNames)
{
_activeUnloads.Remove(name);
SceneUnloadCompleted?.Invoke(name);
}
// Hide loading screen after all scenes are unloaded
if (loadingScreen != null)
{
loadingScreen.HideLoadingScreen();
}
}
// Optionally: expose current progress for all active operations
public float GetAggregateLoadProgress()
{
if (_activeLoads.Count == 0) return 1f;
float sum = 0f;
foreach (var op in _activeLoads.Values) sum += op.progress;
return sum / _activeLoads.Count;
}
public float GetAggregateUnloadProgress()
{
if (_activeUnloads.Count == 0) return 1f;
float sum = 0f;
foreach (var op in _activeUnloads.Values) sum += op.progress;
return sum / _activeUnloads.Count;
}
// Tracks the currently loaded gameplay scene (not persistent/bootstrapper)
public string CurrentGameplayScene { get; private set; } = "MainMenu";
// Switches from current gameplay scene to a new one
public async Task SwitchSceneAsync(string newSceneName, IProgress<float> progress = null)
{
// Remove all AstarPath (A* Pathfinder) singletons before loading the new scene
var astarPaths = FindObjectsByType<AstarPath>(FindObjectsSortMode.None);
foreach (var astar in astarPaths)
{
if (Application.isPlaying)
Destroy(astar.gameObject);
else
DestroyImmediate(astar.gameObject);
}
// Unload previous gameplay scene (if not BootstrapScene and not same as new)
if (!string.IsNullOrEmpty(CurrentGameplayScene) && CurrentGameplayScene != newSceneName && CurrentGameplayScene != BootstrapSceneName)
{
var prevScene = SceneManager.GetSceneByName(CurrentGameplayScene);
if (prevScene.isLoaded)
{
await UnloadSceneAsync(CurrentGameplayScene);
}
else
{
Debug.LogWarning($"SceneManagerService: Previous scene '{CurrentGameplayScene}' is not loaded, skipping unload.");
}
}
// 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
await LoadSceneAsync(newSceneName, progress);
// Update tracker
CurrentGameplayScene = newSceneName;
}
// Load new gameplay scene
await LoadSceneAsync(newSceneName, progress);
// Update tracker
CurrentGameplayScene = newSceneName;
}
}