Files
zeling_v2/Assets/_Game/Scripts/Dialogue/DialogueSequenceSO.cs
Joywayer 446fd5dcd0 feat: Add WorldStateFlagAttribute and custom property drawer for enhanced dialogue management
- Implemented WorldStateFlagAttribute to mark string fields as world state flags.
- Created NarrativeNPCEditor for custom inspector to visualize dialogue version activation states.
- Developed WorldStateFlagDrawer to provide dropdown menu for known flags in the inspector.
- Introduced ActorModule for managing DialogueActorSO assets, including viewing, creating, and deleting actors.
- Added DialogueModule for managing DialogueSequenceSO assets with detailed previews and action bars.
- Established QuestModule for managing QuestSO assets, including objectives and branches.
- Implemented QuestManagerPostprocessor to automatically refresh QuestManager's quest list on asset changes.
2026-05-24 00:36:11 +08:00

116 lines
5.1 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 UnityEngine;
namespace BaseGames.Dialogue
{
/// <summary>
/// 对话行结构(架构 14_NarrativeModule §3
/// 每行包含说话人、文本(本地化 Key和可选的语音片段。
///
/// 推荐通过 actor 引用 DialogueActorSO 统一管理头像/名称;
/// speakerNameKey / portraitSprite 作为无 SO 时的直接覆盖(保持向后兼容)。
/// </summary>
[System.Serializable]
public struct DialogueLine
{
[Tooltip("说话人角色推荐。actor 优先;留空时回退到 speakerNameKey / portraitSprite")]
public DialogueActorSO actor;
[Tooltip("说话人本地化 key留空时使用 actor.nameKey")]
public string speakerNameKey;
[TextArea(2, 5)]
public string textKey; // 本地化文本 key如 "DLG_Elder_001"
[Tooltip("说话人头像,留空时使用 actor.portrait")]
public Sprite portraitSprite;
public AudioClip voiceClip; // 可选语音
[Min(0.01f)]
public float typewriterDelay; // 每字符延迟0 = 使用默认 0.03f
/// <summary>
/// 获取最终使用的说话人名称 Keyactor 优先,回退到直接字段。
/// </summary>
public string ResolvedNameKey => actor != null && !string.IsNullOrEmpty(actor.nameKey)
? actor.nameKey : speakerNameKey;
/// <summary>
/// 获取最终使用的头像actor 优先,回退到直接字段。
/// </summary>
public Sprite ResolvedPortrait => actor != null && actor.portrait != null
? actor.portrait : portraitSprite;
}
/// <summary>
/// 对话序列 SO架构 14_NarrativeModule §3
/// 一个 NPC 对话场合对应一个序列,由若干 DialogueLine 组成。
/// 资产路径: Assets/ScriptableObjects/Dialogue/DLG_{NpcId}_{Context}.asset
/// </summary>
[CreateAssetMenu(menuName = "BaseGames/Dialogue/DialogueSequence")]
public class DialogueSequenceSO : ScriptableObject
{
public string sequenceId; // 全局唯一,如 "DLG_Elder_Quest_Available"
public DialogueLine[] lines;
/// <summary>
/// 条件变体:所有 requiredFlags 均满足时替换整个序列AND 关系)。
/// 与 NarrativeNPC.DialogueVersion 的多条件语义保持一致。
/// </summary>
[System.Serializable]
public struct ConditionalVariant
{
[Tooltip("全部满足时激活此变体AND 关系)。留空表示无条件。")]
[WorldStateFlag]
public string[] requiredFlags;
public DialogueSequenceSO sequence;
}
public ConditionalVariant[] variants;
#if UNITY_EDITOR
// sequenceId → 资产路径5 秒 TTL跨所有 DialogueSequenceSO.OnValidate 共用,
// 避免每次 Save 都重扫所有同类 SOO(1) 路径比对代替 O(n) 全量扫描)。
private static System.Collections.Generic.Dictionary<string, string> s_seqIdToPath;
private static double s_seqIdsCacheTime = -10.0;
private static System.Collections.Generic.Dictionary<string, string> GetSequenceIdCache()
{
double now = UnityEditor.EditorApplication.timeSinceStartup;
if (s_seqIdToPath != null && now - s_seqIdsCacheTime < 5.0)
return s_seqIdToPath;
s_seqIdToPath = new System.Collections.Generic.Dictionary<string, string>(System.StringComparer.Ordinal);
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:DialogueSequenceSO");
foreach (var guid in guids)
{
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
var seq = UnityEditor.AssetDatabase.LoadAssetAtPath<DialogueSequenceSO>(path);
if (seq != null && !string.IsNullOrEmpty(seq.sequenceId) && !s_seqIdToPath.ContainsKey(seq.sequenceId))
s_seqIdToPath[seq.sequenceId] = path;
}
s_seqIdsCacheTime = now;
return s_seqIdToPath;
}
private void OnValidate()
{
if (string.IsNullOrEmpty(sequenceId))
{
sequenceId = name;
UnityEditor.EditorUtility.SetDirty(this);
}
// 检测重复 sequenceId缓存路径 vs 自身路径比对O(1)5 秒内无需重扫。
var cache = GetSequenceIdCache();
string myPath = UnityEditor.AssetDatabase.GetAssetPath(this);
if (!string.IsNullOrEmpty(myPath) &&
cache.TryGetValue(sequenceId, out var existingPath) &&
existingPath != myPath)
{
Debug.LogError(
$"[DialogueSequenceSO] sequenceId '{sequenceId}' 与 " +
$"'{System.IO.Path.GetFileNameWithoutExtension(existingPath)}' 重复!请修改其中一个。", this);
s_seqIdsCacheTime = -10.0;
}
}
#endif
}
}