108 lines
4.1 KiB
C#
108 lines
4.1 KiB
C#
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();
|
||
|
||
/// <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。</summary>
|
||
public void PlayCutscene(CutsceneSO cutscene)
|
||
{
|
||
if (cutscene == null || IsPlaying) return;
|
||
_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();
|
||
}
|
||
}
|
||
}
|