fix(scene-mgmt): 修复 Timeline 播放结束后可选回到起点

Made-with: Cursor
This commit is contained in:
2026-03-25 14:44:33 +08:00
parent 0d72d747e8
commit 7a40dcd165
@@ -20,9 +20,17 @@ namespace AibisDream
[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>
@@ -54,6 +62,8 @@ namespace AibisDream
{
TimelineCenter.Instance.UnregisterDirector(this);
CancelPendingResetToStartCoroutine();
if (director != null)
{
director.stopped -= OnDirectorStopped;
@@ -62,6 +72,58 @@ namespace AibisDream
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;
@@ -156,6 +218,7 @@ namespace AibisDream
}
// 先停止再播放
CancelPendingResetToStartCoroutine();
director.stopped -= OnDirectorStopped;
if (director.state == PlayState.Playing)
{
@@ -164,8 +227,7 @@ namespace AibisDream
_onCompleteCallback = onComplete;
// 如果有完成回调,订阅 stopped 事件
if (onComplete != null)
if (ShouldSubscribeStopped(onComplete))
{
director.stopped += OnDirectorStopped;
}
@@ -187,12 +249,18 @@ namespace AibisDream
yield break;
}
// 先停止再播放
CancelPendingResetToStartCoroutine();
director.stopped -= OnDirectorStopped;
if (director.state == PlayState.Playing)
{
director.Stop();
}
if (ShouldSubscribeStopped(null))
{
director.stopped += OnDirectorStopped;
}
// 自动绑定 CinemachineBrain
AutoBindCinemachineBrain();
@@ -259,6 +327,7 @@ namespace AibisDream
yield break;
}
CancelPendingResetToStartCoroutine();
director.stopped -= OnDirectorStopped;
if (director.state == PlayState.Playing)
{
@@ -276,7 +345,7 @@ namespace AibisDream
_addressableHandle = loadedHandle;
_onCompleteCallback = onComplete;
if (onComplete != null)
if (ShouldSubscribeStopped(onComplete))
{
director.stopped += OnDirectorStopped;
}
@@ -297,6 +366,8 @@ namespace AibisDream
{
if (director == null) return;
CancelPendingResetToStartCoroutine();
// 先取消事件订阅,避免 Stop() 触发回调
director.stopped -= OnDirectorStopped;
@@ -338,12 +409,13 @@ namespace AibisDream
{
if (director == null) return;
CancelPendingResetToStartCoroutine();
// 先取消事件订阅,避免 Stop() 触发回调
director.stopped -= OnDirectorStopped;
director.Stop();
director.time = 0;
director.Evaluate();
ApplyPlaybackTimeZero();
_onCompleteCallback = null;
ReleaseAddressableHandle();
@@ -398,16 +470,18 @@ namespace AibisDream
/// <summary>
/// Director 停止事件处理
/// </summary>
private void OnDirectorStopped(PlayableDirector director)
private void OnDirectorStopped(PlayableDirector stoppedDirector)
{
// 立即取消订阅,避免重复触发
director.stopped -= OnDirectorStopped;
stoppedDirector.stopped -= OnDirectorStopped;
var callback = _onCompleteCallback;
_onCompleteCallback = null;
ReleaseAddressableHandle();
callback?.Invoke();
ApplyEndBehaviorAfterStopped();
}
}
}