多轮审查和修复
This commit is contained in:
@@ -8,7 +8,12 @@
|
||||
"versionDefines": [],
|
||||
"rootNamespace": "BaseGames.Dialogue",
|
||||
"references": [
|
||||
"BaseGames.Core.Events"
|
||||
"BaseGames.Core",
|
||||
"BaseGames.Core.Events",
|
||||
"BaseGames.Input",
|
||||
"BaseGames.World",
|
||||
"Unity.TextMeshPro",
|
||||
"Unity.InputSystem"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"overrideReferences": false,
|
||||
|
||||
@@ -3,9 +3,7 @@ using UnityEngine;
|
||||
namespace BaseGames.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话数据 ScriptableObject(存根)。
|
||||
/// Phase 3 Dialogue 模块实现时填充完整字段(对话行、Speaker、选项分支等)。
|
||||
/// 此处仅声明类型,供 DialogueEventChannelSO 引用。
|
||||
/// 对话数据 ScriptableObject。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Dialogue/DialogueData")]
|
||||
public class DialogueDataSO : ScriptableObject
|
||||
@@ -17,7 +15,7 @@ namespace BaseGames.Dialogue
|
||||
public string speakerName;
|
||||
|
||||
[TextArea(3, 8)]
|
||||
[Tooltip("Phase 3 前的占位文本;正式内容在 DialogueLineSO[] 中定义。")]
|
||||
[Tooltip("对话内容占位文本,正式内容在 DialogueLineSO[] 中定义。")]
|
||||
public string placeholderText;
|
||||
}
|
||||
}
|
||||
|
||||
141
Assets/Scripts/Dialogue/DialogueManager.cs
Normal file
141
Assets/Scripts/Dialogue/DialogueManager.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Dialogue/DialogueManager.cs.meta
Normal file
11
Assets/Scripts/Dialogue/DialogueManager.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5eade47a9934aaf428bf94aa09b30e23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
41
Assets/Scripts/Dialogue/DialogueSequenceSO.cs
Normal file
41
Assets/Scripts/Dialogue/DialogueSequenceSO.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话行结构(架构 14_NarrativeModule §3)。
|
||||
/// 每行包含说话人、文本(本地化 Key)和可选的语音片段。
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct DialogueLine
|
||||
{
|
||||
public string speakerNameKey; // 本地化 key(如 "NPC_Elder_Name")
|
||||
[TextArea(2, 5)]
|
||||
public string textKey; // 本地化文本 key(如 "DLG_Elder_001")
|
||||
public Sprite portraitSprite; // 可选说话人头像
|
||||
public AudioClip voiceClip; // 可选语音
|
||||
[Min(0.01f)]
|
||||
public float typewriterDelay; // 每字符延迟(秒,0 = 使用默认 0.03f)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对话序列 SO(架构 14_NarrativeModule §3)。
|
||||
/// 一个 NPC 对话场合对应一个序列,由若干 DialogueLine 组成。
|
||||
/// 资产路径: Assets/ScriptableObjects/Dialogue/DLG_{NpcId}_{Context}.asset
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Dialogue/DialogueSequence")]
|
||||
public class DialogueSequenceSO : ScriptableObject
|
||||
{
|
||||
public string sequenceId; // 全局唯一,如 "DLG_Elder_Quest_Available"
|
||||
public DialogueLine[] lines;
|
||||
|
||||
/// <summary>条件变体:满足特定世界标志时替换整个序列。</summary>
|
||||
[System.Serializable]
|
||||
public struct ConditionalVariant
|
||||
{
|
||||
public string conditionFlag; // WorldState flag key
|
||||
public DialogueSequenceSO sequence;
|
||||
}
|
||||
public ConditionalVariant[] variants;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Dialogue/DialogueSequenceSO.cs.meta
Normal file
11
Assets/Scripts/Dialogue/DialogueSequenceSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 037a9d55368dde649ac6c1c6a1e80dad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
102
Assets/Scripts/Dialogue/DialogueUI.cs
Normal file
102
Assets/Scripts/Dialogue/DialogueUI.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BaseGames.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话框 UI 组件(架构 14_NarrativeModule §5)。
|
||||
/// 挂载在 Canvas_Overlay 下的 DialogueBox 子对象。
|
||||
/// 负责打字机效果、头像显示、继续提示。
|
||||
/// </summary>
|
||||
public class DialogueUI : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private GameObject _rootPanel;
|
||||
[SerializeField] private TMP_Text _speakerNameText;
|
||||
[SerializeField] private TMP_Text _dialogueText;
|
||||
[SerializeField] private GameObject _speakerNamePanel; // 无名称时隐藏整个名称框
|
||||
[SerializeField] private GameObject _continuePrompt; // "▼" 图标,打字完成后显示
|
||||
[SerializeField] private Image _speakerPortrait; // 角色头像框
|
||||
|
||||
private Coroutine _typingCoroutine;
|
||||
private DialogueLine _currentLine;
|
||||
private const float DefaultTypewriterDelay = 0.03f;
|
||||
|
||||
/// <summary>当前是否仍在执行打字机效果。</summary>
|
||||
public bool IsTyping { get; private set; }
|
||||
|
||||
// ── 公开 API ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>显示一行对话并开始打字机效果。</summary>
|
||||
public void ShowLine(DialogueLine line)
|
||||
{
|
||||
_currentLine = line;
|
||||
_rootPanel.SetActive(true);
|
||||
_continuePrompt.SetActive(false);
|
||||
|
||||
// 说话人名称
|
||||
bool hasSpeaker = !string.IsNullOrEmpty(line.speakerNameKey);
|
||||
if (_speakerNamePanel != null) _speakerNamePanel.SetActive(hasSpeaker);
|
||||
if (hasSpeaker && _speakerNameText != null)
|
||||
_speakerNameText.text = line.speakerNameKey;
|
||||
|
||||
// 头像
|
||||
if (_speakerPortrait != null)
|
||||
{
|
||||
_speakerPortrait.gameObject.SetActive(line.portraitSprite != null);
|
||||
if (line.portraitSprite != null) _speakerPortrait.sprite = line.portraitSprite;
|
||||
}
|
||||
|
||||
// 开始打字机协程
|
||||
if (_typingCoroutine != null) StopCoroutine(_typingCoroutine);
|
||||
_typingCoroutine = StartCoroutine(TypeLine(line));
|
||||
}
|
||||
|
||||
/// <summary>立即显示全部文字(跳过打字机效果)。</summary>
|
||||
public void SkipTyping()
|
||||
{
|
||||
if (!IsTyping) return;
|
||||
if (_typingCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_typingCoroutine);
|
||||
_typingCoroutine = null;
|
||||
}
|
||||
if (_dialogueText != null)
|
||||
_dialogueText.text = _currentLine.textKey ?? "";
|
||||
IsTyping = false;
|
||||
if (_continuePrompt != null) _continuePrompt.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>隐藏对话框面板。</summary>
|
||||
public void Hide()
|
||||
{
|
||||
if (_rootPanel != null) _rootPanel.SetActive(false);
|
||||
}
|
||||
|
||||
// ── 内部协程 ──────────────────────────────────────────────────────
|
||||
|
||||
private IEnumerator TypeLine(DialogueLine line)
|
||||
{
|
||||
IsTyping = true;
|
||||
float delay = line.typewriterDelay > 0f ? line.typewriterDelay : DefaultTypewriterDelay;
|
||||
string text = line.textKey ?? "";
|
||||
|
||||
// 性能:StringBuilder 避免每帧字符串分配(O(n²) → O(n))
|
||||
var sb = new StringBuilder(text.Length);
|
||||
if (_dialogueText != null) _dialogueText.text = "";
|
||||
|
||||
foreach (char c in text)
|
||||
{
|
||||
sb.Append(c);
|
||||
if (_dialogueText != null) _dialogueText.SetText(sb); // TMP SetText(StringBuilder) 零分配
|
||||
yield return new WaitForSecondsRealtime(delay);
|
||||
}
|
||||
|
||||
IsTyping = false;
|
||||
if (_continuePrompt != null) _continuePrompt.SetActive(true);
|
||||
_typingCoroutine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Dialogue/DialogueUI.cs.meta
Normal file
11
Assets/Scripts/Dialogue/DialogueUI.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 699768da82efa244f815d5dbce5b23dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
18
Assets/Scripts/Dialogue/IDialogueService.cs
Normal file
18
Assets/Scripts/Dialogue/IDialogueService.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace BaseGames.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话服务接口。通过 ServiceLocator 注册,供 NPC 和测试使用。
|
||||
/// </summary>
|
||||
public interface IDialogueService
|
||||
{
|
||||
/// <summary>当前是否有对话正在播放。</summary>
|
||||
bool IsDialogueActive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动对话序列。若已有对话在播放则忽略新请求。
|
||||
/// </summary>
|
||||
/// <param name="sequence">要播放的对话序列 SO。</param>
|
||||
/// <param name="npcId">NPC 标识符,对话结束时随 EVT_NpcDialogueCompleted 广播。</param>
|
||||
void StartDialogue(DialogueSequenceSO sequence, string npcId = "");
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Dialogue/IDialogueService.cs.meta
Normal file
11
Assets/Scripts/Dialogue/IDialogueService.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd651be055b226649b2594e0774e764d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
55
Assets/Scripts/Dialogue/InteractableNPC.cs
Normal file
55
Assets/Scripts/Dialogue/InteractableNPC.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.World;
|
||||
|
||||
namespace BaseGames.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// 可交互 NPC 基类(架构 14_NarrativeModule §5)。
|
||||
/// 实现 IInteractable,触发对话序列;子类(如 QuestGiver)可覆盖对话选择逻辑。
|
||||
/// 程序集: BaseGames.Dialogue
|
||||
/// </summary>
|
||||
public class InteractableNPC : MonoBehaviour, IInteractable
|
||||
{
|
||||
[SerializeField] protected string _npcId;
|
||||
[SerializeField] protected DialogueSequenceSO _defaultDialogue;
|
||||
[SerializeField] protected float _interactRadius = 1.5f;
|
||||
|
||||
// ── IInteractable ──────────────────────────────────────────────────
|
||||
public virtual bool CanInteract => true;
|
||||
public virtual string InteractPrompt => "对话";
|
||||
|
||||
public void Interact(Transform player)
|
||||
{
|
||||
if (!CanInteract) return;
|
||||
Interact_Internal(player);
|
||||
var dialogue = GetCurrentDialogue();
|
||||
if (dialogue != null)
|
||||
PlayDialogue(dialogue, player);
|
||||
}
|
||||
|
||||
public virtual void OnPlayerEnterRange(Transform player) { }
|
||||
public virtual void OnPlayerExitRange() { }
|
||||
|
||||
// ── 子类覆盖点 ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>交互前置逻辑(如任务接收/完成判断)。子类覆盖此方法。</summary>
|
||||
protected virtual void Interact_Internal(Transform player) { }
|
||||
|
||||
/// <summary>返回当前应播放的对话序列。子类根据任务状态返回不同版本。</summary>
|
||||
protected virtual DialogueSequenceSO GetCurrentDialogue() => _defaultDialogue;
|
||||
|
||||
// ── 对话播放 ───────────────────────────────────────────────────────
|
||||
|
||||
protected virtual void PlayDialogue(DialogueSequenceSO sequence, Transform player)
|
||||
{
|
||||
if (sequence == null) return;
|
||||
var manager = BaseGames.Core.ServiceLocator.GetOrDefault<IDialogueService>();
|
||||
if (manager == null)
|
||||
{
|
||||
Debug.LogWarning($"[InteractableNPC:{_npcId}] DialogueManager 未注册到 ServiceLocator,无法播放对话。");
|
||||
return;
|
||||
}
|
||||
manager.StartDialogue(sequence, _npcId);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Dialogue/InteractableNPC.cs.meta
Normal file
11
Assets/Scripts/Dialogue/InteractableNPC.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fd9677f057c21d4cbec1f99f76a13d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
45
Assets/Scripts/Dialogue/InteractionPromptController.cs
Normal file
45
Assets/Scripts/Dialogue/InteractionPromptController.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BaseGames.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// 交互提示 UI 控制器(架构 14_NarrativeModule §2)。
|
||||
/// 挂载在每个 IInteractable GameObject 的子节点(Prefab 实例),默认隐藏。
|
||||
/// 根据当前活跃输入设备自动切换图标(键盘/手柄)。
|
||||
/// </summary>
|
||||
public class InteractionPromptController : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private GameObject _promptRoot;
|
||||
[SerializeField] private Image _icon;
|
||||
[SerializeField] private Sprite _keyboardIcon;
|
||||
[SerializeField] private Sprite _gamepadIcon;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_promptRoot != null) _promptRoot.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>显示交互提示,根据输入设备选择图标。</summary>
|
||||
public void Show()
|
||||
{
|
||||
if (_promptRoot == null) return;
|
||||
_promptRoot.SetActive(true);
|
||||
UpdateIcon();
|
||||
}
|
||||
|
||||
/// <summary>隐藏交互提示。</summary>
|
||||
public void Hide()
|
||||
{
|
||||
if (_promptRoot != null) _promptRoot.SetActive(false);
|
||||
}
|
||||
|
||||
private void UpdateIcon()
|
||||
{
|
||||
if (_icon == null) return;
|
||||
bool isGamepad = Gamepad.current != null && Gamepad.current.enabled;
|
||||
_icon.sprite = isGamepad ? _gamepadIcon : _keyboardIcon;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Dialogue/InteractionPromptController.cs.meta
Normal file
11
Assets/Scripts/Dialogue/InteractionPromptController.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55e266aa215ebcf47b8c84b710bf03f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
67
Assets/Scripts/Dialogue/NarrativeNPC.cs
Normal file
67
Assets/Scripts/Dialogue/NarrativeNPC.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using BaseGames.World;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// 条件对话 NPC(架构 14_NarrativeModule §7)。
|
||||
/// 扩展 InteractableNPC,根据 WorldStateRegistry 标志动态选择对话版本。
|
||||
/// 版本列表从高到低优先级排列;第一个满足条件的版本生效。
|
||||
/// </summary>
|
||||
public class NarrativeNPC : InteractableNPC
|
||||
{
|
||||
[Header("台词版本集(从高到低优先级排列)")]
|
||||
[SerializeField] private DialogueVersion[] _dialogueVersions;
|
||||
[SerializeField] private DialogueSequenceSO _fallbackDialogue; // 无条件满足时的兜底台词
|
||||
[SerializeField] private WorldStateRegistry _worldState; // SO 注入
|
||||
|
||||
protected override DialogueSequenceSO GetCurrentDialogue()
|
||||
{
|
||||
if (_dialogueVersions == null) return _fallbackDialogue;
|
||||
|
||||
foreach (var version in _dialogueVersions)
|
||||
{
|
||||
if (version != null && version.CheckConditions(_worldState))
|
||||
return version.dialogue;
|
||||
}
|
||||
|
||||
return _fallbackDialogue;
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 一个对话版本及其激活条件(架构 14_NarrativeModule §7)。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class DialogueVersion
|
||||
{
|
||||
[Tooltip("编辑器显示名,如'森林 Boss 击败后'")]
|
||||
public string versionLabel;
|
||||
public DialogueSequenceSO dialogue;
|
||||
|
||||
[Tooltip("全部满足才激活此版本(AND 关系)")]
|
||||
public string[] requiredFlags;
|
||||
|
||||
[Tooltip("有任意一个 = 此版本不激活(NOT 关系)")]
|
||||
public string[] blockedByFlags;
|
||||
|
||||
/// <summary>检查此版本的激活条件(AND requiredFlags / NOT blockedByFlags)。</summary>
|
||||
public bool CheckConditions(WorldStateRegistry registry)
|
||||
{
|
||||
if (registry == null) return false;
|
||||
|
||||
if (requiredFlags != null)
|
||||
foreach (var f in requiredFlags)
|
||||
if (!registry.HasFlag(f)) return false;
|
||||
|
||||
if (blockedByFlags != null)
|
||||
foreach (var f in blockedByFlags)
|
||||
if (registry.HasFlag(f)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Dialogue/NarrativeNPC.cs.meta
Normal file
11
Assets/Scripts/Dialogue/NarrativeNPC.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59f5feba1a7232f4a8cd823402a5e512
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user