using System; using System.Collections; using Cinemachine; using UnityEngine; using UnityEngine.Playables; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.Timeline; using AibisDream.Framework; using AibisDream.SaveSystem; namespace AibisDream { public class DirectorHandler : MonoBehaviour { private const string TimelinePrefix = "Timeline/"; public string timelineName; [SerializeField] private PlayableDirector director; [Header("Cinemachine 自动绑定")] [Tooltip("加载新 Timeline 时自动绑定 CinemachineBrain")] [SerializeField] private bool autoBindCinemachineBrain; [Header("播放结束")] [Tooltip("勾选:自然播放到结尾(PlayableDirector.stopped)后将时间设回 0;不勾选:停留在最后一帧(默认)")] [SerializeField] private bool resetToStartWhenFinished; [Tooltip("当 resetToStartWhenFinished 为真时,回到时间 0 前的等待秒数;0 表示立刻回到起点")] [Min(0f)] [SerializeField] private float resetToStartDelay; private Action _onCompleteCallback; private AsyncOperationHandle _addressableHandle; private PlayableAsset _savedPlayableAsset; private Coroutine _pendingResetToStartCoroutine; private string _lastAddressableKey; #if UNITY_EDITOR /// /// 在 Editor 中,当组件创建或 Inspector 变化时自动查找同物体上的 PlayableDirector /// private void OnValidate() { if (director == null) { director = GetComponent(); } } #endif private void Awake() { if (director == null) { director = GetComponent(); } } private void Start() { TimelineCenter.Instance.RegisterDirector(this); } private void OnDestroy() { TimelineCenter.Instance.UnregisterDirector(this); CancelPendingResetToStartCoroutine(); if (director != null) { director.stopped -= OnDirectorStopped; } ReleaseAddressableHandle(); } /// /// 是否需要订阅 PlayableDirector.stopped(完成回调或需要播放结束行为时)。 /// private bool ShouldSubscribeStopped(Action onComplete) { return onComplete != null || resetToStartWhenFinished; } private void CancelPendingResetToStartCoroutine() { if (_pendingResetToStartCoroutine == null) return; StopCoroutine(_pendingResetToStartCoroutine); _pendingResetToStartCoroutine = null; } /// /// 将 Director 时间设到 0 并刷新一帧(不 Stop、不释放资源)。 /// private void ApplyPlaybackTimeZero() { if (director == null) return; director.time = 0; director.Evaluate(); } /// /// 在 Release + 完成回调之后执行:按配置回到起点或延时回到起点。 /// private void ApplyEndBehaviorAfterStopped() { if (!resetToStartWhenFinished) return; CancelPendingResetToStartCoroutine(); var delay = Mathf.Max(0f, resetToStartDelay); if (delay <= 0f) { ApplyPlaybackTimeZero(); } else { _pendingResetToStartCoroutine = StartCoroutine(ResetToStartAfterDelayCoroutine(delay)); } } private IEnumerator ResetToStartAfterDelayCoroutine(float delaySeconds) { yield return new WaitForSeconds(delaySeconds); _pendingResetToStartCoroutine = null; ApplyPlaybackTimeZero(); } private void ReleaseAddressableHandle() { if (!_addressableHandle.IsValid()) return; if (director != null && director.playableAsset == _addressableHandle.Result) { director.playableAsset = _savedPlayableAsset; } ResourceKit.Release(_addressableHandle); _addressableHandle = default; } /// /// 自动绑定 CinemachineBrain 到 Timeline 中的所有 CinemachineTrack /// private void AutoBindCinemachineBrain() { if (!autoBindCinemachineBrain) { return; } if (director == null) { return; } if (director.playableAsset == null) { return; } // 获取目标 CinemachineBrain var mainCamera = Camera.main; if (mainCamera == null) { return; } var brain = mainCamera.GetComponent(); if (brain == null) { return; } // 遍历所有输出轨道,找到 CinemachineTrack 并绑定 var timelineAsset = director.playableAsset as TimelineAsset; if (timelineAsset == null) { return; } int trackCount = 0; int boundCount = 0; foreach (var track in timelineAsset.GetOutputTracks()) { trackCount++; // 直接检查轨道类型 if (track is CinemachineTrack) { director.SetGenericBinding(track, brain); boundCount++; } } } /// /// 播放 Timeline /// public void Play(Action onComplete = null) { if (director == null) { Debug.LogError($"DirectorHandler {timelineName}: PlayableDirector is null"); return; } // 先停止、回到起点再播放(保证同一 Timeline 可重复从头播放) CancelPendingResetToStartCoroutine(); director.stopped -= OnDirectorStopped; director.Stop(); ApplyPlaybackTimeZero(); _onCompleteCallback = onComplete; if (ShouldSubscribeStopped(onComplete)) { director.stopped += OnDirectorStopped; } // 自动绑定 CinemachineBrain AutoBindCinemachineBrain(); Debug.Log($"DirectorHandler {timelineName}: Play"); director.Play(); } /// /// 异步播放 Timeline(协程版本) /// public IEnumerator PlayAsync() { if (director == null) { Debug.LogError($"DirectorHandler {timelineName}: PlayableDirector is null"); yield break; } CancelPendingResetToStartCoroutine(); director.stopped -= OnDirectorStopped; director.Stop(); ApplyPlaybackTimeZero(); if (ShouldSubscribeStopped(null)) { director.stopped += OnDirectorStopped; } // 自动绑定 CinemachineBrain AutoBindCinemachineBrain(); Debug.Log($"DirectorHandler {timelineName}: Play"); director.Play(); // 等待播放完成 while (director.state == PlayState.Playing) { yield return null; } } /// /// 从 Addressables 加载 PlayableAsset 并播放(加载时自动加 "Timeline/" 前缀) /// public void PlayFromAddressable(string addressableKey, Action onComplete = null) { StartCoroutine(PlayFromAddressableCoroutine(addressableKey, onComplete)); } /// /// 从 Addressables 加载 PlayableAsset 并播放(协程版本,可等待播完) /// public IEnumerator PlayFromAddressableAsync(string addressableKey) { yield return PlayFromAddressableCoroutine(addressableKey, null); } private IEnumerator PlayFromAddressableCoroutine(string addressableKey, Action onComplete) { if (director == null) { Debug.LogError($"DirectorHandler {timelineName}: PlayableDirector is null"); yield break; } if (string.IsNullOrEmpty(addressableKey)) { Debug.LogError($"DirectorHandler {timelineName}: addressableKey is null or empty"); yield break; } // 播放新 Addressable 前,先释放上一次 ReleaseAddressableHandle(); var loadKey = TimelinePrefix + addressableKey; _lastAddressableKey = addressableKey; PlayableAsset loadedAsset = null; AsyncOperationHandle loadedHandle = default; var loadDone = false; ResourceKit.LoadAssetAsyncWithHandle(loadKey, handle => { loadedHandle = handle; if (handle.Status == AsyncOperationStatus.Succeeded) loadedAsset = handle.Result; loadDone = true; }); yield return new WaitUntil(() => loadDone); if (loadedAsset == null) { Debug.LogError($"DirectorHandler {timelineName}: Failed to load PlayableAsset '{loadKey}'"); yield break; } CancelPendingResetToStartCoroutine(); director.stopped -= OnDirectorStopped; director.Stop(); _savedPlayableAsset = director.playableAsset; director.playableAsset = loadedAsset; director.RebuildGraph(); // 自动绑定 CinemachineBrain(必须在 RebuildGraph 之后) AutoBindCinemachineBrain(); ApplyPlaybackTimeZero(); _addressableHandle = loadedHandle; _onCompleteCallback = onComplete; if (ShouldSubscribeStopped(onComplete)) { director.stopped += OnDirectorStopped; } director.Play(); // 等待播放完成(与 PlayAsync 行为一致) while (director.state == PlayState.Playing) { yield return null; } } /// /// 停止播放 /// public void Stop() { if (director == null) return; CancelPendingResetToStartCoroutine(); // 先取消事件订阅,避免 Stop() 触发回调 director.stopped -= OnDirectorStopped; director.Stop(); _onCompleteCallback = null; ReleaseAddressableHandle(); } /// /// 暂停播放 /// public void Pause() { if (director == null) return; if (director.state == PlayState.Playing) { director.Pause(); } } /// /// 恢复播放 /// public void Resume() { if (director == null) return; if (director.state == PlayState.Paused) { director.Resume(); } } /// /// 重置 Timeline /// public void Reset() { if (director == null) return; CancelPendingResetToStartCoroutine(); // 先取消事件订阅,避免 Stop() 触发回调 director.stopped -= OnDirectorStopped; director.Stop(); ApplyPlaybackTimeZero(); _onCompleteCallback = null; ReleaseAddressableHandle(); } public TimelineEntrySnapshotDto CaptureSnapshotEntry() { var entry = new TimelineEntrySnapshotDto { directorName = timelineName, isActive = gameObject.activeSelf, loadedAddressableKey = _lastAddressableKey }; if (director == null) { entry.phase = TimelinePlaybackPhase.Stopped.ToString(); return entry; } var duration = director.duration; var time = director.time; if (duration > 0 && time >= duration - 0.01) { entry.phase = TimelinePlaybackPhase.AtEnd.ToString(); } else if (time <= 0.01) { entry.phase = TimelinePlaybackPhase.AtStart.ToString(); } else { entry.phase = TimelinePlaybackPhase.AtEnd.ToString(); } return entry; } public IEnumerator RestoreSnapshotEntryAsync(TimelineEntrySnapshotDto entry) { if (entry == null) yield break; gameObject.SetActive(entry.isActive); if (!entry.isActive || director == null) yield break; if (!Enum.TryParse(entry.phase, out var phase)) { phase = TimelinePlaybackPhase.Stopped; } switch (phase) { case TimelinePlaybackPhase.AtStart: Reset(); break; case TimelinePlaybackPhase.AtEnd: if (!string.IsNullOrEmpty(entry.loadedAddressableKey)) { yield return RestoreAtEndFromAddressable(entry.loadedAddressableKey); } else { RestoreAtEndLocal(); } break; default: Reset(); break; } } private void RestoreAtEndLocal() { CancelPendingResetToStartCoroutine(); director.stopped -= OnDirectorStopped; director.Stop(); if (director.duration > 0) { director.time = director.duration; director.Evaluate(); } director.Stop(); } private IEnumerator RestoreAtEndFromAddressable(string addressableKey) { yield return LoadAddressablePlayable(addressableKey); RestoreAtEndLocal(); } private IEnumerator LoadAddressablePlayable(string addressableKey) { if (director == null) { Debug.LogError($"DirectorHandler {timelineName}: PlayableDirector is null"); yield break; } if (string.IsNullOrEmpty(addressableKey)) { Debug.LogError($"DirectorHandler {timelineName}: addressableKey is null or empty"); yield break; } ReleaseAddressableHandle(); var loadKey = TimelinePrefix + addressableKey; _lastAddressableKey = addressableKey; PlayableAsset loadedAsset = null; AsyncOperationHandle loadedHandle = default; var loadDone = false; ResourceKit.LoadAssetAsyncWithHandle(loadKey, handle => { loadedHandle = handle; if (handle.Status == AsyncOperationStatus.Succeeded) { loadedAsset = handle.Result; } loadDone = true; }); yield return new WaitUntil(() => loadDone); if (loadedAsset == null) { Debug.LogError($"DirectorHandler {timelineName}: Failed to load PlayableAsset '{loadKey}'"); yield break; } CancelPendingResetToStartCoroutine(); director.stopped -= OnDirectorStopped; director.Stop(); _savedPlayableAsset = director.playableAsset; director.playableAsset = loadedAsset; director.RebuildGraph(); AutoBindCinemachineBrain(); _addressableHandle = loadedHandle; } /// /// 获取当前播放时间 /// public double GetPlaybackTime() { if (director == null) return 0; return director.time; } /// /// 获取总时长 /// public double GetDuration() { if (director == null) return 0; return director.duration; } /// /// 设置播放速度 /// public void SetPlaybackSpeed(float speed) { if (director == null) return; if (director.playableGraph.IsValid()) { director.playableGraph.GetRootPlayable(0).SetSpeed(speed); } else { Debug.LogWarning($"DirectorHandler {timelineName}: PlayableGraph is not valid. Cannot set playback speed."); } } /// /// 跳转到指定时间点 /// public void SeekToTime(double time) { if (director == null) return; director.time = Math.Clamp(time, 0, director.duration); director.Evaluate(); } /// /// Director 停止事件处理 /// private void OnDirectorStopped(PlayableDirector stoppedDirector) { // 立即取消订阅,避免重复触发 stoppedDirector.stopped -= OnDirectorStopped; var callback = _onCompleteCallback; _onCompleteCallback = null; ReleaseAddressableHandle(); callback?.Invoke(); ApplyEndBehaviorAfterStopped(); } } }