Semi-working intro cinematic with loading screen

This commit is contained in:
Michal Pikulski
2025-10-13 14:25:11 +02:00
parent 65e14c07d2
commit aefff3d050
6 changed files with 181 additions and 50 deletions

View File

@@ -15,11 +15,11 @@ namespace Cinematics
{ {
public event System.Action OnCinematicStarted; public event System.Action OnCinematicStarted;
public event System.Action OnCinematicStopped; public event System.Action OnCinematicStopped;
private static CinematicsManager _instance; private static CinematicsManager _instance;
private static bool _isQuitting; private static bool _isQuitting;
private Image cinematicSprites; private Image _cinematicSprites;
public PlayableAsset cinematicToPlay; private bool _isCinematicPlaying = false;
public bool IsCinematicPlaying => _isCinematicPlaying;
// Dictionary to track addressable handles by PlayableDirector // Dictionary to track addressable handles by PlayableDirector
private Dictionary<PlayableDirector, AsyncOperationHandle<PlayableAsset>> _addressableHandles private Dictionary<PlayableDirector, AsyncOperationHandle<PlayableAsset>> _addressableHandles
@@ -69,20 +69,21 @@ namespace Cinematics
/// </summary> /// </summary>
public PlayableDirector PlayCinematic(PlayableAsset assetToPlay) public PlayableDirector PlayCinematic(PlayableAsset assetToPlay)
{ {
cinematicSprites.enabled = true; _cinematicSprites.enabled = true;
playableDirector.stopped += OnPlayableDirectorStopped; playableDirector.stopped += OnPlayableDirectorStopped;
playableDirector.Play(assetToPlay); playableDirector.Play(assetToPlay);
Debug.Log("Playing cinematic " + assetToPlay.name); Debug.Log("Playing cinematic " + assetToPlay.name);
_isCinematicPlaying = true;
OnCinematicStarted?.Invoke(); OnCinematicStarted?.Invoke();
return playableDirector; return playableDirector;
} }
void OnPlayableDirectorStopped(PlayableDirector director) void OnPlayableDirectorStopped(PlayableDirector director)
{ {
cinematicSprites.enabled = false; _cinematicSprites.enabled = false;
Debug.Log("Cinematic stopped!"); Debug.Log("Cinematic stopped!");
_isCinematicPlaying = false;
OnCinematicStopped?.Invoke(); OnCinematicStopped?.Invoke();
// Release the addressable handle associated with this director // Release the addressable handle associated with this director
ReleaseAddressableHandle(director); ReleaseAddressableHandle(director);
} }
@@ -116,14 +117,6 @@ namespace Cinematics
} }
} }
/// <summary>
/// Checks if a cinematic is currently playing
/// </summary>
public bool IsCinematicPlaying()
{
return playableDirector != null && playableDirector.state == PlayState.Playing;
}
/// <summary> /// <summary>
/// Releases the addressable handle associated with a specific PlayableDirector /// Releases the addressable handle associated with a specific PlayableDirector
/// </summary> /// </summary>
@@ -152,7 +145,54 @@ namespace Cinematics
_addressableHandles.Clear(); _addressableHandles.Clear();
} }
private void Start() private void Awake()
{
PlayStartCinematicOnGameLoad();
}
/// <summary>
/// Loads a cinematic asynchronously while showing a loading screen, then plays it
/// </summary>
/// <param name="key">The addressable key of the cinematic to load</param>
/// <returns>The PlayableDirector playing the cinematic</returns>
public async System.Threading.Tasks.Task<PlayableDirector> PlayCinematicWithLoadingScreen(string key)
{
Debug.Log($"[CinematicsManager] Preparing to load cinematic with loading screen: {key}");
// First, show the loading screen BEFORE creating any async operations
UI.LoadingScreenController.Instance.ShowLoadingScreen();
// Give the loading screen a frame to render
await System.Threading.Tasks.Task.Yield();
// Now create the load handle and track its progress
Debug.Log($"[CinematicsManager] Starting cinematic asset load: {key}");
AsyncOperationHandle<PlayableAsset> handle = Addressables.LoadAssetAsync<PlayableAsset>(key);
// Update the loading screen with the progress provider after the handle is created
UI.LoadingScreenController.Instance.ShowLoadingScreen(() => handle.PercentComplete);
// Wait for the loading to complete
var result = await handle.Task;
// Store the handle for later release
_addressableHandles[playableDirector] = handle;
Debug.Log($"[CinematicsManager] Cinematic loaded: {key}");
// Hide the loading screen
UI.LoadingScreenController.Instance.HideLoadingScreen();
// Important: Wait for the loading screen to be fully hidden before playing the cinematic
await UI.LoadingScreenController.Instance.WaitForLoadingScreenToHideAsync();
Debug.Log($"[CinematicsManager] Loading screen hidden, now playing cinematic: {key}");
// Play the cinematic
return PlayCinematic(result);
}
private async void PlayStartCinematicOnGameLoad()
{ {
if (!SceneManager.GetActiveScene().name.ToLower().Contains("mainmenu")) if (!SceneManager.GetActiveScene().name.ToLower().Contains("mainmenu"))
{ {
@@ -161,14 +201,11 @@ namespace Cinematics
_instance = this; _instance = this;
if (!SceneManager.GetActiveScene().name.ToLower().Contains("mainmenu"))
{
return;
}
playableDirector = GetComponent<PlayableDirector>(); playableDirector = GetComponent<PlayableDirector>();
cinematicSprites = GetComponentInChildren<Image>(true); _cinematicSprites = GetComponentInChildren<Image>(true);
LoadAndPlayCinematic("IntroSequence");
// Use the new method with loading screen instead of direct load
await PlayCinematicWithLoadingScreen("IntroSequence");
} }
} }
} }

View File

@@ -25,6 +25,9 @@ namespace Cinematics
void OnEnable() void OnEnable()
{ {
if (CinematicsManager.Instance.IsCinematicPlaying)
HandleCinematicStarted();
CinematicsManager.Instance.OnCinematicStarted += HandleCinematicStarted; CinematicsManager.Instance.OnCinematicStarted += HandleCinematicStarted;
CinematicsManager.Instance.OnCinematicStopped += HandleCinematicStopped; CinematicsManager.Instance.OnCinematicStopped += HandleCinematicStopped;
} }
@@ -50,7 +53,7 @@ namespace Cinematics
void Update() void Update()
{ {
// Only process while cinematic is playing and we're holding // Only process while cinematic is playing and we're holding
if (_isHolding && CinematicsManager.Instance.IsCinematicPlaying()) if (_isHolding && CinematicsManager.Instance.IsCinematicPlaying)
{ {
float holdTime = Time.time - _holdStartTime; float holdTime = Time.time - _holdStartTime;
float progress = Mathf.Clamp01(holdTime / holdDuration); float progress = Mathf.Clamp01(holdTime / holdDuration);

View File

@@ -12,8 +12,7 @@ namespace Core
/// </summary> /// </summary>
public class SceneManagerService : MonoBehaviour public class SceneManagerService : MonoBehaviour
{ {
[SerializeField] private LoadingScreenController loadingScreen; private LoadingScreenController _loadingScreen;
private static SceneManagerService _instance; private static SceneManagerService _instance;
private static bool _isQuitting = false; private static bool _isQuitting = false;
/// <summary> /// <summary>
@@ -49,6 +48,15 @@ namespace Core
private readonly Dictionary<string, AsyncOperation> _activeUnloads = new(); private readonly Dictionary<string, AsyncOperation> _activeUnloads = new();
private const string BootstrapSceneName = "BootstrapScene"; private const string BootstrapSceneName = "BootstrapScene";
void Start()
{
_loadingScreen = LoadingScreenController.Instance;
// Set up loading screen event handlers
SetupLoadingScreenEvents();
}
void Awake() void Awake()
{ {
_instance = this; _instance = this;
@@ -64,9 +72,6 @@ namespace Core
} }
} }
#endif #endif
// Set up loading screen event handlers
SetupLoadingScreenEvents();
// Ensure BootstrapScene is loaded at startup // Ensure BootstrapScene is loaded at startup
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName); var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
if (!bootstrap.isLoaded) if (!bootstrap.isLoaded)
@@ -77,10 +82,10 @@ namespace Core
private void SetupLoadingScreenEvents() private void SetupLoadingScreenEvents()
{ {
if (loadingScreen == null) return; if (_loadingScreen == null) return;
SceneLoadStarted += _ => loadingScreen.ShowLoadingScreen(); SceneLoadStarted += _ => _loadingScreen.ShowLoadingScreen(() => GetAggregateLoadProgress());
SceneLoadCompleted += _ => loadingScreen.HideLoadingScreen(); SceneLoadCompleted += _ => _loadingScreen.HideLoadingScreen();
} }
void OnApplicationQuit() void OnApplicationQuit()
@@ -142,9 +147,9 @@ namespace Core
public async Task LoadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null) public async Task LoadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null)
{ {
// Show loading screen at the start of multiple scene loading // Show loading screen at the start of multiple scene loading
if (loadingScreen != null) if (_loadingScreen != null)
{ {
loadingScreen.ShowLoadingScreen(); _loadingScreen.ShowLoadingScreen();
} }
int total = 0; int total = 0;
@@ -181,9 +186,9 @@ namespace Core
} }
// Hide loading screen after all scenes are loaded // Hide loading screen after all scenes are loaded
if (loadingScreen != null) if (_loadingScreen != null)
{ {
loadingScreen.HideLoadingScreen(); _loadingScreen.HideLoadingScreen();
} }
} }
@@ -195,9 +200,9 @@ namespace Core
public async Task UnloadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null) public async Task UnloadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null)
{ {
// Show loading screen at the start of multiple scene unloading // Show loading screen at the start of multiple scene unloading
if (loadingScreen != null) if (_loadingScreen != null)
{ {
loadingScreen.ShowLoadingScreen(); _loadingScreen.ShowLoadingScreen();
} }
int total = 0; int total = 0;
@@ -234,9 +239,9 @@ namespace Core
} }
// Hide loading screen after all scenes are unloaded // Hide loading screen after all scenes are unloaded
if (loadingScreen != null) if (_loadingScreen != null)
{ {
loadingScreen.HideLoadingScreen(); _loadingScreen.HideLoadingScreen();
} }
} }

View File

@@ -90,7 +90,7 @@ namespace Input
if (sceneName.ToLower().Contains("mainmenu")) if (sceneName.ToLower().Contains("mainmenu"))
{ {
Debug.Log("[InputManager] SwitchInputOnSceneLoaded - Setting InputMode to UI for MainMenu"); Debug.Log("[InputManager] SwitchInputOnSceneLoaded - Setting InputMode to UI for MainMenu");
SetInputMode(InputMode.UI); SetInputMode(InputMode.GameAndUI);
} }
else else
{ {

View File

@@ -1,4 +1,5 @@
using System.Collections; using System.Collections;
using System;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using Core; using Core;
@@ -22,9 +23,52 @@ namespace UI
private Coroutine _progressCoroutine; private Coroutine _progressCoroutine;
private bool _loadingComplete = false; private bool _loadingComplete = false;
private bool _animationComplete = false; private bool _animationComplete = false;
private Action _onLoadingScreenFullyHidden;
private static LoadingScreenController _instance;
private static bool _isQuitting;
/// <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;
public static LoadingScreenController Instance
{
get
{
if (_instance == null && Application.isPlaying && !_isQuitting)
{
_instance = FindAnyObjectByType<LoadingScreenController>();
if (_instance == null)
{
var go = new GameObject("LoadingScreenController");
_instance = go.AddComponent<LoadingScreenController>();
}
}
return _instance;
}
}
private void Awake() private void Awake()
{ {
_instance = this;
if (loadingScreenContainer == null) if (loadingScreenContainer == null)
loadingScreenContainer = gameObject; loadingScreenContainer = gameObject;
@@ -38,8 +82,16 @@ namespace UI
/// <summary> /// <summary>
/// Shows the loading screen and resets the progress bar to zero /// Shows the loading screen and resets the progress bar to zero
/// </summary> /// </summary>
public void ShowLoadingScreen() /// <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 // Stop any existing progress coroutine
if (_progressCoroutine != null) if (_progressCoroutine != null)
{ {
@@ -67,7 +119,7 @@ namespace UI
/// <summary> /// <summary>
/// Animates the progress bar at a steady pace over the minimum display time, /// Animates the progress bar at a steady pace over the minimum display time,
/// while also checking actual loading progress from SceneManagerService /// while also checking actual loading progress from the current progress provider
/// </summary> /// </summary>
private IEnumerator AnimateProgressBar() private IEnumerator AnimateProgressBar()
{ {
@@ -80,12 +132,8 @@ namespace UI
float elapsedTime = Time.time - startTime; float elapsedTime = Time.time - startTime;
float steadyProgress = Mathf.Clamp01(elapsedTime / minimumDisplayTime); float steadyProgress = Mathf.Clamp01(elapsedTime / minimumDisplayTime);
// Get the actual loading progress from SceneManagerService // Get the actual loading progress from the current provider
float actualProgress = 0f; float actualProgress = _currentProgressProvider();
if (SceneManagerService.Instance != null)
{
actualProgress = SceneManagerService.Instance.GetAggregateLoadProgress();
}
// If loading is complete, actualProgress should be 1.0 // If loading is complete, actualProgress should be 1.0
if (_loadingComplete) if (_loadingComplete)
@@ -133,6 +181,10 @@ namespace UI
{ {
loadingScreenContainer.SetActive(false); loadingScreenContainer.SetActive(false);
Debug.Log("[LoadingScreen] Animation AND loading complete, hiding screen"); Debug.Log("[LoadingScreen] Animation AND loading complete, hiding screen");
// Invoke the callback when fully hidden
_onLoadingScreenFullyHidden?.Invoke();
_onLoadingScreenFullyHidden = null;
} }
} }
@@ -156,6 +208,10 @@ namespace UI
{ {
loadingScreenContainer.SetActive(false); loadingScreenContainer.SetActive(false);
Debug.Log("[LoadingScreen] Animation already complete, hiding screen immediately"); Debug.Log("[LoadingScreen] Animation already complete, hiding screen immediately");
// Invoke the callback when fully hidden
_onLoadingScreenFullyHidden?.Invoke();
_onLoadingScreenFullyHidden = null;
} }
} }
else else
@@ -164,5 +220,35 @@ namespace UI
// The coroutine will handle hiding when animation completes // 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;
}
} }
} }

View File

@@ -48,10 +48,10 @@ namespace UI
// Subscribe to scene loaded events // Subscribe to scene loaded events
SceneManagerService.Instance.SceneLoadCompleted += SetPauseMenuByLevel; SceneManagerService.Instance.SceneLoadCompleted += SetPauseMenuByLevel;
#if UNITY_EDITOR
// Set initial state based on current scene // Set initial state based on current scene
SetPauseMenuByLevel(SceneManager.GetActiveScene().name); SetPauseMenuByLevel(SceneManager.GetActiveScene().name);
#if UNITY_EDITOR
// Initialize pause menu state // Initialize pause menu state
HidePauseMenu(false); HidePauseMenu(false);
#endif #endif