Files
zeling_v2/Assets/Scripts/Dialogue/DialogueManager.cs
2026-05-12 15:34:08 +08:00

142 lines
5.5 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 System.Collections;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Input;
using BaseGames.World;
using UnityEngine;
namespace BaseGames.Dialogue
{
/// <summary>
/// 对话管理器(架构 14_NarrativeModule §4
/// 驱动 DialogueUI 打字机效果;管理 Action Map 切换;向 QuestManager 广播对话完成事件。
/// 在 Awake 中注册到 ServiceLocator。
/// </summary>
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)
private bool _skipRequested;
/// <summary>当前是否有对话正在播放。</summary>
public bool IsDialogueActive { get; private set; }
// ── 生命周期 ──────────────────────────────────────────────────────
private void Awake()
{
if (ServiceLocator.GetOrDefault<IDialogueService>() != null) { Destroy(gameObject); return; }
ServiceLocator.Register<IDialogueService>(this);
}
private void OnDestroy()
{
ServiceLocator.Unregister<IDialogueService>(this);
}
private void OnEnable()
{
if (_inputReader != null) _inputReader.SubmitEvent += OnSubmit;
}
private void OnDisable()
{
if (_inputReader != null) _inputReader.SubmitEvent -= OnSubmit;
}
// ── 公开 API ──────────────────────────────────────────────────────
/// <summary>
/// 启动对话序列。若已有对话在播放则忽略新请求。
/// 由 InteractableNPC.Interact() 调用。
/// </summary>
/// <param name="sequence">要播放的对话序列 SO。</param>
/// <param name="npcId">NPC 标识符,对话结束时随 EVT_NpcDialogueCompleted 广播。</param>
public void StartDialogue(DialogueSequenceSO sequence, string npcId = "")
{
if (IsDialogueActive || sequence == null) return;
IsDialogueActive = true;
_skipRequested = false;
// 切换到 UI Action Map禁用玩家移动输入
_inputReader.EnableUIInput();
_onDialogueStarted?.Raise();
StartCoroutine(PlaySequence(sequence, npcId));
}
// ── 输入回调 ──────────────────────────────────────────────────────
private void OnSubmit() => _skipRequested = true;
// ── 内部协程 ──────────────────────────────────────────────────────
private IEnumerator PlaySequence(DialogueSequenceSO sequence, string npcId)
{
// 选择条件变体(查询 ConditionalVariant.conditionFlag — 未实现 WorldStateRegistry 查询时直接使用默认序列)
var resolved = ResolveVariant(sequence);
foreach (var line in resolved.lines)
{
_skipRequested = false;
_dialogueBox.ShowLine(line);
// 等待打字完成,期间允许跳过
yield return new WaitUntil(() => !_dialogueBox.IsTyping || _skipRequested);
if (_dialogueBox.IsTyping) _dialogueBox.SkipTyping();
// 等待玩家按 Submit 推进下一行
_skipRequested = false;
yield return new WaitUntil(() => _skipRequested);
}
EndDialogue(npcId);
}
private void EndDialogue(string npcId)
{
_dialogueBox.Hide();
IsDialogueActive = false;
// 恢复 Gameplay Action Map
_inputReader.EnableGameplayInput();
_onDialogueEnded?.Raise();
if (!string.IsNullOrEmpty(npcId))
_onNpcDialogueCompleted?.Raise(npcId);
}
/// <summary>
/// 根据 ConditionalVariant 选择正确的序列版本。
/// 按顺序检查 variants第一个满足 WorldStateRegistry 标志的变体胜出;
/// 未注入 WorldStateRegistry 或无满足条件的变体时,返回原序列(默认)。
/// </summary>
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 (!string.IsNullOrEmpty(variant.conditionFlag)
&& variant.sequence != null
&& _worldState.HasFlag(variant.conditionFlag))
return variant.sequence;
}
}
return sequence;
}
}
}