diff --git a/Assets/GameMain/DataTables/UIForm.txt b/Assets/GameMain/DataTables/UIForm.txt index 836c032..accf4d2 100644 --- a/Assets/GameMain/DataTables/UIForm.txt +++ b/Assets/GameMain/DataTables/UIForm.txt @@ -5,8 +5,8 @@ 100 主菜单 MenuForm Medium False True 101 设置 SettingForm Medium False True 102 关于 AboutForm Medium False True - 110 主界面 MainForm Medium False True - 111 仓库UI RepoForm Medium False True + 110 主界面 MainForm Medium False False + 111 仓库UI RepoForm Medium False False 112 大地图UI NodeMapForm Medium False True 113 详细信息 ItemDescForm Medium True False 114 奖励选择UI RewardSelectForm Medium False True diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs index 868ed7a..99ca303 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs @@ -3,6 +3,7 @@ using GameFramework.DataTable; using GeometryTD.CustomEvent; using GeometryTD.DataTable; using GeometryTD.Definition; +using GeometryTD.Procedure; using UnityEngine; using UnityGameFramework.Runtime; @@ -174,7 +175,12 @@ namespace GeometryTD.CustomComponent EnsureCombatRuntimeInitialized(); } - public void StartCombat(int levelId = 0) + public void StartCombat( + int levelId = 0, + string runId = null, + int nodeId = 0, + RunNodeType nodeType = RunNodeType.None, + int sequenceIndex = -1) { if (!EnsureCombatRuntimeInitialized()) { @@ -221,7 +227,14 @@ namespace GeometryTD.CustomComponent LastDefeatedEnemyCount = 0; LastGainedCoin = 0; LastGainedGold = 0; - if (!_combatScheduler.Start(selectedLevel, phaseList, _selectedSpawnEntriesByPhaseId)) + if (!_combatScheduler.Start( + selectedLevel, + phaseList, + _selectedSpawnEntriesByPhaseId, + runId, + nodeId, + nodeType, + sequenceIndex)) { CurrentLevel = null; return; diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs index e86c4d3..1b992c2 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs @@ -4,6 +4,7 @@ using GeometryTD.CustomEvent; using GeometryTD.DataTable; using GeometryTD.Definition; using GeometryTD.Entity; +using GeometryTD.Procedure; using GeometryTD.UI; using UnityEngine; using UnityGameFramework.Runtime; @@ -67,7 +68,11 @@ namespace GeometryTD.CustomComponent public bool Start( DRLevel level, IReadOnlyList phases, - IReadOnlyDictionary> spawnEntriesByPhaseId) + IReadOnlyDictionary> spawnEntriesByPhaseId, + string runId = null, + int nodeId = 0, + RunNodeType nodeType = RunNodeType.None, + int sequenceIndex = -1) { if (!_initialized || _context.Entity == null) { @@ -89,6 +94,10 @@ namespace GeometryTD.CustomComponent _context.IsFinishAsVictory = true; _context.CurrentLevel = level; + _context.RunId = runId; + _context.NodeId = nodeId; + _context.NodeType = nodeType; + _context.SequenceIndex = sequenceIndex; _context.CombatInRunResourceManager.InitializeForCombat(level); for (int i = 0; i < phases.Count; i++) { diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs index d4216ec..dcc451b 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerFlowCoordinator.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using GeometryTD.CustomEvent; using GeometryTD.DataTable; using GeometryTD.Definition; +using GeometryTD.Procedure; using GeometryTD.UI; using UnityEngine; using UnityGameFramework.Runtime; @@ -55,6 +56,10 @@ namespace GeometryTD.CustomComponent _context.IsFinishAsVictory = true; _context.IsCompleted = false; _context.NodeEnterFired = false; + _context.RunId = null; + _context.NodeId = 0; + _context.NodeType = RunNodeType.None; + _context.SequenceIndex = -1; } public void CleanupAllCombatEntities() @@ -196,7 +201,15 @@ namespace GeometryTD.CustomComponent public void CompleteNormalCombatAndNotify(bool succeeded) { CompleteCombat(succeeded); - GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create()); + GameEntry.Event.Fire( + this, + NodeCompleteEventArgs.Create( + _context.RunId, + _context.NodeId, + _context.NodeType, + _context.SequenceIndex, + succeeded, + GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null)); } public void CompleteFailureCombatAndNotify() diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerRuntimeContext.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerRuntimeContext.cs index d4e3d9f..bbf451f 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerRuntimeContext.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatSchedulerRuntimeContext.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using GeometryTD.DataTable; using GeometryTD.Entity; +using GeometryTD.Procedure; using GeometryTD.UI; using UnityGameFramework.Runtime; @@ -29,5 +30,9 @@ namespace GeometryTD.CustomComponent public bool IsCompleted { get; set; } public bool NodeEnterFired { get; set; } public CombatSettlementContext SettlementContext { get; set; } + public string RunId { get; set; } + public int NodeId { get; set; } + public RunNodeType NodeType { get; set; } + public int SequenceIndex { get; set; } } } diff --git a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRunningPhaseState.cs b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRunningPhaseState.cs index cf29545..183ce08 100644 --- a/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRunningPhaseState.cs +++ b/Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatStates/CombatRunningPhaseState.cs @@ -38,7 +38,13 @@ namespace GeometryTD.CustomComponent if (!Context.NodeEnterFired) { Context.NodeEnterFired = true; - GameEntry.Event.Fire(Flow, NodeEnterEventArgs.Create()); + GameEntry.Event.Fire( + Flow, + NodeEnterEventArgs.Create( + Context.RunId, + Context.NodeId, + Context.NodeType, + Context.SequenceIndex)); } Log.Info( diff --git a/Assets/GameMain/Scripts/CustomComponent/EventNodeComponent.cs b/Assets/GameMain/Scripts/CustomComponent/EventNodeComponent.cs index 702c86c..40c49e4 100644 --- a/Assets/GameMain/Scripts/CustomComponent/EventNodeComponent.cs +++ b/Assets/GameMain/Scripts/CustomComponent/EventNodeComponent.cs @@ -3,6 +3,7 @@ using GameFramework.DataTable; using GeometryTD.CustomEvent; using GeometryTD.DataTable; using GeometryTD.Definition; +using GeometryTD.Procedure; using Newtonsoft.Json.Linq; using GeometryTD.UI; using UnityEngine; @@ -12,6 +13,11 @@ namespace GeometryTD.CustomComponent { public class EventNodeComponent : GameFrameworkComponent { + private string _activeRunId; + private int _activeNodeId; + private RunNodeType _activeNodeType = RunNodeType.None; + private int _activeSequenceIndex = -1; + private readonly List _eventItems = new List(); private EventFormUseCase _eventFormUseCase; @@ -47,7 +53,7 @@ namespace GeometryTD.CustomComponent Log.Info("EventNodeComponent initialized with {0} events.", _eventItems.Count); } - public void StartEvent() + public void StartEvent(string runId = null, int nodeId = 0, RunNodeType nodeType = RunNodeType.None, int sequenceIndex = -1) { if (!_initialized) { @@ -69,15 +75,36 @@ namespace GeometryTD.CustomComponent return; } + _activeRunId = runId; + _activeNodeId = nodeId; + _activeNodeType = nodeType; + _activeSequenceIndex = sequenceIndex; _eventFormUseCase.SetCurrentEvent(randomEvent); GameEntry.UIRouter.OpenUI(UIFormType.EventForm); - GameEntry.Event.Fire(this, NodeEnterEventArgs.Create()); + GameEntry.Event.Fire(this, NodeEnterEventArgs.Create(runId, nodeId, nodeType, sequenceIndex)); } public void EndEvent() { GameEntry.UIRouter.CloseUI(UIFormType.EventForm); - GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create()); + GameEntry.Event.Fire( + this, + NodeCompleteEventArgs.Create( + _activeRunId, + _activeNodeId, + _activeNodeType, + _activeSequenceIndex, + true, + GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null)); + ClearActiveNodeContext(); + } + + private void ClearActiveNodeContext() + { + _activeRunId = null; + _activeNodeId = 0; + _activeNodeType = RunNodeType.None; + _activeSequenceIndex = -1; } private static EventOption[] ParseOptions(string optionsRaw) diff --git a/Assets/GameMain/Scripts/CustomComponent/ShopNodeComponent.cs b/Assets/GameMain/Scripts/CustomComponent/ShopNodeComponent.cs index ffd81e6..1d85e98 100644 --- a/Assets/GameMain/Scripts/CustomComponent/ShopNodeComponent.cs +++ b/Assets/GameMain/Scripts/CustomComponent/ShopNodeComponent.cs @@ -2,11 +2,17 @@ using GeometryTD.UI; using UnityGameFramework.Runtime; using GeometryTD.CustomEvent; using GeometryTD.Definition; +using GeometryTD.Procedure; namespace GeometryTD.CustomComponent { public class ShopNodeComponent : GameFrameworkComponent { + private string _activeRunId; + private int _activeNodeId; + private RunNodeType _activeNodeType = RunNodeType.None; + private int _activeSequenceIndex = -1; + private ShopFormUseCase _shopFormUseCase; private bool _initialized; @@ -22,7 +28,7 @@ namespace GeometryTD.CustomComponent _initialized = true; } - public void StartShop() + public void StartShop(string runId = null, int nodeId = 0, RunNodeType nodeType = RunNodeType.None, int sequenceIndex = -1) { if (!_initialized) { @@ -35,14 +41,35 @@ namespace GeometryTD.CustomComponent return; } + _activeRunId = runId; + _activeNodeId = nodeId; + _activeNodeType = nodeType; + _activeSequenceIndex = sequenceIndex; GameEntry.UIRouter.OpenUI(UIFormType.ShopForm); - GameEntry.Event.Fire(this, NodeEnterEventArgs.Create()); + GameEntry.Event.Fire(this, NodeEnterEventArgs.Create(runId, nodeId, nodeType, sequenceIndex)); } public void EndShop() { GameEntry.UIRouter.CloseUI(UIFormType.ShopForm); - GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create()); + GameEntry.Event.Fire( + this, + NodeCompleteEventArgs.Create( + _activeRunId, + _activeNodeId, + _activeNodeType, + _activeSequenceIndex, + true, + GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null)); + ClearActiveNodeContext(); + } + + private void ClearActiveNodeContext() + { + _activeRunId = null; + _activeNodeId = 0; + _activeNodeType = RunNodeType.None; + _activeSequenceIndex = -1; } } } diff --git a/Assets/GameMain/Scripts/Event/Game/NodeCompleteEventArgs.cs b/Assets/GameMain/Scripts/Event/Game/NodeCompleteEventArgs.cs index 86c119b..ead88be 100644 --- a/Assets/GameMain/Scripts/Event/Game/NodeCompleteEventArgs.cs +++ b/Assets/GameMain/Scripts/Event/Game/NodeCompleteEventArgs.cs @@ -18,6 +18,8 @@ namespace GeometryTD.CustomEvent public RunNodeType NodeType { get; private set; } + public int SequenceIndex { get; private set; } + public bool Succeeded { get; private set; } public BackpackInventoryData InventorySnapshotAfterNode { get; private set; } @@ -28,13 +30,14 @@ namespace GeometryTD.CustomEvent public static NodeCompleteEventArgs Create() { - return Create(null, 0, RunNodeType.None, true, null); + return Create(null, 0, RunNodeType.None, -1, true, null); } public static NodeCompleteEventArgs Create( string runId, int nodeId, RunNodeType nodeType, + int sequenceIndex, bool succeeded, BackpackInventoryData inventorySnapshotAfterNode) { @@ -42,6 +45,7 @@ namespace GeometryTD.CustomEvent args.RunId = runId; args.NodeId = nodeId; args.NodeType = nodeType; + args.SequenceIndex = sequenceIndex; args.Succeeded = succeeded; args.InventorySnapshotAfterNode = InventoryCloneUtility.CloneInventory(inventorySnapshotAfterNode); @@ -53,6 +57,7 @@ namespace GeometryTD.CustomEvent RunId = null; NodeId = 0; NodeType = RunNodeType.None; + SequenceIndex = -1; Succeeded = false; InventorySnapshotAfterNode = null; } diff --git a/Assets/GameMain/Scripts/Procedure/ProcedureMain.cs b/Assets/GameMain/Scripts/Procedure/ProcedureMain.cs index 78439b5..e120ebc 100644 --- a/Assets/GameMain/Scripts/Procedure/ProcedureMain.cs +++ b/Assets/GameMain/Scripts/Procedure/ProcedureMain.cs @@ -78,11 +78,45 @@ namespace GeometryTD.Procedure private void OnNodeEnter(object sender, GameEventArgs e) { - if (!(e is NodeEnterEventArgs)) + if (!(e is NodeEnterEventArgs args)) { return; } + if (!string.IsNullOrWhiteSpace(args.RunId) && + _currentRunState != null && + !string.Equals(args.RunId, _currentRunState.RunId)) + { + Log.Warning( + "ProcedureMain.OnNodeEnter() ignored. EventRunId={0}, CurrentRunId={1}.", + args.RunId, + _currentRunState.RunId); + return; + } + + RunNodeState currentNode = _currentRunState?.CurrentNode; + if (currentNode != null && + ((args.NodeId > 0 && args.NodeId != currentNode.NodeId) || + (args.NodeType != RunNodeType.None && args.NodeType != currentNode.NodeType) || + (args.SequenceIndex >= 0 && args.SequenceIndex != currentNode.SequenceIndex))) + { + Log.Warning( + "ProcedureMain.OnNodeEnter() node mismatch. EventNodeId={0}, EventNodeType={1}, EventSequenceIndex={2}; CurrentNodeId={3}, CurrentNodeType={4}, CurrentSequenceIndex={5}.", + args.NodeId, + args.NodeType, + args.SequenceIndex, + currentNode.NodeId, + currentNode.NodeType, + currentNode.SequenceIndex); + } + + Log.Info( + "ProcedureMain.OnNodeEnter() accepted. RunId={0}, NodeId={1}, NodeType={2}, SequenceIndex={3}.", + string.IsNullOrWhiteSpace(args.RunId) ? _currentRunState?.RunId : args.RunId, + args.NodeId, + args.NodeType, + args.SequenceIndex); + GameEntry.UIRouter.CloseUI(UIFormType.NodeMapForm); GameEntry.UIRouter.CloseUI(UIFormType.MainForm); } @@ -94,13 +128,24 @@ namespace GeometryTD.Procedure return; } + if (!string.IsNullOrWhiteSpace(args.RunId) && + _currentRunState != null && + !string.Equals(args.RunId, _currentRunState.RunId)) + { + Log.Warning( + "ProcedureMain.OnNodeComplete() ignored. EventRunId={0}, CurrentRunId={1}.", + args.RunId, + _currentRunState.RunId); + return; + } + BackpackInventoryData snapshot = args.InventorySnapshotAfterNode; if (snapshot == null && GameEntry.PlayerInventory != null) { snapshot = GameEntry.PlayerInventory.GetInventorySnapshot(); } - AdvanceRunState(true, snapshot); + AdvanceRunState(args.Succeeded, snapshot); OpenHubUI(); } @@ -153,16 +198,30 @@ namespace GeometryTD.Procedure return; } - GameEntry.CombatNode.StartCombat(currentNode.LinkedLevelId); + GameEntry.CombatNode.StartCombat( + currentNode.LinkedLevelId, + _currentRunState.RunId, + currentNode.NodeId, + currentNode.NodeType, + currentNode.SequenceIndex); return; case RunNodeType.Event: - GameEntry.EventNode.StartEvent(); + GameEntry.EventNode.StartEvent( + _currentRunState.RunId, + currentNode.NodeId, + currentNode.NodeType, + currentNode.SequenceIndex); return; case RunNodeType.Shop: - GameEntry.ShopNode.StartShop(); + GameEntry.ShopNode.StartShop( + _currentRunState.RunId, + currentNode.NodeId, + currentNode.NodeType, + currentNode.SequenceIndex); return; default: - Log.Warning("ProcedureMain.OnNodeMapNodeEnterRequested() encountered unsupported node type: {0}.", currentNode.NodeType); + Log.Warning("ProcedureMain.OnNodeMapNodeEnterRequested() encountered unsupported node type: {0}.", + currentNode.NodeType); return; } } @@ -206,8 +265,8 @@ namespace GeometryTD.Procedure private void OpenHubUI() { _nodeMapFormUseCase?.SetRunState(_currentRunState); - GameEntry.UIRouter.OpenUI(UIFormType.MainForm); GameEntry.UIRouter.OpenUI(UIFormType.NodeMapForm); + GameEntry.UIRouter.OpenUI(UIFormType.MainForm); } } -} +} \ No newline at end of file diff --git a/Assets/GameMain/Tests/Editor/RunStateTests.cs b/Assets/GameMain/Tests/Editor/RunStateTests.cs index f9ef14a..d6e38d3 100644 --- a/Assets/GameMain/Tests/Editor/RunStateTests.cs +++ b/Assets/GameMain/Tests/Editor/RunStateTests.cs @@ -108,6 +108,7 @@ namespace GeometryTD.Tests.Editor "run-1", 7, RunNodeType.Shop, + 3, true, inventory); @@ -116,6 +117,7 @@ namespace GeometryTD.Tests.Editor Assert.That(eventArgs.RunId, Is.EqualTo("run-1")); Assert.That(eventArgs.NodeId, Is.EqualTo(7)); Assert.That(eventArgs.NodeType, Is.EqualTo(RunNodeType.Shop)); + Assert.That(eventArgs.SequenceIndex, Is.EqualTo(3)); Assert.That(eventArgs.Succeeded, Is.True); Assert.That(eventArgs.InventorySnapshotAfterNode.Gold, Is.EqualTo(66)); @@ -124,6 +126,7 @@ namespace GeometryTD.Tests.Editor Assert.That(eventArgs.RunId, Is.Null); Assert.That(eventArgs.NodeId, Is.EqualTo(0)); Assert.That(eventArgs.NodeType, Is.EqualTo(RunNodeType.None)); + Assert.That(eventArgs.SequenceIndex, Is.EqualTo(-1)); Assert.That(eventArgs.Succeeded, Is.False); Assert.That(eventArgs.InventorySnapshotAfterNode, Is.Null); } diff --git a/Assets/GameMain/UI/UIForms/MainForm.prefab b/Assets/GameMain/UI/UIForms/MainForm.prefab index daba16e..da6977e 100644 --- a/Assets/GameMain/UI/UIForms/MainForm.prefab +++ b/Assets/GameMain/UI/UIForms/MainForm.prefab @@ -208,12 +208,12 @@ PrefabInstance: - target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc, type: 3} propertyPath: m_SizeDelta.x - value: 200 + value: 160 objectReference: {fileID: 0} - target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc, type: 3} propertyPath: m_SizeDelta.y - value: 200 + value: 160 objectReference: {fileID: 0} - target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc, type: 3} @@ -275,6 +275,26 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: m_SizeDelta.x + value: -30 + objectReference: {fileID: 0} + - target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: m_SizeDelta.y + value: -30 + objectReference: {fileID: 0} + - target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] @@ -372,12 +392,12 @@ PrefabInstance: - target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc, type: 3} propertyPath: m_SizeDelta.x - value: 200 + value: 160 objectReference: {fileID: 0} - target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc, type: 3} propertyPath: m_SizeDelta.y - value: 200 + value: 160 objectReference: {fileID: 0} - target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc, type: 3} @@ -439,6 +459,26 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} + - target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: m_SizeDelta.x + value: -30 + objectReference: {fileID: 0} + - target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: m_SizeDelta.y + value: -30 + objectReference: {fileID: 0} + - target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc, + type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] diff --git a/Assets/GameMain/UI/UIForms/RepoForm.prefab b/Assets/GameMain/UI/UIForms/RepoForm.prefab index faccc68..39c1040 100644 --- a/Assets/GameMain/UI/UIForms/RepoForm.prefab +++ b/Assets/GameMain/UI/UIForms/RepoForm.prefab @@ -999,7 +999,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 0.2, g: 0.2, b: 0.2, a: 0.5019608} + m_Color: {r: 0.2, g: 0.2, b: 0.2, a: 0.8} m_RaycastTarget: 1 m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 diff --git a/docs/CodeX-TODO.md b/docs/CodeX-TODO.md index afb1565..dc2ba27 100644 --- a/docs/CodeX-TODO.md +++ b/docs/CodeX-TODO.md @@ -8,7 +8,7 @@ 和上一版相比,仓库已经把 Run 相关基础件进一步接到了 `ProcedureMain`,因此这份清单不再把重点放在“有没有 Run 模型”,而是聚焦下面这几个真实阻塞项: -- 已有 `ProcedureMain + TestMenuForm` 的临时 Run 推进闭环,但还没有正式节点地图 / 节点面板。 +- 已有 `ProcedureMain + NodeMapForm` 的临时 Run 推进闭环,正式节点面板骨架已经接入流程。 - 固定 10 节点顺序已经开始驱动战斗 / 事件 / 商店入口,但节点事件上下文仍然是空载版本。 - 出战入口已有“至少有参战塔”的最小校验,但还没收口成严格的最终合法性约束。 - 品质 / Tag / 耐久仍然停留在部分实现状态,尚未和 M1 范围完全对齐。 @@ -90,25 +90,42 @@ - `Assets/GameMain/Scripts/UI/Shop/Controller/ShopFormController.cs` - `Assets/GameMain/Scripts/UI/Shop/View/ShopForm.cs` +### 7. `NodeMapForm` 五层 UI 已经接入主流程 + +- 已新增 `NodeMapFormRawData / UseCase / Controller / Context / View` 和 `NodeItemContext / NodeItem`。 +- `ProcedureMain` 已不再打开 `TestMenuForm`,而是打开 `NodeMapForm` 作为 Hub 节点入口。 +- `NodeMapFormController` 会把 `NodeItem` 点击转换为 `NodeMapNodeEnterRequestedEventArgs`,再交给流程层决定是否进入节点。 +- `NodeItem` 的 `Icon` 已改成通过 `SpriteCacheComponent` 按资源名异步获取,`Bg` 负责表示节点状态。 + +关键文件: +- `Assets/GameMain/Scripts/UI/Game/RawData/NodeMapFormRawData.cs` +- `Assets/GameMain/Scripts/UI/Game/UseCase/NodeMapFormUseCase.cs` +- `Assets/GameMain/Scripts/UI/Game/Controller/NodeMapFormController.cs` +- `Assets/GameMain/Scripts/UI/Game/Context/NodeMapFormContext.cs` +- `Assets/GameMain/Scripts/UI/Game/Context/NodeItemContext.cs` +- `Assets/GameMain/Scripts/UI/Game/View/NodeMapForm.cs` +- `Assets/GameMain/Scripts/UI/Game/View/NodeItem.cs` +- `Assets/GameMain/Scripts/Event/Game/NodeMapNodeEnterRequestedEventArgs.cs` + ## 当前未完成 -### 1. Run 主流程已经形成临时闭环,但还不是正式节点 UI +### 1. Run 主流程已经形成基于 `NodeMapForm` 的临时闭环,但还没完全收口 -- `ProcedureMain` 进入后会创建 Run,并打开 `MainForm + TestMenuForm`。 -- `TestMenuForm` 已不再同时提供三种节点入口,而是通过 `TestMenuFormContext.CurrentNodeType` 只显示当前节点对应的唯一按钮。 -- 点击按钮后由 `ProcedureMain` 根据当前 `RunState.CurrentNode` 决定是否允许进入战斗 / 事件 / 商店。 +- `ProcedureMain` 进入后会创建 Run,并打开 `MainForm + NodeMapForm`。 +- `NodeMapForm` 已经有五层结构,`NodeItem` 也能按当前 `RunState` 刷出 10 个节点。 +- 点击当前节点后,由 `NodeMapFormController` 发出 `NodeMapNodeEnterRequestedEventArgs`,再由 `ProcedureMain` 根据当前 `RunState.CurrentNode` 决定是否允许进入战斗 / 事件 / 商店。 - 节点完成或战斗失败后,`ProcedureMain` 会推进 `RunState`,再重新打开 Hub UI。 -- 因此当前已经能从开始游戏后按固定顺序推进一局的临时版本。 +- 因此当前已经不是“测试菜单三选一”,而是“节点地图单入口”的临时版本。 当前缺口: -- UI 仍是测试面板,不是正式节点地图或正式节点信息面板。 -- `TestMenuFormContext` 目前只传 `CurrentNodeType`,还没有显示节点序号、总数、Boss 标记等关键信息。 +- `NodeMapForm` 已接入,但当前仍偏 MVP 骨架,缺节点连线、节点说明、Boss 特效等正式表现。 +- `NodeMapFormContext` 当前只提供基础进度和当前节点信息,还没有更完整的地图展示上下文。 - Run 完成后的正式结算 / 收尾表现仍未建立。 关键文件: - `Assets/GameMain/Scripts/Procedure/ProcedureMain.cs` -- `Assets/GameMain/Scripts/UI/Menu/View/TestMenuForm.cs` -- `Assets/GameMain/Scripts/UI/Menu/Controller/TestMenuFormController.cs` +- `Assets/GameMain/Scripts/UI/Game/View/NodeMapForm.cs` +- `Assets/GameMain/Scripts/UI/Game/Controller/NodeMapFormController.cs` ### 2. 固定 10 节点序列已开始驱动真实流程,但上下文仍不完整 @@ -131,7 +148,7 @@ - 现在可以单独打开商店、随机生成组件、购买并退出。 - 商店结束后已能通过 `ProcedureMain` 推进当前 `RunState`。 -- 但退出后仍是回到 `MainForm + TestMenuForm`,而不是真实节点地图或正式节点选择界面。 +- 但退出后当前回流的是 `MainForm + NodeMapForm` 的 MVP 节点面板,而不是完整表现版地图。 关键文件: - `Assets/GameMain/Scripts/CustomComponent/ShopNodeComponent.cs` @@ -183,21 +200,21 @@ 结合静态代码检查,当前更接近下面这个状态: -- `P0-04`:基础模型已完成,并已接入 `ProcedureMain` 的临时 Run 闭环。 -- `P0-05`:固定 10 节点序列已由 builder + `ProcedureMain` 开始驱动实际流程,但缺完整事件上下文与正式节点 UI。 -- `P0-06`:节点进入、完成、失败后回 Hub 的临时闭环已存在,但“正式地图/节点界面 + 正式结算”仍未完成。 +- `P0-04`:基础模型已完成,并已接入 `ProcedureMain + NodeMapForm` 的临时 Run 闭环。 +- `P0-05`:固定 10 节点序列已由 builder + `ProcedureMain + NodeMapForm` 驱动实际流程,但缺完整事件上下文与地图表现层。 +- `P0-06`:节点进入、完成、失败后回 `NodeMapForm` 的临时闭环已存在,但正式地图表现与正式结算仍未完成。 - `P0-10`:未完成。 - `P0-11`:未完成。 - `P0-12`:未完成。 -换句话说,仓库已经不再是“还没有 Run 模型 / 10 节点 / 商店最小实现”的状态;真正的缺口是“这些能力已经接成临时可跑版本,但还没收口成正式 M1 主流程表现层与上下文系统”。 +换句话说,仓库已经不再是“还没有 Run 模型 / 10 节点 / 商店最小实现”的状态;真正的缺口是“这些能力已经接成 `NodeMapForm` 驱动的临时可跑版本,但还没收口成正式 M1 主流程表现层与上下文系统”。 ## 推荐的后续执行顺序 1. 先把临时 Hub 升级成正式节点面板 - 保留 `ProcedureMain` 持有 Run 的做法 - - 不再把 `TestMenuForm` 当测试菜单,而是改成正式节点信息面板或节点地图 - - 至少补上当前节点序号、总节点数、节点名称、Boss 标记 + - 继续以 `NodeMapForm` 为基础补正式节点地图表现 + - 至少补上节点连线、Boss 视觉强调、当前节点说明和完成态反馈 2. 再把节点事件改成带上下文的真实推进 - `NodeEnterEventArgs` 和 `NodeCompleteEventArgs` 传递 `runId / nodeId / nodeType / sequenceIndex` @@ -218,7 +235,7 @@ ## 当前做变更时要记住的约束 -- 不要再把 `TestMenuForm` 维持成“测试菜单”语义。 +- 不要再把 Hub 退回 `TestMenuForm` 语义。 - 优先补“临时闭环 -> 正式节点 UI / 正式上下文”的收口,不要继续只加单点功能。 - 商店已经接入 Run,下一步重点不是继续扩商店,而是把 Hub/UI 做正式。 - 若 M1 最终不做完整耐久 / 红色品质,要先同步文档再改代码目标。 @@ -233,8 +250,9 @@ - `Assets/GameMain/Scripts/Procedure/RunStateFactory.cs` - `Assets/GameMain/Scripts/Procedure/RunStateAdvanceService.cs` - `Assets/GameMain/Scripts/Procedure/FixedRunNodeSequenceBuilder.cs` -- 临时 Hub / 节点入口: - - `Assets/GameMain/Scripts/UI/Menu/Controller/TestMenuFormController.cs` +- 当前 Hub / 节点入口: + - `Assets/GameMain/Scripts/UI/Game/Controller/NodeMapFormController.cs` + - `Assets/GameMain/Scripts/UI/Game/View/NodeMapForm.cs` - 战斗节点 facade: - `Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs` - 事件节点: @@ -251,4 +269,4 @@ - 这份清单基于 2026-03-08 的静态代码检查更新。 - 本次已修正上一版中“Run 模型不存在”“10 节点未实现”“商店节点基本未实现”等过期判断。 -- 当前最值得优先推进的是:`ProcedureMain 继续收口 Run 流程 -> 节点事件带上下文 -> 战斗按节点关卡进入 -> 出战校验`。 +- 当前最值得优先推进的是:`NodeMapForm 继续收口表现层 -> 节点事件带上下文 -> 战斗按节点关卡进入 -> 出战校验`。 diff --git a/docs/TODO.md b/docs/TODO.md index a4cc314..2a393ac 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -16,9 +16,9 @@ | [x] | P0-01 | 冻结 MVP 范围(只保留:战斗节点/事件节点/商店节点/节点后组装) | `docs/MVP-Scope.md` | 明确“做/不做”清单,团队评审通过 | | [x] | P0-02 | 补齐数据表:组件、配件、敌人、波次、节点、事件、商店商品 | `Assets/GameMain/DataTables/*.txt` | 游戏启动可无报错加载全部新增表 | | [x] | P0-03 | 新增/完善 DataRow 解析类 | `Assets/GameMain/Scripts/DataTable/*.cs` | 每个新增表都有对应 DR 类并成功解析 | -| [~] | P0-04 | 建立单局 Run 状态模型(金币、生命、库存、当前节点) | `Assets/GameMain/Scripts/Procedure/` | 已有 `RunState` / 推进模型 / 测试,并已接入 `ProcedureMain` 的临时 Run 闭环,但还不是正式节点 UI | -| [~] | P0-05 | 实现 10 节点地图生成(最后节点固定 Boss) | `Assets/GameMain/Scripts/Procedure/`
`Assets/GameMain/Scripts/Scene/` | 已有固定 10 节点序列 builder 与测试,且已开始驱动真实流程,但节点事件上下文与正式节点面板仍未完成 | -| [~] | P0-06 | 实现节点选择与跳转流程(战斗/事件/商店) | `Assets/GameMain/Scripts/Procedure/` | `ProcedureMain + TestMenuForm` 的临时闭环已可推进战斗/事件/商店,但仍缺正式节点地图/节点面板与正式结算收尾 | +| [~] | P0-04 | 建立单局 Run 状态模型(金币、生命、库存、当前节点) | `Assets/GameMain/Scripts/Procedure/` | 已有 `RunState` / 推进模型 / 测试,并已接入 `ProcedureMain + NodeMapForm` 的临时 Run 闭环 | +| [~] | P0-05 | 实现 10 节点地图生成(最后节点固定 Boss) | `Assets/GameMain/Scripts/Procedure/`
`Assets/GameMain/Scripts/Scene/` | 已有固定 10 节点序列 builder 与测试,且已由 `NodeMapForm` 驱动节点入口,但节点事件上下文与地图表现层仍未完成 | +| [~] | P0-06 | 实现节点选择与跳转流程(战斗/事件/商店) | `Assets/GameMain/Scripts/Procedure/` | `ProcedureMain + NodeMapForm` 的临时闭环已可推进战斗/事件/商店,但仍缺完整地图表现、正式结算收尾与节点上下文回流 | | [x] | P0-07 | 战斗节点基础玩法:放置塔、出怪、基地扣血、胜负判定 | `Assets/GameMain/Scripts/Entity/`
`Assets/GameMain/Scripts/Scene/` | 可完整打一场并得到胜利/失败结果 | | [x] | P0-08 | 胜利波次与结算规则(100/80/50/<50) | `Assets/GameMain/Scripts/Procedure/` | 结算奖励与惩罚严格匹配设计文档 | | [x] | P0-09 | 敌人掉落与关卡奖励(组件/配件/金币) | `Assets/GameMain/Scripts/Entity/` | 战斗结束能发放掉落并写入库存 | @@ -55,7 +55,7 @@ ## 本周建议开工顺序 -1. 先把 `P0-04` ~ `P0-06` 从“临时闭环”收口成正式节点面板 / 节点地图,并补齐节点事件上下文 +1. 先把 `P0-04` ~ `P0-06` 从“`NodeMapForm` 临时闭环”收口成正式节点地图表现,并补齐节点事件上下文 2. 完成 `P0-10`(把“至少有参战塔”提升为“满足完整合法参战条件才能出战”) 3. 再处理 `P0-11` ~ `P0-12`(品质 / Tag / 耐久是否纳入 M1 需先统一范围) diff --git a/数据表/UIForm.xlsx b/数据表/UIForm.xlsx index efefb14..1045732 100644 Binary files a/数据表/UIForm.xlsx and b/数据表/UIForm.xlsx differ