using System.Collections.Generic; using Core; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.Playables; using UnityEngine.SceneManagement; using UnityEngine.UI; namespace Cinematics { /// /// Handles loading, playing and unloading cinematics /// public class CinematicsManager : MonoBehaviour { public event System.Action OnCinematicStarted; public event System.Action OnCinematicStopped; private static CinematicsManager _instance; private static bool _isQuitting; private Image _cinematicSprites; private bool _isCinematicPlaying = false; public bool IsCinematicPlaying => _isCinematicPlaying; public Image cinematicBackground; // Dictionary to track addressable handles by PlayableDirector private Dictionary> _addressableHandles = new Dictionary>(); public static CinematicsManager Instance { get { if (_instance == null && Application.isPlaying && !_isQuitting) { _instance = FindAnyObjectByType(); if (_instance == null) { var go = new GameObject("CinematicsManager"); _instance = go.AddComponent(); // DontDestroyOnLoad(go); } } return _instance; } } public PlayableDirector playableDirector; private void OnEnable() { // Subscribe to application quit event to ensure cleanup Application.quitting += OnApplicationQuit; } private void OnDisable() { // Unsubscribe from application quit event Application.quitting -= OnApplicationQuit; // Clean up any remaining addressable handles when disabled ReleaseAllHandles(); } private void OnApplicationQuit() { ReleaseAllHandles(); } /// /// Plays a cinematic from an object reference and returns the PlayableDirector playing the timeline /// public PlayableDirector PlayCinematic(PlayableAsset assetToPlay) { _cinematicSprites.enabled = true; playableDirector.stopped += OnPlayableDirectorStopped; playableDirector.Play(assetToPlay); Logging.Debug("Playing cinematic " + assetToPlay.name); _isCinematicPlaying = true; OnCinematicStarted?.Invoke(); return playableDirector; } void OnPlayableDirectorStopped(PlayableDirector director) { _cinematicSprites.enabled = false; Logging.Debug("Cinematic stopped!"); _isCinematicPlaying = false; OnCinematicStopped?.Invoke(); // Release the addressable handle associated with this director ReleaseAddressableHandle(director); } /// /// Loads a playable from an asset path and plays it as a cinematic /// public PlayableDirector LoadAndPlayCinematic(string key) { playableDirector = GetComponent(); _cinematicSprites = GetComponentInChildren(true); // Load the asset via addressables var handle = Addressables.LoadAssetAsync(key); var result = handle.WaitForCompletion(); // Store the handle for later release _addressableHandles[playableDirector] = handle; Logging.Debug($"[CinematicsManager] Loaded addressable cinematic: {key}"); return PlayCinematic(result); } /// /// Skips the currently playing cinematic if one is active /// public void SkipCurrentCinematic() { if (playableDirector != null && playableDirector.state == PlayState.Playing) { Logging.Debug("Skipping current cinematic"); playableDirector.Stop(); } } /// /// Releases the addressable handle associated with a specific PlayableDirector /// private void ReleaseAddressableHandle(PlayableDirector director) { if (_addressableHandles.TryGetValue(director, out var handle)) { Logging.Debug($"[CinematicsManager] Releasing addressable handle for cinematic"); Addressables.Release(handle); _addressableHandles.Remove(director); } } /// /// Releases all active addressable handles /// private void ReleaseAllHandles() { foreach (var handle in _addressableHandles.Values) { if (handle.IsValid()) { Addressables.Release(handle); } } _addressableHandles.Clear(); } private void Awake() { PlayStartCinematicOnGameLoad(); } /// /// Loads a cinematic asynchronously while showing a loading screen, then plays it /// /// The addressable key of the cinematic to load /// The PlayableDirector playing the cinematic public async System.Threading.Tasks.Task PlayCinematicWithLoadingScreen(string key) { Logging.Debug($"[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 Logging.Debug($"[CinematicsManager] Starting cinematic asset load: {key}"); AsyncOperationHandle handle = Addressables.LoadAssetAsync(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; Logging.Debug($"[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(); Logging.Debug($"[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")) { return; } _instance = this; playableDirector = GetComponent(); _cinematicSprites = GetComponentInChildren(true); // Use the new method with loading screen instead of direct load await PlayCinematicWithLoadingScreen("IntroSequence"); } public void ShowCinematicBackground(bool showCinematic) { if(showCinematic == true) { var _image = cinematicBackground.GetComponent(); _image.enabled = true; } if (showCinematic == false) { var _image = cinematicBackground.GetComponent(); _image.enabled = false; } } } }