Files
zeling_v2/Assets/_Game/Scripts/Quest/QuestSO.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

127 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;
using UnityEngine;
using BaseGames.Dialogue;
namespace BaseGames.Quest
{
/// <summary>
/// 任务定义 SO架构 22_QuestChallengeModule §2
/// 资产路径: Assets/ScriptableObjects/Quest/Quest_{questId}.asset
/// </summary>
[CreateAssetMenu(menuName = "BaseGames/Quest/Quest")]
public class QuestSO : ScriptableObject
{
[Header("标识")]
public string questId; // 唯一 ID如 "Quest_FindMushroom"
[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 QuestSO[] prerequisiteQuests; // 所有前置任务 Completed 后才可接
public int minAffinityToAccept; // NPC 好感度门槛0 = 无限制)
[Header("奖励")]
public RewardSO reward;
[Header("失败条件(可选)")]
public bool canFail;
public QuestObjectiveSO failCondition;
[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 直接引用,无需字符串校验。
// 旧字段 npcDialogueKeyObsolete有值时提示迁移。
#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
{
/// <summary>若此前置任务已完成 → 走本分支null = 默认分支)。</summary>
public QuestSO conditionQuest;
public QuestSO nextQuest;
/// <summary>完成后触发的 NPC 对话序列(直接引用,避免手写 sequenceId 字符串出错)。</summary>
public DialogueSequenceSO npcDialogueSequence;
[System.Obsolete("已废弃,请改用 npcDialogueSequence直接 SO 引用)。保留字段以兼容现有资产序列化。")]
[HideInInspector]
public string npcDialogueKey;
}
}