using BaseGames.Core.Events; using BaseGames.Input; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; namespace BaseGames.Cutscene { /// /// Unity Timeline 过场动画封装(架构 14_NarrativeModule §11)。 /// 负责播放/停止 CutsceneSO;切换 Action Map;广播过场开始/结束事件。 /// [RequireComponent(typeof(PlayableDirector))] public class CutsceneManager : MonoBehaviour { [SerializeField] private InputReaderSO _inputReader; [Header("事件频道")] [SerializeField] private VoidEventChannelSO _onCutsceneStarted; [SerializeField] private VoidEventChannelSO _onCutsceneEnded; [SerializeField] private StringEventChannelSO _onPlayCutsceneById; // 由 PlayCutsceneAction 触发 [Header("过场素材库(PlayById 查找用)")] [SerializeField] private CutsceneSO[] _registeredCutscenes; private PlayableDirector _director; private readonly CompositeDisposable _subs = new(); private System.Action _onCompletedCallback; /// 是否正在播放过场。 public bool IsPlaying => _director != null && _director.state == PlayState.Playing; // ── 生命周期 ────────────────────────────────────────────────────── private void Awake() { _director = GetComponent(); } private void OnEnable() { _onPlayCutsceneById?.Subscribe(PlayById).AddTo(_subs); } private void OnDisable() { _subs.Clear(); } // ── 公开 API ────────────────────────────────────────────────────── /// 通过 cutsceneId 查找并播放(供 PlayCutsceneAction 通过事件触发)。 public void PlayById(string cutsceneId) { if (_registeredCutscenes == null) return; foreach (var cs in _registeredCutscenes) if (cs != null && cs.cutsceneId == cutsceneId) { PlayCutscene(cs); return; } Debug.LogWarning($"[CutsceneManager] 找不到 cutsceneId='{cutsceneId}'"); } /// /// 播放指定过场 SO。 /// :过场完全结束(PlayableDirector.stopped)后调用, /// 可用于写入存档 flag 等需要在播完后执行的逻辑。 /// public void PlayCutscene(CutsceneSO cutscene, System.Action onCompleted = null) { if (cutscene == null || IsPlaying) return; _onCompletedCallback = onCompleted; _director.playableAsset = cutscene.Timeline; // 应用 Track → GameObject 绑定 if (cutscene.Bindings != null && cutscene.Timeline != null) { var outputs = cutscene.Timeline.outputs; int idx = 0; foreach (var output in outputs) { if (idx >= cutscene.Bindings.Length) break; var binding = cutscene.Bindings[idx]; if (!string.IsNullOrEmpty(binding.trackName) && output.streamName == binding.trackName && binding.target != null) { _director.SetGenericBinding(output.sourceObject, binding.target); } idx++; } } _director.stopped += OnCutsceneStopped; _director.Play(); _inputReader?.EnableUIInput(); _onCutsceneStarted?.Raise(); } /// 立即停止当前过场。 public void StopCutscene() { if (_director != null) _director.Stop(); } // ── 内部 ────────────────────────────────────────────────────────── private void OnCutsceneStopped(PlayableDirector director) { _director.stopped -= OnCutsceneStopped; _inputReader?.EnableGameplayInput(); _onCutsceneEnded?.Raise(); // 取出并调用完成回调(清空后调用,防止回调内再次触发 PlayCutscene 时覆盖) var cb = _onCompletedCallback; _onCompletedCallback = null; cb?.Invoke(); } } }