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:
2026-05-24 00:36:11 +08:00
parent 520f84999b
commit 446fd5dcd0
22 changed files with 1908 additions and 101 deletions

View File

@@ -26,6 +26,11 @@ namespace BaseGames.Dialogue
private DialogueLine _currentLine;
private const float DefaultTypewriterDelay = 0.03f;
// 缓存单个 WaitForSecondsRealtime避免 TypeLine 每字符 new 分配。
// 当 delay 值改变时才重新创建(不同行可能有不同打字速度)。
private WaitForSecondsRealtime _cachedTypeDelay;
private float _cachedTypeDelayValue = -1f;
/// <summary>当前是否仍在执行打字机效果。</summary>
public bool IsTyping { get; private set; }
@@ -35,20 +40,22 @@ namespace BaseGames.Dialogue
public void ShowLine(DialogueLine line)
{
_currentLine = line;
_rootPanel.SetActive(true);
_continuePrompt.SetActive(false);
if (_rootPanel != null) _rootPanel.SetActive(true);
if (_continuePrompt != null) _continuePrompt.SetActive(false);
// 说话人名称
bool hasSpeaker = !string.IsNullOrEmpty(line.speakerNameKey);
// 说话人名称actor 优先,回退到直接字段)
string resolvedNameKey = line.ResolvedNameKey;
bool hasSpeaker = !string.IsNullOrEmpty(resolvedNameKey);
if (_speakerNamePanel != null) _speakerNamePanel.SetActive(hasSpeaker);
if (hasSpeaker && _speakerNameText != null)
_speakerNameText.text = LocalizationManager.Get(line.speakerNameKey, "Dialogue");
_speakerNameText.text = LocalizationManager.Get(resolvedNameKey, "Dialogue");
// 头像
// 头像actor 优先,回退到直接字段)
var resolvedPortrait = line.ResolvedPortrait;
if (_speakerPortrait != null)
{
_speakerPortrait.gameObject.SetActive(line.portraitSprite != null);
if (line.portraitSprite != null) _speakerPortrait.sprite = line.portraitSprite;
_speakerPortrait.gameObject.SetActive(resolvedPortrait != null);
if (resolvedPortrait != null) _speakerPortrait.sprite = resolvedPortrait;
}
// 语音播放
@@ -95,6 +102,14 @@ namespace BaseGames.Dialogue
{
IsTyping = true;
float delay = line.typewriterDelay > 0f ? line.typewriterDelay : DefaultTypewriterDelay;
// 复用缓存的 WaitForSecondsRealtime仅当 delay 值变化时才重新 new
if (_cachedTypeDelay == null || !Mathf.Approximately(_cachedTypeDelayValue, delay))
{
_cachedTypeDelay = new WaitForSecondsRealtime(delay);
_cachedTypeDelayValue = delay;
}
string text = LocalizationManager.Get(line.textKey ?? "", "Dialogue");
// 性能StringBuilder 避免每帧字符串分配O(n²) → O(n)
@@ -105,7 +120,7 @@ namespace BaseGames.Dialogue
{
sb.Append(c);
if (_dialogueText != null) _dialogueText.SetText(sb); // TMP SetText(StringBuilder) 零分配
yield return new WaitForSecondsRealtime(delay);
yield return _cachedTypeDelay;
}
IsTyping = false;