using System.Collections; using System.Collections.Generic; using BaseGames.Core; using BaseGames.Core.Events; using BaseGames.Input; using BaseGames.World; using UnityEngine; namespace BaseGames.Dialogue { /// /// 对话管理器(架构 14_NarrativeModule §4)。 /// 驱动 DialogueUI 打字机效果;管理 Action Map 切换;向 QuestManager 广播对话完成事件。 /// 在 Awake 中注册到 ServiceLocator。 /// public class DialogueManager : MonoBehaviour, IDialogueService { [SerializeField] private DialogueUI _dialogueBox; [SerializeField] private InputReaderSO _inputReader; [SerializeField] private WorldStateRegistry _worldState; [Header("事件频道")] [SerializeField] private VoidEventChannelSO _onDialogueStarted; [SerializeField] private VoidEventChannelSO _onDialogueEnded; [SerializeField] private StringEventChannelSO _onNpcDialogueCompleted; // → EVT_NpcDialogueCompleted (npcId) [SerializeField] private VoidEventChannelSO _onLineStarted; // 每行开始打字时广播 [SerializeField] private VoidEventChannelSO _onLineEnded; // 每行玩家确认后广播 private bool _skipRequested; // ── 复用 Yield 指令,避免协程每行 new WaitUntil 闭包 ─────────────── private sealed class WaitTypingOrSkip : CustomYieldInstruction { private readonly DialogueManager _m; public WaitTypingOrSkip(DialogueManager m) => _m = m; public override bool keepWaiting => _m._dialogueBox.IsTyping && !_m._skipRequested; } private sealed class WaitSkip : CustomYieldInstruction { private readonly DialogueManager _m; public WaitSkip(DialogueManager m) => _m = m; public override bool keepWaiting => !_m._skipRequested; } private WaitTypingOrSkip _waitTypingOrSkip; private WaitSkip _waitSkip; /// /// 当 IsDialogueActive 时排队等待的对话请求。 /// 支持脚本触发的连续对话序列(如剧情链、事件链触发的对话), /// 但容量上限为 8,避免因误触导致无限排队。 /// private readonly Queue<(DialogueSequenceSO seq, string npcId)> _pending = new(); private const int PendingCapacity = 8; /// 当前是否有对话正在播放。 public bool IsDialogueActive { get; private set; } // ── 生命周期 ────────────────────────────────────────────────────── private void Awake() { if (ServiceLocator.GetOrDefault() != null) { Destroy(gameObject); return; } ServiceLocator.Register(this); _waitTypingOrSkip = new WaitTypingOrSkip(this); _waitSkip = new WaitSkip(this); } private void OnDestroy() { ServiceLocator.Unregister(this); } private void OnEnable() { if (_inputReader != null) _inputReader.SubmitEvent += OnSubmit; } private void OnDisable() { if (_inputReader != null) _inputReader.SubmitEvent -= OnSubmit; _pending.Clear(); // 若对话协程在组件禁用或场景切换时仍在运行,Unity 会强制杀死协程但不调用 // EndDialogue(),导致 Action Map 永久停留在 UI 模式。此处主动恢复。 if (IsDialogueActive) { StopAllCoroutines(); _dialogueBox?.Hide(); IsDialogueActive = false; _inputReader?.EnableGameplayInput(); } } // ── 公开 API ────────────────────────────────────────────────────── /// /// 启动对话序列。 /// 若已有对话在播放,请求会进入等待队列(上限 ), /// 待当前对话结束后依序自动播放;超出上限的请求被丢弃。 /// 由 InteractableNPC.Interact() 调用。 /// /// 要播放的对话序列 SO。 /// NPC 标识符,对话结束时随 EVT_NpcDialogueCompleted 广播。 public void StartDialogue(DialogueSequenceSO sequence, string npcId = "") { if (sequence == null) return; if (IsDialogueActive) { if (_pending.Count < PendingCapacity) _pending.Enqueue((sequence, npcId)); return; } PlayImmediate(sequence, npcId); } private void PlayImmediate(DialogueSequenceSO sequence, string npcId) { IsDialogueActive = true; _skipRequested = false; if (_inputReader != null) _inputReader.EnableUIInput(); _onDialogueStarted?.Raise(); StartCoroutine(PlaySequence(sequence, npcId)); } // ── 输入回调 ────────────────────────────────────────────────────── private void OnSubmit() => _skipRequested = true; // ── 内部协程 ────────────────────────────────────────────────────── private IEnumerator PlaySequence(DialogueSequenceSO sequence, string npcId) { var resolved = ResolveVariant(sequence); if (resolved.lines == null || resolved.lines.Length == 0) { EndDialogue(npcId); yield break; } foreach (var line in resolved.lines) { _skipRequested = false; _dialogueBox.ShowLine(line); _onLineStarted?.Raise(); // 等待打字完成,期间允许跳过 yield return _waitTypingOrSkip; if (_dialogueBox.IsTyping) _dialogueBox.SkipTyping(); // 等待玩家按 Submit 推进下一行 _skipRequested = false; yield return _waitSkip; _onLineEnded?.Raise(); } EndDialogue(npcId); } private void EndDialogue(string npcId) { _dialogueBox.Hide(); IsDialogueActive = false; if (_inputReader != null) _inputReader.EnableGameplayInput(); _onDialogueEnded?.Raise(); if (!string.IsNullOrEmpty(npcId)) _onNpcDialogueCompleted?.Raise(npcId); // 自动播放队首等待中的对话(脚本触发的连续序列) if (_pending.Count > 0) { var (seq, id) = _pending.Dequeue(); PlayImmediate(seq, id); } } /// /// 根据 ConditionalVariant 选择正确的序列版本。 /// 按顺序检查 variants:所有 requiredFlags 均满足的第一个变体胜出(AND 关系); /// 未注入 WorldStateRegistry 或无满足条件的变体时,返回原序列(默认)。 /// private DialogueSequenceSO ResolveVariant(DialogueSequenceSO sequence) { if (sequence.variants == null || sequence.variants.Length == 0) return sequence; if (_worldState != null) { foreach (var variant in sequence.variants) { if (variant.sequence == null) continue; if (variant.requiredFlags == null || variant.requiredFlags.Length == 0) return variant.sequence; // 无条件变体:直接采用 bool allMet = true; foreach (var flag in variant.requiredFlags) { if (!string.IsNullOrEmpty(flag) && !_worldState.HasFlag(flag)) { allMet = false; break; } } if (allMet) return variant.sequence; } } #if UNITY_EDITOR || DEVELOPMENT_BUILD else if (sequence.variants.Length > 0) { Debug.LogWarning( $"[DialogueManager] 序列 '{sequence.sequenceId}' 有 {sequence.variants.Length} 个条件变体," + "但 WorldStateRegistry 未注入,将使用默认序列。请检查 Inspector 中的 _worldState 字段。", this); } #endif return sequence; } } }