using System; using System.Collections.Generic; using Bootstrap; 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; // Dictionary to track addressable handles by PlayableDirector private Dictionary> _addressableHandles = new Dictionary>(); /// /// Singleton instance of the CinematicsManager. No longer creates an instance if one doesn't exist. /// public static CinematicsManager Instance => _instance; public PlayableDirector playableDirector; private void Awake() { _instance = this; // Register for post-boot initialization BootCompletionService.RegisterInitAction(InitializePostBoot); } private void InitializePostBoot() { // Initialize any dependencies that require other services to be ready // For example, subscribe to SceneManagerService events if needed Logging.Debug("[CinematicsManager] Post-boot initialization complete"); } 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() { _isQuitting = true; ReleaseAllHandles(); } /// /// Initializes required components for the CinematicsManager /// private void InitializeComponents() { // Initialize PlayableDirector if not set if (playableDirector == null) { playableDirector = GetComponent(); // If still null, try to add the component if (playableDirector == null) { playableDirector = gameObject.AddComponent(); Debug.Log("[CinematicsManager] Added missing PlayableDirector component"); } } // Initialize _cinematicSprites if not set if (_cinematicSprites == null) { // First try to find in children _cinematicSprites = GetComponentInChildren(true); // If still null, create a new UI Image for cinematics if (_cinematicSprites == null) { Debug.LogWarning("[CinematicsManager] No Image found for cinematics display. Cinematics may not display correctly."); } } } /// /// Plays a cinematic from an object reference and returns the PlayableDirector playing the timeline /// public PlayableDirector PlayCinematic(PlayableAsset assetToPlay) { // Ensure components are initialized before playing InitializeComponents(); if (_cinematicSprites != null) { _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) { // 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(); } } }