fix: Round 55 递归硬中止、存档加载缓存刷新、好感度空值防护、选项穿透延迟、分支对话去重
- QuestSO.HasPrerequisiteCycle/HasBranchCycle: depth>32 改为 LogError+return true 硬中止,防止栈溢出 - DialogueSequenceSO.HasChoiceCycle: 新增 depth 参数及 >32 硬中止,同时更新递归调用传 depth+1 - IQuestEventSource: 新增 OnAfterSaveLoaded 事件接口,供存档加载后统一刷新缓存 - QuestManager.OnLoad: 末尾触发 OnAfterSaveLoaded,确保所有缓存组件收到通知 - QuestGiver: 订阅 OnAfterSaveLoaded 设 _cacheDirty,存档恢复后 NPC 交互提示始终最新 - QuestManager.ApplyAffinity: 新增 giverNpc null 显式 LogWarning、maxAffinity<0 LogError 防护 - DialogueManager: 选项穿透防护改为预创建 WaitForSeconds(0.15f),替代 yield return null - QuestManager.UnlockBranches: 多分支同时满足时只播首个有对话的分支,防止重复播放 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -99,6 +99,8 @@ namespace BaseGames.Dialogue
|
||||
private WaitTypingOrSkip _waitTypingOrSkip;
|
||||
private WaitSkip _waitSkip;
|
||||
private WaitForChoice _waitForChoice;
|
||||
// 延迟 0.15s 防止玩家快速连击穿透:跳过打字机后立即触发选项0
|
||||
private WaitForSeconds _waitChoiceInputGuard;
|
||||
|
||||
/// <summary>
|
||||
/// 当 IsDialogueActive 时排队等待的对话请求。
|
||||
@@ -123,9 +125,10 @@ namespace BaseGames.Dialogue
|
||||
{
|
||||
if (ServiceLocator.GetOrDefault<IDialogueService>() != null) { Destroy(gameObject); return; }
|
||||
ServiceLocator.Register<IDialogueService>(this);
|
||||
_waitTypingOrSkip = new WaitTypingOrSkip(this);
|
||||
_waitSkip = new WaitSkip(this);
|
||||
_waitForChoice = new WaitForChoice(this);
|
||||
_waitTypingOrSkip = new WaitTypingOrSkip(this);
|
||||
_waitSkip = new WaitSkip(this);
|
||||
_waitForChoice = new WaitForChoice(this);
|
||||
_waitChoiceInputGuard = new WaitForSeconds(0.15f);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
@@ -390,9 +393,10 @@ namespace BaseGames.Dialogue
|
||||
// 清除打字机阶段积压的输入,防止选项显示后被立即误触发
|
||||
_skipRequested = false;
|
||||
_selectedChoiceIndex = -1;
|
||||
// 延迟一帧:确保此前积压的"确认键"输入在下一帧开始前已被消耗,
|
||||
// 防止快速连击(先跳过打字机→再误触发选项0)的穿透问题。
|
||||
yield return null;
|
||||
// 延迟 0.15s:确保此前积压的"确认键"输入已被彻底消耗,
|
||||
// 防止快速连击(跳过打字机→立即误选选项0)的穿透问题。
|
||||
// 使用预创建的缓存实例,避免每次分配 WaitForSeconds 对象。
|
||||
yield return _waitChoiceInputGuard;
|
||||
// 捕获当前播放标记,防止被打断后遗留回调写入新序列的选择索引
|
||||
int capturedId = _playbackId;
|
||||
_dialogueBox.ShowChoices(line.choices, idx =>
|
||||
|
||||
@@ -286,8 +286,13 @@ namespace BaseGames.Dialogue
|
||||
}
|
||||
|
||||
private static bool HasChoiceCycle(DialogueSequenceSO seq,
|
||||
System.Collections.Generic.HashSet<string> visited)
|
||||
System.Collections.Generic.HashSet<string> visited, int depth = 0)
|
||||
{
|
||||
if (depth > 32)
|
||||
{
|
||||
Debug.LogError($"[DialogueSequenceSO] 选项链深度超过 32 层(路径末端:'{seq.name}'),已视为存在循环引用并中止检测。请减少 nextSequence 嵌套层数。");
|
||||
return true;
|
||||
}
|
||||
if (string.IsNullOrEmpty(seq.sequenceId)) return false;
|
||||
if (!visited.Add(seq.sequenceId)) return true;
|
||||
if (seq.lines != null)
|
||||
@@ -297,7 +302,7 @@ namespace BaseGames.Dialogue
|
||||
if (line.choices == null) continue;
|
||||
foreach (var choice in line.choices)
|
||||
{
|
||||
if (choice.nextSequence != null && HasChoiceCycle(choice.nextSequence, visited))
|
||||
if (choice.nextSequence != null && HasChoiceCycle(choice.nextSequence, visited, depth + 1))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -307,7 +312,7 @@ namespace BaseGames.Dialogue
|
||||
{
|
||||
foreach (var variant in seq.variants)
|
||||
{
|
||||
if (variant.sequence != null && HasChoiceCycle(variant.sequence, visited))
|
||||
if (variant.sequence != null && HasChoiceCycle(variant.sequence, visited, depth + 1))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user