feat: Round 48 narrative systems improvements
- QuestSO: Add ValidateBranchCycles() DFS detection for branches[].nextQuest loop - QuestSO: Mark three legacy prerequisite fields with v2.0 removal warning in Tooltip - IQuestManager: Add QuestLockReason enum + QuestLockInfo struct (strongly-typed lock info) - IQuestManager: Add GetQuestLockInfo() method to interface; GetQuestLockReason() now delegates to it - IQuestEventSource: Add OnQuestStateChanged(questId, oldState, newState) unified event - QuestManager: Implement GetQuestLockInfo(); fire OnQuestStateChanged on all state transitions - DialogueManager: Add one-frame yield in HandleChoices before ShowChoices (skip-debounce fix) - DialogueManager: Increment _playbackId in ForceEnd() to invalidate residual choice callbacks - DialogueSequenceSO: Add UNITY_EDITOR debug log in TryGetActiveVariant on variant match - WorldStateRegistry: Add OnBatchStateChanged event + BatchMark() batch-write API - DialogueModule: List badge shows warning indicator for unconditional-shadowing variants - DialogueModule: BuildVariantsCard shows logic mode (AND/OR) alongside flag conditions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -37,6 +37,9 @@ namespace BaseGames.Editor
|
||||
private IDataModule _activeModule;
|
||||
|
||||
private VisualElement _navSidebar;
|
||||
// NavBar 搜索:按 DisplayName 过滤可见模块
|
||||
private string _navFilter = "";
|
||||
private readonly Dictionary<string, Button> _navButtons = new();
|
||||
|
||||
// 缓存:列表区和详情区引用(由 TwoPaneSplitView 子节点提供)
|
||||
private VisualElement _listWrapper;
|
||||
@@ -70,16 +73,31 @@ namespace BaseGames.Editor
|
||||
private void RegisterModules()
|
||||
{
|
||||
_modules.Clear();
|
||||
_modules.Add(new WeaponModule());
|
||||
_modules.Add(new SkillModule());
|
||||
_modules.Add(new EnemyModule());
|
||||
_modules.Add(new FormModule());
|
||||
_modules.Add(new BossSkillModule());
|
||||
_modules.Add(new CharmModule());
|
||||
_modules.Add(new StreamingModule());
|
||||
_modules.Add(new DialogueModule());
|
||||
_modules.Add(new QuestModule());
|
||||
_modules.Add(new ActorModule());
|
||||
|
||||
// 自动发现所有实现 IDataModule 的非抽象类型(排除接口/抽象类本身)
|
||||
// TypeCache 仅在 UnityEditor 中可用,零运行时开销
|
||||
var moduleTypes = UnityEditor.TypeCache.GetTypesDerivedFrom<IDataModule>();
|
||||
var instances = new List<IDataModule>(moduleTypes.Count);
|
||||
foreach (var t in moduleTypes)
|
||||
{
|
||||
if (t.IsAbstract || t.IsInterface) continue;
|
||||
try { instances.Add((IDataModule)Activator.CreateInstance(t)); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[DataHubWindow] 无法实例化模块 {t.Name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 按 DisplayOrder 升序排列(若模块实现了 IDataModuleOrdered 则使用其值,否则默认 0)
|
||||
instances.Sort((a, b) =>
|
||||
{
|
||||
int oa = a is IDataModuleOrdered ao ? ao.DisplayOrder : 0;
|
||||
int ob = b is IDataModuleOrdered bo ? bo.DisplayOrder : 0;
|
||||
int c = oa.CompareTo(ob);
|
||||
return c != 0 ? c : string.Compare(a.DisplayName, b.DisplayName, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
_modules.AddRange(instances);
|
||||
}
|
||||
|
||||
// ── 布局 ─────────────────────────────────────────────────────────────
|
||||
@@ -136,13 +154,31 @@ namespace BaseGames.Editor
|
||||
title.style.fontSize = 10;
|
||||
title.style.opacity = 0.5f;
|
||||
title.style.paddingLeft = 10;
|
||||
title.style.marginBottom = 6;
|
||||
title.style.marginBottom = 4;
|
||||
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
sidebar.Add(title);
|
||||
|
||||
// 搜索框
|
||||
var searchField = new TextField();
|
||||
searchField.style.marginLeft = 6;
|
||||
searchField.style.marginRight = 6;
|
||||
searchField.style.marginBottom = 6;
|
||||
searchField.tooltip = "按模块名称过滤";
|
||||
// 自定义占位符效果
|
||||
if (string.IsNullOrEmpty(searchField.value))
|
||||
searchField.value = "";
|
||||
searchField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
_navFilter = evt.newValue ?? "";
|
||||
ApplyNavFilter();
|
||||
});
|
||||
sidebar.Add(searchField);
|
||||
|
||||
_navButtons.Clear();
|
||||
foreach (var module in _modules)
|
||||
{
|
||||
var btn = BuildNavItem(module);
|
||||
_navButtons[module.ModuleId] = btn;
|
||||
sidebar.Add(btn);
|
||||
}
|
||||
|
||||
@@ -154,6 +190,18 @@ namespace BaseGames.Editor
|
||||
return sidebar;
|
||||
}
|
||||
|
||||
private void ApplyNavFilter()
|
||||
{
|
||||
foreach (var module in _modules)
|
||||
{
|
||||
if (!_navButtons.TryGetValue(module.ModuleId, out var btn)) continue;
|
||||
bool visible = string.IsNullOrEmpty(_navFilter) ||
|
||||
module.DisplayName.IndexOf(_navFilter, System.StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
module.ModuleId.IndexOf(_navFilter, System.StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
btn.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
|
||||
private Button BuildNavItem(IDataModule module)
|
||||
{
|
||||
var btn = new Button(() => ActivateModule(module));
|
||||
|
||||
@@ -25,4 +25,17 @@ namespace BaseGames.Editor
|
||||
/// <summary>切换到本模块时调用,可用于刷新数据。</summary>
|
||||
void OnActivated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可选排序接口。DataHubWindow 自动发现模块时按 <see cref="DisplayOrder"/> 升序排列。
|
||||
/// 未实现此接口的模块默认顺序为 0,再按 DisplayName 字母序排列。
|
||||
/// </summary>
|
||||
public interface IDataModuleOrdered
|
||||
{
|
||||
/// <summary>
|
||||
/// 导航侧边栏排列顺序。数值越小越靠前。
|
||||
/// 建议使用 10, 20, 30… 间隔,便于插入新模块。
|
||||
/// </summary>
|
||||
int DisplayOrder { get; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user