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.
This commit is contained in:
@@ -13,17 +13,19 @@ namespace BaseGames.Quest
|
||||
{
|
||||
[Header("标识")]
|
||||
public string questId; // 唯一 ID,如 "Quest_FindMushroom"
|
||||
public string displayName;
|
||||
[TextArea(2, 6)]
|
||||
public string description;
|
||||
|
||||
[Tooltip("本地化 Key,格式如 \"Quest_FindMushroom_Name\"。通过 LocalizationManager.Get(displayNameKey, \"Quest\") 显示。")]
|
||||
public string displayNameKey;
|
||||
[Tooltip("本地化 Key,格式如 \"Quest_FindMushroom_Desc\"。通过 LocalizationManager.Get(descriptionKey, \"Quest\") 显示。")]
|
||||
public string descriptionKey;
|
||||
public Sprite icon;
|
||||
|
||||
[Header("目标链")]
|
||||
public QuestObjectiveSO[] objectives; // 按顺序完成,全部完成 = 可交完
|
||||
|
||||
[Header("前置条件")]
|
||||
public string[] prerequisiteQuestIds; // 所有前置任务 Completed 后才可接
|
||||
public int minAffinityToAccept; // NPC 好感度门槛(0 = 无限制)
|
||||
public QuestSO[] prerequisiteQuests; // 所有前置任务 Completed 后才可接
|
||||
public int minAffinityToAccept; // NPC 好感度门槛(0 = 无限制)
|
||||
|
||||
[Header("奖励")]
|
||||
public RewardSO reward;
|
||||
@@ -34,13 +36,91 @@ namespace BaseGames.Quest
|
||||
|
||||
[Header("完成后续任务(分支)")]
|
||||
public QuestBranch[] branches;
|
||||
|
||||
// ── 编辑器校验 ────────────────────────────────────────────────────────
|
||||
#if UNITY_EDITOR
|
||||
// questId → 资产路径,5 秒 TTL,跨所有 QuestSO.OnValidate 共用。
|
||||
// 重复检测时只需将缓存路径与自身路径比对(O(1)),无需全量扫描。
|
||||
private static System.Collections.Generic.Dictionary<string, string> s_questIdToPath;
|
||||
private static double s_questIdsCacheTime = -10.0;
|
||||
|
||||
private static System.Collections.Generic.Dictionary<string, string> GetQuestIdCache()
|
||||
{
|
||||
double now = UnityEditor.EditorApplication.timeSinceStartup;
|
||||
if (s_questIdToPath != null && now - s_questIdsCacheTime < 5.0)
|
||||
return s_questIdToPath;
|
||||
|
||||
s_questIdToPath = new System.Collections.Generic.Dictionary<string, string>(System.StringComparer.Ordinal);
|
||||
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:QuestSO");
|
||||
foreach (var guid in guids)
|
||||
{
|
||||
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
|
||||
var q = UnityEditor.AssetDatabase.LoadAssetAtPath<QuestSO>(path);
|
||||
if (q != null && !string.IsNullOrEmpty(q.questId) && !s_questIdToPath.ContainsKey(q.questId))
|
||||
s_questIdToPath[q.questId] = path;
|
||||
}
|
||||
s_questIdsCacheTime = now;
|
||||
return s_questIdToPath;
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(questId))
|
||||
{
|
||||
Debug.LogWarning($"[QuestSO] '{name}' 缺少 questId,保存前请填写。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测重复 questId:缓存路径 vs 自身路径比对(O(1)),5 秒内无需重扫。
|
||||
var cache = GetQuestIdCache();
|
||||
string myPath = UnityEditor.AssetDatabase.GetAssetPath(this);
|
||||
if (!string.IsNullOrEmpty(myPath) &&
|
||||
cache.TryGetValue(questId, out var existingPath) &&
|
||||
existingPath != myPath)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[QuestSO] questId '{questId}' 与 " +
|
||||
$"'{System.IO.Path.GetFileNameWithoutExtension(existingPath)}' 重复!请修改其中一个。", this);
|
||||
s_questIdsCacheTime = -10.0;
|
||||
}
|
||||
|
||||
ValidateBranchDialogueKeys();
|
||||
}
|
||||
|
||||
private void ValidateBranchDialogueKeys()
|
||||
{
|
||||
if (branches == null || branches.Length == 0) return;
|
||||
|
||||
foreach (var branch in branches)
|
||||
{
|
||||
if (branch == null) continue;
|
||||
|
||||
// npcDialogueSequence 是 SO 直接引用,无需字符串校验。
|
||||
// 旧字段 npcDialogueKey(Obsolete)有值时提示迁移。
|
||||
#pragma warning disable CS0618
|
||||
if (!string.IsNullOrEmpty(branch.npcDialogueKey) && branch.npcDialogueSequence == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[QuestSO] '{name}' 分支仍使用旧字段 npcDialogueKey='{branch.npcDialogueKey}'," +
|
||||
"请迁移至 npcDialogueSequence(直接拖入 DialogueSequenceSO)。", this);
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class QuestBranch
|
||||
{
|
||||
public string conditionQuestId; // 若此任务已完成 → 走本分支(空 = 默认)
|
||||
public QuestSO nextQuest;
|
||||
public string npcDialogueKey; // 触发 NPC 对话 key
|
||||
/// <summary>若此前置任务已完成 → 走本分支(null = 默认分支)。</summary>
|
||||
public QuestSO conditionQuest;
|
||||
public QuestSO nextQuest;
|
||||
/// <summary>完成后触发的 NPC 对话序列(直接引用,避免手写 sequenceId 字符串出错)。</summary>
|
||||
public DialogueSequenceSO npcDialogueSequence;
|
||||
|
||||
[System.Obsolete("已废弃,请改用 npcDialogueSequence(直接 SO 引用)。保留字段以兼容现有资产序列化。")]
|
||||
[HideInInspector]
|
||||
public string npcDialogueKey;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user