Files
zeling_v2/Assets/_Game/Scripts/Cutscene/CutsceneManager.cs

118 lines
4.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using BaseGames.Core.Events;
using BaseGames.Input;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
namespace BaseGames.Cutscene
{
/// <summary>
/// Unity Timeline 过场动画封装(架构 14_NarrativeModule §11
/// 负责播放/停止 CutsceneSO切换 Action Map广播过场开始/结束事件。
/// </summary>
[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;
/// <summary>是否正在播放过场。</summary>
public bool IsPlaying => _director != null && _director.state == PlayState.Playing;
// ── 生命周期 ──────────────────────────────────────────────────────
private void Awake()
{
_director = GetComponent<PlayableDirector>();
}
private void OnEnable()
{
_onPlayCutsceneById?.Subscribe(PlayById).AddTo(_subs);
}
private void OnDisable()
{
_subs.Clear();
}
// ── 公开 API ──────────────────────────────────────────────────────
/// <summary>通过 cutsceneId 查找并播放(供 PlayCutsceneAction 通过事件触发)。</summary>
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}'");
}
/// <summary>
/// 播放指定过场 SO。
/// <paramref name="onCompleted"/>过场完全结束PlayableDirector.stopped后调用
/// 可用于写入存档 flag 等需要在播完后执行的逻辑。
/// </summary>
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();
}
/// <summary>立即停止当前过场。</summary>
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();
}
}
}