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 OnCinematicStopped;
private static CinematicsManager _instance;
private static bool _isQuitting;
private Image cinematicSprites;
public PlayableAsset cinematicToPlay;
private Image _cinematicSprites;
private bool _isCinematicPlaying = false;
public bool IsCinematicPlaying => _isCinematicPlaying;
// Dictionary to track addressable handles by PlayableDirector
private Dictionary<PlayableDirector, AsyncOperationHandle<PlayableAsset>> _addressableHandles
@@ -69,20 +69,21 @@ namespace Cinematics
/// </summary>
public PlayableDirector PlayCinematic(PlayableAsset assetToPlay)
{
cinematicSprites.enabled = true;
_cinematicSprites.enabled = true;
playableDirector.stopped += OnPlayableDirectorStopped;
playableDirector.Play(assetToPlay);
Debug.Log("Playing cinematic " + assetToPlay.name);
_isCinematicPlaying = true;
OnCinematicStarted?.Invoke();
return playableDirector;
}
void OnPlayableDirectorStopped(PlayableDirector director)
{
cinematicSprites.enabled = false;
_cinematicSprites.enabled = false;
Debug.Log("Cinematic stopped!");
_isCinematicPlaying = false;
OnCinematicStopped?.Invoke();
// Release the addressable handle associated with this director
ReleaseAddressableHandle(director);
}
@@ -115,14 +116,6 @@ namespace Cinematics
playableDirector.Stop();
}
}
/// <summary>
/// Checks if a cinematic is currently playing
/// </summary>
public bool IsCinematicPlaying()
{
return playableDirector != null && playableDirector.state == PlayState.Playing;
}
/// <summary>
/// Releases the addressable handle associated with a specific PlayableDirector
@@ -152,7 +145,54 @@ namespace Cinematics
_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"))
{
@@ -161,14 +201,11 @@ namespace Cinematics
_instance = this;
if (!SceneManager.GetActiveScene().name.ToLower().Contains("mainmenu"))
{
return;
}
playableDirector = GetComponent<PlayableDirector>();
cinematicSprites = GetComponentInChildren<Image>(true);
LoadAndPlayCinematic("IntroSequence");
_cinematicSprites = GetComponentInChildren<Image>(true);
// Use the new method with loading screen instead of direct load
await PlayCinematicWithLoadingScreen("IntroSequence");
}
}
}

View File

@@ -25,6 +25,9 @@ namespace Cinematics
void OnEnable()
{
if (CinematicsManager.Instance.IsCinematicPlaying)
HandleCinematicStarted();
CinematicsManager.Instance.OnCinematicStarted += HandleCinematicStarted;
CinematicsManager.Instance.OnCinematicStopped += HandleCinematicStopped;
}
@@ -50,7 +53,7 @@ namespace Cinematics
void Update()
{
// 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 progress = Mathf.Clamp01(holdTime / holdDuration);

View File

@@ -12,8 +12,7 @@ namespace Core
/// </summary>
public class SceneManagerService : MonoBehaviour
{
[SerializeField] private LoadingScreenController loadingScreen;
private LoadingScreenController _loadingScreen;
private static SceneManagerService _instance;
private static bool _isQuitting = false;
/// <summary>
@@ -49,6 +48,15 @@ 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;
@@ -64,9 +72,6 @@ namespace Core
}
}
#endif
// Set up loading screen event handlers
SetupLoadingScreenEvents();
// Ensure BootstrapScene is loaded at startup
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
if (!bootstrap.isLoaded)
@@ -77,10 +82,10 @@ namespace Core
private void SetupLoadingScreenEvents()
{
if (loadingScreen == null) return;
SceneLoadStarted += _ => loadingScreen.ShowLoadingScreen();
SceneLoadCompleted += _ => loadingScreen.HideLoadingScreen();
if (_loadingScreen == null) return;
SceneLoadStarted += _ => _loadingScreen.ShowLoadingScreen(() => GetAggregateLoadProgress());
SceneLoadCompleted += _ => _loadingScreen.HideLoadingScreen();
}
void OnApplicationQuit()
@@ -142,9 +147,9 @@ namespace Core
public async Task LoadScenesAsync(IEnumerable<string> sceneNames, IProgress<float> progress = null)
{
// Show loading screen at the start of multiple scene loading
if (loadingScreen != null)
if (_loadingScreen != null)
{
loadingScreen.ShowLoadingScreen();
_loadingScreen.ShowLoadingScreen();
}
int total = 0;
@@ -181,9 +186,9 @@ namespace Core
}
// 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)
{
// Show loading screen at the start of multiple scene unloading
if (loadingScreen != null)
if (_loadingScreen != null)
{
loadingScreen.ShowLoadingScreen();
_loadingScreen.ShowLoadingScreen();
}
int total = 0;
@@ -234,9 +239,9 @@ namespace Core
}
// 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"))
{
Debug.Log("[InputManager] SwitchInputOnSceneLoaded - Setting InputMode to UI for MainMenu");
SetInputMode(InputMode.UI);
SetInputMode(InputMode.GameAndUI);
}
else
{

View File

@@ -1,4 +1,5 @@
using System.Collections;
using System;
using UnityEngine;
using UnityEngine.UI;
using Core;
@@ -22,9 +23,52 @@ namespace UI
private Coroutine _progressCoroutine;
private bool _loadingComplete = 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()
{
_instance = this;
if (loadingScreenContainer == null)
loadingScreenContainer = gameObject;
@@ -38,8 +82,16 @@ namespace UI
/// <summary>
/// Shows the loading screen and resets the progress bar to zero
/// </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
if (_progressCoroutine != null)
{
@@ -67,7 +119,7 @@ namespace UI
/// <summary>
/// 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>
private IEnumerator AnimateProgressBar()
{
@@ -80,12 +132,8 @@ namespace UI
float elapsedTime = Time.time - startTime;
float steadyProgress = Mathf.Clamp01(elapsedTime / minimumDisplayTime);
// Get the actual loading progress from SceneManagerService
float actualProgress = 0f;
if (SceneManagerService.Instance != null)
{
actualProgress = SceneManagerService.Instance.GetAggregateLoadProgress();
}
// Get the actual loading progress from the current provider
float actualProgress = _currentProgressProvider();
// If loading is complete, actualProgress should be 1.0
if (_loadingComplete)
@@ -133,6 +181,10 @@ namespace UI
{
loadingScreenContainer.SetActive(false);
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);
Debug.Log("[LoadingScreen] Animation already complete, hiding screen immediately");
// Invoke the callback when fully hidden
_onLoadingScreenFullyHidden?.Invoke();
_onLoadingScreenFullyHidden = null;
}
}
else
@@ -164,5 +220,35 @@ namespace UI
// 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
SceneManagerService.Instance.SceneLoadCompleted += SetPauseMenuByLevel;
#if UNITY_EDITOR
// Set initial state based on current scene
SetPauseMenuByLevel(SceneManager.GetActiveScene().name);
#if UNITY_EDITOR
// Initialize pause menu state
HidePauseMenu(false);
#endif