Files
aibis-dream/Assets/Scripts/SceneManagement/TimelineKit/DirectorHandler.cs
T

485 lines
15 KiB
C#

using System;
using System.Collections;
using Cinemachine;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.Timeline;
using AibisDream.Framework;
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<PlayableAsset> _addressableHandle;
private PlayableAsset _savedPlayableAsset;
private Coroutine _pendingResetToStartCoroutine;
#if UNITY_EDITOR
/// <summary>
/// 在 Editor 中,当组件创建或 Inspector 变化时自动查找同物体上的 PlayableDirector
/// </summary>
private void OnValidate()
{
if (director == null)
{
director = GetComponent<PlayableDirector>();
}
}
#endif
private void Awake()
{
if (director == null)
{
director = GetComponent<PlayableDirector>();
}
}
private void Start()
{
TimelineCenter.Instance.RegisterDirector(this);
}
private void OnDestroy()
{
TimelineCenter.Instance.UnregisterDirector(this);
CancelPendingResetToStartCoroutine();
if (director != null)
{
director.stopped -= OnDirectorStopped;
}
ReleaseAddressableHandle();
}
/// <summary>
/// 是否需要订阅 PlayableDirector.stopped(完成回调或需要播放结束行为时)。
/// </summary>
private bool ShouldSubscribeStopped(Action onComplete)
{
return onComplete != null || resetToStartWhenFinished;
}
private void CancelPendingResetToStartCoroutine()
{
if (_pendingResetToStartCoroutine == null) return;
StopCoroutine(_pendingResetToStartCoroutine);
_pendingResetToStartCoroutine = null;
}
/// <summary>
/// 将 Director 时间设到 0 并刷新一帧(不 Stop、不释放资源)。
/// </summary>
private void ApplyPlaybackTimeZero()
{
if (director == null) return;
director.time = 0;
director.Evaluate();
}
/// <summary>
/// 在 Release + 完成回调之后执行:按配置回到起点或延时回到起点。
/// </summary>
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;
}
/// <summary>
/// 自动绑定 CinemachineBrain 到 Timeline 中的所有 CinemachineTrack
/// </summary>
private void AutoBindCinemachineBrain()
{
Debug.Log($"[DirectorHandler] {timelineName}: AutoBindCinemachineBrain 被调用,autoBindCinemachineBrain={autoBindCinemachineBrain}");
if (!autoBindCinemachineBrain)
{
Debug.Log($"[DirectorHandler] {timelineName}: autoBindCinemachineBrain 为 false,跳过绑定");
return;
}
if (director == null)
{
Debug.LogError($"[DirectorHandler] {timelineName}: director 为 null");
return;
}
if (director.playableAsset == null)
{
Debug.LogError($"[DirectorHandler] {timelineName}: director.playableAsset 为 null");
return;
}
// 获取目标 CinemachineBrain
var mainCamera = Camera.main;
if (mainCamera == null)
{
Debug.LogError($"[DirectorHandler] {timelineName}: Camera.main 为 null");
return;
}
var brain = mainCamera.GetComponent<CinemachineBrain>();
if (brain == null)
{
Debug.LogWarning($"[DirectorHandler] {timelineName}: 主摄像机上找不到 CinemachineBrain,无法自动绑定");
return;
}
Debug.Log($"[DirectorHandler] {timelineName}: 找到 CinemachineBrain,准备绑定");
// 遍历所有输出轨道,找到 CinemachineTrack 并绑定
var timelineAsset = director.playableAsset as TimelineAsset;
if (timelineAsset == null)
{
Debug.LogError($"[DirectorHandler] {timelineName}: playableAsset 不是 TimelineAsset");
return;
}
int trackCount = 0;
int boundCount = 0;
foreach (var track in timelineAsset.GetOutputTracks())
{
trackCount++;
Debug.Log($"[DirectorHandler] {timelineName}: 检查轨道 {track.name},类型: {track.GetType().Name}");
// 直接检查轨道类型
if (track is CinemachineTrack)
{
director.SetGenericBinding(track, brain);
boundCount++;
Debug.Log($"[DirectorHandler] {timelineName}: 成功绑定轨道 {track.name}");
}
}
Debug.Log($"[DirectorHandler] {timelineName}: 共检查 {trackCount} 个轨道,绑定了 {boundCount} 个 CinemachineTrack");
}
/// <summary>
/// 播放 Timeline
/// </summary>
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();
}
/// <summary>
/// 异步播放 Timeline(协程版本)
/// </summary>
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;
}
}
/// <summary>
/// 从 Addressables 加载 PlayableAsset 并播放(加载时自动加 "Timeline/" 前缀)
/// </summary>
public void PlayFromAddressable(string addressableKey, Action onComplete = null)
{
StartCoroutine(PlayFromAddressableCoroutine(addressableKey, onComplete));
}
/// <summary>
/// 从 Addressables 加载 PlayableAsset 并播放(协程版本,可等待播完)
/// </summary>
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;
PlayableAsset loadedAsset = null;
AsyncOperationHandle<PlayableAsset> loadedHandle = default;
var loadDone = false;
ResourceKit.LoadAssetAsyncWithHandle<PlayableAsset>(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;
}
}
/// <summary>
/// 停止播放
/// </summary>
public void Stop()
{
if (director == null) return;
CancelPendingResetToStartCoroutine();
// 先取消事件订阅,避免 Stop() 触发回调
director.stopped -= OnDirectorStopped;
director.Stop();
_onCompleteCallback = null;
ReleaseAddressableHandle();
}
/// <summary>
/// 暂停播放
/// </summary>
public void Pause()
{
if (director == null) return;
if (director.state == PlayState.Playing)
{
director.Pause();
}
}
/// <summary>
/// 恢复播放
/// </summary>
public void Resume()
{
if (director == null) return;
if (director.state == PlayState.Paused)
{
director.Resume();
}
}
/// <summary>
/// 重置 Timeline
/// </summary>
public void Reset()
{
if (director == null) return;
CancelPendingResetToStartCoroutine();
// 先取消事件订阅,避免 Stop() 触发回调
director.stopped -= OnDirectorStopped;
director.Stop();
ApplyPlaybackTimeZero();
_onCompleteCallback = null;
ReleaseAddressableHandle();
}
/// <summary>
/// 获取当前播放时间
/// </summary>
public double GetPlaybackTime()
{
if (director == null) return 0;
return director.time;
}
/// <summary>
/// 获取总时长
/// </summary>
public double GetDuration()
{
if (director == null) return 0;
return director.duration;
}
/// <summary>
/// 设置播放速度
/// </summary>
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.");
}
}
/// <summary>
/// 跳转到指定时间点
/// </summary>
public void SeekToTime(double time)
{
if (director == null) return;
director.time = Math.Clamp(time, 0, director.duration);
director.Evaluate();
}
/// <summary>
/// Director 停止事件处理
/// </summary>
private void OnDirectorStopped(PlayableDirector stoppedDirector)
{
// 立即取消订阅,避免重复触发
stoppedDirector.stopped -= OnDirectorStopped;
var callback = _onCompleteCallback;
_onCompleteCallback = null;
ReleaseAddressableHandle();
callback?.Invoke();
ApplyEndBehaviorAfterStopped();
}
}
}