- 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.
116 lines
5.1 KiB
C#
116 lines
5.1 KiB
C#
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>
|
||
/// 获取最终使用的说话人名称 Key:actor 优先,回退到直接字段。
|
||
/// </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 都重扫所有同类 SO(O(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
|
||
}
|
||
}
|