using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Playables;
namespace EventBossFight
{
///
/// 视频 Boss:待机与段落切换必须由 Timeline 驱动,不提供 Spine idle 名兜底。
/// Leviathan 轨请绑定仅用于本 Timeline的解码器。视频片段 Inspector 中「Timeline 多线程驱动」建议 DecodeThreadCatchUp(默认),行为接近 Match 待机多线程;若异常可改回 ProcessFrameLockstep。
///
public class EventBossFightTimelineCellVideo : MonoBehaviour
{
private CancellationTokenSource _playIdleDeferredCts;
[Header("Timeline 驱动(每分包 Cell 独立,与面板 enter 根 Director 解耦)")]
[Tooltip("未赋值则在 Awake 时 GetComponent()")]
[SerializeField]
protected PlayableDirector cellPlayableDirector;
protected GameObject idle;
protected GameObject death;
protected GameObject show;
[Tooltip("必须配置:待机循环由 idle Timeline 播放")]
public PlayableAsset idleTimeline;
public PlayableAsset deathTimeline;
/// 本分包 Boss 专用 PlayableDirector,播放入场 show / idle / 攻击等轨。
public PlayableDirector CellPlayableDirector =>
cellPlayableDirector ? cellPlayableDirector : GetComponent();
///
/// 先走 (与工程其它 UI 一致),找不到再在子层级中按名字深度查找(含未激活节点)。
///
protected static GameObject ResolveVideoLayer(Transform root, string layerName)
{
if (!root)
return null;
var go = root.gameObject;
var found = go.FindChildGameObject(layerName);
if (found)
return found;
foreach (var t in root.GetComponentsInChildren(true))
{
if (t == root)
continue;
if (t.name == layerName)
return t.gameObject;
}
return null;
}
protected virtual void Awake()
{
if (!cellPlayableDirector)
cellPlayableDirector = GetComponent();
idle ??= ResolveVideoLayer(transform, "idle");
death ??= ResolveVideoLayer(transform, "die");
show ??= ResolveVideoLayer(transform, "show");
}
public virtual void PlayIdle(PlayableDirector director)
{
if (!director)
return;
if (!idleTimeline)
{
Debug.LogError($"{name}: 视频 Boss 必须配置 idleTimeline", this);
return;
}
var ct = StartPlayIdleDeferredCancellation();
_ = PlayIdleDeferredAsync(director, ct);
}
/// 新一段 PlayIdle 时取消上一段;销毁时 会 Cancel。
protected CancellationToken StartPlayIdleDeferredCancellation()
{
_playIdleDeferredCts?.Cancel();
_playIdleDeferredCts = new CancellationTokenSource();
return _playIdleDeferredCts.Token;
}
protected void OnDestroy()
{
_playIdleDeferredCts?.Cancel();
_playIdleDeferredCts = null;
}
protected static async Task WaitPlayIdleFrameAsync(CancellationToken cancellationToken)
{
await Awaiters.NextFrame;
cancellationToken.ThrowIfCancellationRequested();
}
///
/// 先播 idle Timeline,等待若干帧后再关 show,减轻共用材质未就绪时的白屏。
///
private async Task PlayIdleDeferredAsync(PlayableDirector director, CancellationToken cancellationToken)
{
try
{
OnReturnedToIdleVisuals();
director.Play(idleTimeline, DirectorWrapMode.Loop);
await WaitPlayIdleFrameAsync(cancellationToken);
await WaitPlayIdleFrameAsync(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
if (show)
show.SetActive(false);
await WaitPlayIdleFrameAsync(cancellationToken);
OnReturnedToIdleVisuals();
}
catch (OperationCanceledException)
{
// 面板关闭或再次 PlayIdle 时取消,属正常情况
}
catch (Exception e)
{
Debug.LogException(e);
}
}
///
/// 进入待机 Director 或段落 Timeline 播完切回 idle 循环时调用:子类可隐藏攻击/受击等视频节点。
///
protected virtual void OnReturnedToIdleVisuals()
{
if (idle)
idle.SetActive(true);
}
///
/// Director.Play 后过一帧再调:覆盖 Timeline Activation Track 等对显隐的改写。
///
protected virtual void OnAfterDirectorPlayedFirstFrame(PlayableDirector director, PlayableAsset timeline)
{
}
/// 播放一段 Timeline 至结束(不切 idle);用于入场 show 等与 串联。
protected async Task PlayTimelineSegmentOnceAsync(PlayableDirector director, PlayableAsset timeline)
{
if (!director || !timeline)
return;
director.extrapolationMode = DirectorWrapMode.None;
director.Play(timeline, DirectorWrapMode.Hold);
await Awaiters.NextFrame;
OnAfterDirectorPlayedFirstFrame(director, timeline);
await WaitDirectorSegmentFinished(director, timeline);
}
protected async Task PlayTimeline(PlayableDirector director, PlayableAsset timeline)
{
if (!director || !timeline)
return;
director.Play(timeline, DirectorWrapMode.Hold);
await Awaiters.NextFrame;
OnAfterDirectorPlayedFirstFrame(director, timeline);
// PlayableAsset.duration 常与 Timeline 里 Clip 实际长度不一致,不能只用 Seconds 等
await WaitDirectorSegmentFinished(director, timeline);
if (timeline != deathTimeline)
{
if (!idleTimeline)
{
Debug.LogError($"{name}: 段落结束后回到待机需要 idleTimeline", this);
return;
}
director.Play(idleTimeline, DirectorWrapMode.Loop);
await Awaiters.NextFrame;
OnReturnedToIdleVisuals();
await Awaiters.NextFrame;
OnReturnedToIdleVisuals();
}
}
///
/// 等到当前 Director 上这一段 Hold 播完(用 time/duration/state,避免只靠 PlayableAsset.duration)。
///
private static async Task WaitDirectorSegmentFinished(PlayableDirector director, PlayableAsset timelineFallback)
{
await Awaiters.NextFrame;
float safety = Mathf.Max((float)(timelineFallback != null ? timelineFallback.duration : 1.0), 0.1f) + 8f;
float elapsed = 0f;
while (elapsed < safety)
{
if (director == null)
break;
// Play() 后图可能延迟一两帧才 IsValid;若此处直接 break 会把 show 等段落当成「已播完」并立刻进 idle。
if (director.playableGraph.IsValid() && IsDirectorSegmentFinished(director))
break;
await Awaiters.NextFrame;
elapsed += Time.deltaTime;
}
}
private static bool IsDirectorSegmentFinished(PlayableDirector dir)
{
if (dir == null)
return true;
if (dir.state != PlayState.Playing)
return true;
var dur = dir.duration;
if (dur > 0.001 && dir.time >= dur - 0.03)
return true;
return false;
}
public virtual async Task PlayTimelineDeath(PlayableDirector director)
{
if (idle)
idle.SetActive(false);
if (death)
death.SetActive(true);
await PlayTimeline(director, deathTimeline);
}
}
}