This commit is contained in:
2026-04-30 19:33:16 +08:00
commit fb2252f688
556 changed files with 69721 additions and 0 deletions
+736
View File
@@ -0,0 +1,736 @@
# CombatNode 设计规范(开发约束)
最后更新:2026-03-12
## 1. 适用范围与目标
本文描述 `CombatNode` 域的后续开发标准。
说明:
- 本文是“目标架构约束”,不要求当前代码已经完全达成。
- 后续新增功能、重构、拆分类、review 职责边界时,以本文为准。
- 如果当前实现与本文不一致,新增代码优先向本文收敛,而不是继续扩大旧结构。
核心目标:
- `CombatScheduler` 收敛为“状态机管理器”,不再继续堆积加载、结算、奖励选择等业务细节。
- 战斗内资源收口到独立资源服务,由内部管理,不再由 `CombatNodeComponent` 直接持有真值。
- `MapEntity` 通过 `MapData + Event` 获取战斗上下文,不反查 `CombatNode` 域内部状态。
---
## 2. 架构总览
### 2.1 CombatNodeComponent(入口 Facade)
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs`
长期职责:
- 读取并缓存 `DRLevel / DRLevelPhase / DRLevelSpawnEntry`。
- 按主题筛选关卡。
- 启动/停止 `CombatScheduler`。
- 对外暴露只读运行时属性。
- 提供少量用户入口,例如 `StartCombat`、`TryEndCombatByPlayer`。
长期不负责:
- 不直接持有 `Coin / Gold / BaseHp / Loot Backpack` 的真值。
- 不直接缓存本局建塔属性快照。
- 不直接发布战斗流程事件。
- 不直接处理敌人掉落、结算、奖励选择、地图加载。
### 2.2 CombatScheduler(状态机管理器)
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatScheduler.cs`
长期职责:
- 持有共享运行时数据与共享服务实例。
- 管理状态实例。
- 提供统一的 `ChangeState(...)` 状态迁移入口。
- 提供敌人事件的公共处理入口。
- 作为状态机生命周期边界,统一做运行时重置。
长期不负责:
- 不直接硬编码加载流程。
- 不直接硬编码结算流程。
- 不直接硬编码奖励选择 UI 逻辑。
- 不直接硬编码 `PhaseEndType` 结束条件。
推荐状态类命名:
- `CombatLoadingState`
- `CombatRunningPhaseState`
- `CombatWaitingForPhaseEndState`
- `CombatSettlementState`
- `CombatRewardSelectionState`
- `CombatFinishFormState`
- `CombatWaitingForReturnState`
- `CombatFailedState`
实现约束:
- 上述状态类可以作为 `CombatScheduler` 的嵌套类实现,也可以拆成独立文件;但必须只服务于 `CombatScheduler` 状态机,不形成独立业务边界。
- 共享数据与共享服务统一收口到 `CombatScheduler` 内部持有的运行时承载体,不允许散落在各状态类中。
- 若 `CombatScheduler` 体量过大,允许在其内部实现中继续拆出:
- `CombatSchedulerRuntime`:承载共享运行时字段与共享服务引用
- `CombatSchedulerCoordinator`:承载多个状态共用的流程辅助方法
- 上述拆分只属于 `CombatScheduler` 的内部实现细化,不改变 `CombatScheduler` 作为唯一状态机边界的职责。
- 所有状态切换只能通过 `CombatScheduler.ChangeState(...)` 完成。
- 状态类不能彼此直接操控。
### 2.3 EnemyManager(敌人域 Facade)
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyManager.cs`
长期职责:
- 对状态机提供统一敌人域接口。
- 编排敌人子服务。
- 暴露只读事实:
- `AliveEnemyCount`
- `IsPhaseSpawnCompleted`
- `HasAliveBoss`
- 在敌人死亡或到家时,通过公共入口向 `CombatScheduler` 上报:
- `OnEnemyDefeated(DREnemy enemy)`
- `OnEnemyReachedBase(DREnemy enemy)`
长期不负责:
- 不直接给资源入账。
- 不直接扣基地血量。
- 不直接决定状态切换。
---
## 3. 状态机模型
### 3.1 状态列表(目标)
- `Loading`
- `RunningPhase`
- `WaitingForPhaseEnd`
- `Settlement`
- `RewardSelection`
- `FinishForm`
- `WaitingForReturn`
- `Failed`
说明:
- 正常结束流只有一条状态链:
- `Settlement -> RewardSelection(可选) -> FinishForm -> WaitingForReturn`
- 正常通关、玩家主动结束、基地血量归零都走同一条结束链。
- `Failed` 仅用于异常失败,不用于“基地被击破”这类正常战斗失败。
### 3.2 CombatLoadingState
职责:
- 通过 `CombatLoadSession` 执行地图与基础战斗 UI 加载。
- 从局内资源管理器读取本局快照。
- 组装 `MapData` 并发起 `ShowEntity(MapEntity)`。
约束:
- 只负责加载,不负责初始化局内资源。
- 局内资源必须在进入状态机前初始化完成。
### 3.3 CombatRunningPhaseState
职责:
- 执行当前 `DRLevelPhase` 的行为。
- 推进 `SpawnEntry` 时序与出怪。
- 管理 `EnemySpawnDirector` 的阶段级初始化与重置。
- 在新 phase 开始时发布:
- `CombatProcessEventArgs`
- `CombatEnemyHpRateChangedEventArgs`
退出条件:
- 当前 phase 的所有 `SpawnEntry` 已执行完毕时,进入 `WaitingForPhaseEnd`。
- 若共享“结束战斗请求标记”已置位,也可结束当前运行态并转入正常结束链。
不负责:
- 不根据 `PhaseEndType` 判断 phase 是否真正结束。
- 不直接根据 `BaseHp` 或敌人死亡事件切状态。
### 3.4 CombatWaitingForPhaseEndState
职责:
- 不再生成新敌人。
- 根据 `PhaseEndType` 判断当前 phase 是否结束。
约束:
- `PhaseEndType` 的判断由独立判定服务负责,不在状态内硬编码。
- 每种 `PhaseEndType` 对应一个实现类。
- 该判定服务为本状态专用,不作为全局共享服务常驻在 `CombatScheduler` 上。
### 3.5 CombatSettlementState
职责:
- 进入时统一构造结算上下文。
- 根据共享资源状态完成结算修正。
- 决定后续进入 `RewardSelection` 还是 `FinishForm`。
负责的逻辑包括:
- 基地血量奖励/惩罚。
- 满血奖励选择的准入判断。
- 生成最终展示摘要。
- 准备待合并的结算背包快照。
约束:
- 不依赖单独的 `CombatEndReason` 字段。
- `BaseHp <= 0` 表示基地被击破。
- 正常通关与玩家主动结束在结算产出上不区分原因。
### 3.6 CombatRewardSelectionState
职责:
- 绑定、配置、打开、关闭 `RewardSelectForm`。
- 处理奖励选择过程。
- 将奖励选择结果写入“结束状态链持有的结算上下文”。
约束:
- 不重新判断“是否应该出现奖励选择”。
- 只处理选择过程本身。
### 3.7 CombatFinishFormState
职责:
- 绑定、配置、打开、关闭 `CombatFinishForm`。
- 读取结算上下文并展示最终结算结果。
### 3.8 CombatWaitingForReturnState
职责:
- 等待玩家从结算返回。
- 完成地图与战斗基础 UI 清理。
- 完成正常退出收尾。
- 在整场战斗真正退出时发布 `NodeCompleteEventArgs`。
### 3.9 CombatFailedState
职责:
- 表示异常失败。
- 保存并展示错误信息。
- 执行异常收尾与剩余资源回收。
约束:
- `Failed` 只处理异常路径。
- “基地血量为 0”不进入 `Failed`。
---
## 4. 共享服务与推荐命名
### 4.1 命名后缀词典
- `Scheduler`:只用于状态机边界或阶段推进总控,例如 `CombatScheduler`。
- `Manager`:只用于子域 Facade/聚合入口,例如 `EnemyManager`。
- `Coordinator`:只用于跨状态、跨服务的流程编排,不持有独立业务真值。
- `Service`:只用于聚焦业务行为,不承担框架事件桥接或异步句柄跟踪。
- `Calculator`:只用于纯计算与结果组装,不直接提交状态或驱动 UI。
- `Session`:只用于一次加载/交互过程的生命周期对象。
- `Bridge`:只用于框架边界适配器。
- `Runtime`:只用于运行时可变状态承载。
- `Context`:只用于被动数据包或共享上下文。
- `Result`:只用于动作输出或结算产出;若外围服务名已表达动作语义,不再重复动作前缀。
- `Flags`:只用于聚合布尔控制项,不承载流程编排逻辑。
- `Resolver`:只用于映射、查找、判定、解析职责。
- `Tracker`:只用于跟踪运行中的实体或事实真值。
- `Port`:只用于向内部状态或 use case 暴露的受限宿主接口。
### 4.2 CombatLoadSession
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatLoadSession.cs`
长期定位:
- 长期保留的独立加载服务。
- 专门负责地图与战斗内基础 UI 的加载/清理。
职责:
- 加载地图实体。
- 打开/关闭 `CombatInfoForm`。
- 跟踪加载成功/失败状态。
- 对外提供 `CurrentMap` 与 `IsReady`。
### 4.2.x CombatSchedulerRuntime / CombatSchedulerCoordinator(实现细化)
当前实现允许:
- 用 `CombatSchedulerRuntime` 承载所有状态共享的运行时字段与共享服务引用。
- 用 `CombatSchedulerCoordinator` 承载多个状态共用的流程辅助逻辑。
约束:
- 两者都必须由 `CombatScheduler` 持有并统一管理生命周期。
- 两者都不替代 `CombatScheduler` 对外暴露状态机边界。
- `Runtime` 不负责状态切换。
- `Coordinator` 不持有独立业务真值,只能围绕共享运行时做编排辅助。
- 状态类只允许通过 `Runtime + Coordinator` 访问共享状态与共享流程,不应再直接耦合其他状态实现细节。
### 4.3 PhaseLoopRuntime
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/PhaseLoopRuntime.cs`
长期定位:
- 长期保留的独立 phase runtime 服务。
职责:
- 维护当前 `DRLevelPhase`。
- 维护 `DisplayPhaseIndex`、`PhaseCount`。
- 维护统一的 phase 时间基准,例如 `phaseElapsedTime` 或 `phaseStartTime`。
- 负责进入下一 phase。
- 持有统一“请求结束战斗”标记。
约束:
- 只做 phase 运行时数据管理,不直接切状态。
### 4.4 CombatRunResourceStore
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler/CombatRunResourceStore.cs`
目标职责:
- 持有本局 `Coin` 真值。
- 持有本局累计 `Gold` 真值。
- 持有本局 `BaseHp` 真值。
- 持有本局战利品背包。
- 持有本局建塔属性快照。
- 提供只读快照给结束状态链与加载状态使用。
- 发布资源变化事件:
- `CombatCoinChangedEventArgs`
- `CombatBaseHpChangedEventArgs`
初始化约束:
- 在进入状态机前完成初始化。
- 由内部从 `PlayerInventory` 获取并缓存本局建塔快照。
事件约束:
- `Coin / BaseHp` 变化事件同时携带“当前值”和“变化量”。
- `Gold` 只是结算累计值,不要求战斗内实时事件驱动。
### 4.5 InventoryGenerationComponent
文件:`Assets/GameMain/Scripts/CustomComponent/InventoryGeneration/InventoryGenerationComponent.cs`
目标职责:
- 作为局外组件产出的统一运行时入口。
- 对外提供:
- `BuildShopGoods(...)`
- `ResolveEnemyDrop(...)`
- `BuildRewardCandidates(...)`
- 在内部编排:
- `DropPoolRoller`
- `RewardCandidateBuilder`
- `OutGameDropRuleService`
- `OutGameDropItemBuilder`
- `InventoryGenerationRandomContext`
约束:
- `CombatNode` 域不直接持有或复制组件产出规则。
- `CombatScheduler` 与结算状态链只调用统一入口,不直接访问掉落池滚动或组件实例构造细节。
- `InventoryGenerationComponent` 负责运行时入口、稳定临时实例 Id、Tag 随机上下文以及 `runSeed/sequenceIndex` 相关上下文。
- 掉落是否产出组件由 `OutGameDropRuleService` 决定;掉落池行到组件实例的构造由 `OutGameDropItemBuilder` 决定。
### 4.5.x InventoryGenerationRandomContext
文件:`Assets/GameMain/Scripts/CustomComponent/InventoryGeneration/InventoryGenerationRandomContext.cs`
目标职责:
- 统一承载组件产出链路的随机合同。
- 统一派生:
- 商店 / 掉落 / 奖励候选的稳定随机流
- 稳定临时组件 `InstanceId`
- `InventoryTagRandomContext`
约束:
- `ShopGoodsBuilder`、`DropPoolRoller`、`RewardCandidateBuilder` 不再直接使用全局 `UnityEngine.Random`。
- 同一 `runSeed + sequenceIndex + sourceType + localOrdinal` 下,应得到一致的物品本体与 Tag 结果。
### 4.6 CombatSettlementCalculator
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCalculator.cs`
目标职责:
- 只负责结算计算与 `CombatSettlementContext` 组装。
- 负责基地血量奖励、奖励选择准入、奖励背包快照与耐久扣减目标计算。
约束:
- 不直接并包到玩家库存。
- 不直接打开 UI。
- 不承担奖励候选生成。
### 4.7 CombatSettlementCommitter
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatSettlementCommitter.cs`
目标职责:
- 只负责把结算结果提交到玩家库存。
- 负责结算背包并包与延迟耐久扣减落地。
约束:
- 不重新计算结算上下文。
- 不直接生成奖励候选或打开 UI。
### 4.8 IPhaseEndCondition
目标职责:
- 作为 `PhaseEndType` 判定接口。
- 每种 `PhaseEndType` 对应一个实现类。
只读输入:
- 当前 `DRLevelPhase`
- phase 时间信息
- `AliveEnemyCount`
- `IsPhaseSpawnCompleted`
- `HasAliveBoss`
输出:
- `bool ShouldExit`
约束:
- 不直接切状态。
- 不直接发事件。
- 不直接改资源。
---
## 5. EnemyManager 子服务边界
### 5.1 EnemySpawnDirector
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemySpawnDirector.cs`
职责:
- 长期保留为独立服务。
- 基于 `spawnEntries + phase time` 计算当前应执行的刷怪行为。
- 提供“当前 phase 的 `SpawnEntry` 是否已全部执行完”的事实。
生命周期:
- 由 `CombatRunningPhaseState` 在状态进入/退出时初始化与重置。
### 5.2 EnemySpawnPathResolver
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemySpawnPathResolver.cs`
职责:
- 缓存当前地图可用 `Spawner`。
- 提供出生点与路径解析。
### 5.3 EnemyLifecycleTracker
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyLifecycleTracker.cs`
职责:
- 维护 `AliveEnemyCount` 真值。
- 维护 `HasAliveBoss` 真值。
- 追踪本局 tracked 敌人。
- 导出 tracked ids 供清场使用。
Boss 识别规则:
- Boss 身份由 `DRLevelSpawnEntry.EntryType == Boss` 决定。
- 不由 `DREnemy` 自身类型决定。
### 5.4 EnemyConfigProvider
文件:`Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager/EnemyConfigProvider.cs`
职责:
- 读取 `DREnemy`。
- 处理默认配置兜底。
- 计算循环周目下的基础血量倍率。
---
## 6. 事件与数据流规范
### 6.1 MapEntity 与 Combat 域解耦
必须保持:
- `MapEntity` 不直接查询 `CombatNodeComponent` 的运行时资源字段。
- 战斗初始上下文通过 `MapData` 注入。
- `Coin` 初值通过 `MapData` 传入。
- 后续 `Coin` 变化通过 `CombatCoinChangedEventArgs` 同步。
- `TowerStatsData` 等本局不变量直接放进 `MapData`。
- `MapEntity` 不反查 Combat 域内部服务。
`MapData` 组装规则:
- 由 `CombatLoadingState` 从局内资源管理器读取快照。
- 由 `CombatLoadingState` 打包成 `MapData` 后再 `ShowEntity(MapEntity)`。
### 6.2 敌人事件处理
统一边界:
- `EnemyManager` 只上报:
- `OnEnemyDefeated(DREnemy enemy)`
- `OnEnemyReachedBase(DREnemy enemy)`
- `CombatScheduler` 公共层负责处理敌人事件的通用副作用:
- 击杀:调用 `GameEntry.InventoryGeneration.ResolveEnemyDrop(...)`,再调用局内资源管理器入账。
- 到家:调用局内资源管理器扣减 `BaseHp`。
约束:
- 敌人事件入口不直接调用 `ChangeState(...)`。
- `BaseHp <= 0` 的判断由当前状态在 `OnUpdate` 中处理。
### 6.3 战斗流程事件
发布边界:
- 资源变化事件由局内资源管理器发布。
- 流程/阶段事件由状态机或具体状态发布。
发布时间:
- `NodeEnterEventArgs`:`Loading` 完成并正式进入首个 `RunningPhase` 时。
- `CombatProcessEventArgs`:新 phase 的 `RunningPhase.OnEnter`。
- `CombatEnemyHpRateChangedEventArgs`:与 `CombatProcessEventArgs` 同时发布。
- `NodeCompleteEventArgs`:`WaitingForReturn` 完成清理、整场战斗真正退出时。
---
## 7. 结束链与结算上下文
### 7.1 统一结束链
正常结束统一走:
- `Settlement`
- `RewardSelection`(可选)
- `FinishForm`
- `WaitingForReturn`
### 7.2 结算上下文
归属:
- 作为 `CombatScheduler` 上的共享字段存在。
- `Settlement` 在 `OnEnter` 时统一构造。
- `RewardSelection` 只追加奖励结果。
- `FinishForm` 与 `WaitingForReturn` 只读取。
最小字段集合:
- 最终结算的 `Gold/Coin` 结果
- 待合并的背包快照
- `BaseHp` 结算结果
- 是否进入过奖励选择
- `FinishForm` 所需摘要数据
命名约束:
- 结算上下文中的布尔控制项统一收口到 `Flags`,不再使用 `Flow` 命名。
奖励选择约束:
- 满血奖励选择结果只写入结算上下文。
- 不直接写入局内资源管理器。
- 最终由结束状态链统一合并到玩家背包。
---
## 8. 核心不变量(必须保持)
1. `CombatScheduler` 只做状态机管理与共享运行时收口,不继续吸收具体业务细节。
2. `CombatNodeComponent` 不再持有战斗内资源真值。
3. 局内 `Coin / Gold / BaseHp / Loot Backpack / BuildTowerSnapshots` 以 `CombatRunResourceStore` 为唯一真值来源。
4. 组件产出规则以 `InventoryGenerationComponent` 为统一运行时入口;战斗掉落与奖励候选都通过它生成。
5. 存活敌人数与 `HasAliveBoss` 以 `EnemyLifecycleTracker` 为唯一真值来源。
6. Phase 运行时信息与统一结束标记以 `PhaseLoopRuntime` 为唯一真值来源。
7. `PhaseEndType` 的退出条件以 `IPhaseEndCondition` 实现类为唯一判定入口。
8. 状态切换只能通过 `CombatScheduler.ChangeState(...)` 完成。
9. 敌人事件处理入口不直接切状态,状态只能在自己的 `OnUpdate` 中决定迁移。
10. `MapEntity` 通过 `MapData + Event` 获取战斗上下文,不反查 Combat 域内部运行时。
---
## 9. 清理职责
- 敌人清理:`EnemyManager`,且只清理本局 tracked 敌人。
- 地图与战斗基础 UI 清理:`CombatLoadSession`。
- 结算/奖励 UI 清理:结束状态链或 `Failed` 状态。
- 运行时数据重置:`CombatScheduler` 在状态机生命周期边界统一执行。
---
## 10. 扩展开发规范
### 10.1 新增刷怪类型或 SpawnEntry 行为
优先改 `EnemySpawnDirector`,不要把时序细节塞进 `CombatRunningPhaseState`。
### 10.2 新增 Phase 结束条件
新增 `IPhaseEndCondition` 实现类,不要在 `CombatWaitingForPhaseEndState` 里写大分支。
### 10.3 新增敌人掉落规则
优先改 `InventoryGenerationComponent` 及其下层规则模块,不要在 `EnemyManager`、`CombatScheduler` 或状态类里直接计算掉落。
### 10.3.x 新增奖励候选规则
优先改 `InventoryGenerationComponent`、`RewardCandidateBuilder` 或 `DropPoolRoller`,不要在结算状态链里复制一套候选生成规则。
### 10.4 新增战斗内资源或建塔快照规则
优先改 `CombatRunResourceStore`,不要回流到 `CombatNodeComponent`。
### 10.5 新增地图/战斗基础 UI 加载规则
优先改 `CombatLoadSession` 或 `CombatLoadingState`,不要把加载细节塞回 `CombatScheduler` 本体。
### 10.6 新增强化结算、奖励选择、结算 UI 逻辑
优先改结束状态链:
- `CombatSettlementState`
- `CombatRewardSelectionState`
- `CombatFinishFormState`
- `CombatWaitingForReturnState`
### 10.7 新增战斗流程事件
优先由具体状态或局内资源管理器发布,不要回流到 `CombatNodeComponent`。
---
## 11. 代码变更检查清单(PR 自检)
1. 新逻辑是否落在正确的状态或服务,而不是继续堆进 `CombatScheduler` 本体?
2. `CombatNodeComponent` 是否仍然保持为轻量入口 Facade?
3. 是否破坏了局内资源、掉落判定、phase runtime、phase end 判定的唯一真值来源?
4. 敌人事件处理是否仍然只做公共副作用,而不直接切状态?
5. 状态迁移是否仍然统一走 `ChangeState(...)`?
6. `MapEntity` 是否仍然只通过 `MapData + Event` 获取战斗上下文?
7. 清理是否仍按”敌人 / 地图基础 UI / 结算 UI / 运行时数据”分工执行?
---
## 12. Combat Economy(战斗经济)
> **Status**: 新增段落 — GDD 一致性审查中发现 Coin 经济在实现中存在但未写入文档
> **最后更新**: 2026-04-30
本段说明战斗域内双货币体系的完整设计依据。所有数值均来自数据表驱动,无需代码硬编码。
---
### 12.1 双货币架构概述
游戏使用两层货币,分属不同生命周期:
| 货币 | 生命周期 | 存储位置 | 描述 |
|------|----------|----------|------|
| **Coin** | 单次战斗内 | `CombatRunResourceStore.CurrentCoin` | 战斗内部经济,用于建塔/升级/拆除 |
| **Gold** | 整局运行 | `PlayerInventoryComponent.Gold` | 跨节点持久,用于商店购买、出售 |
**设计意图**: Coin 创造战斗内的即时决策张力(”我现在花 Coin 建塔还是留着防万一?”);Gold 创造跨节点的战略张力(”我是现在买还是等下一个商店?”)。两层经济分离,防止任一层的决策深度被稀释。
**关键约束**: Coin 不跨战斗持久,战斗结束时清零。战斗胜利后 `GainedGold` 入账,`GainedCoin` 不入账。
---
### 12.2 Coin(战斗内部货币)
#### 12.2.1 来源
| 来源 | 字段 | 说明 |
|------|------|------|
| 关卡初始 Coin | `DRLevel.StartCoin` | 战斗开始时发放,来源于 `CombatRunResourceStore.InitializeForCombat()` |
| 敌人击杀奖励 | `DREnemy.DropCoin` | 每次击杀敌人时发放,来源于 `CombatRunResourceStore.AddEnemyDefeatedReward()` |
`StartCoin` 按关卡难度配置,确保玩家在战斗开始时有足够的决策空间。建议低难度关卡 `StartCoin` 较低,高难度/Boss 关卡较高。
`DropCoin` 按敌人类型配置。建议普通敌人 `DropCoin` 较低(5–15),精英敌人较高(20–50),与 `DropGold` 分开计算。
#### 12.2.2 消耗(Sinks)
| 操作 | 消耗方式 | 触发时机 |
|------|----------|----------|
| **BuildTower** | `TryConsumeCoin(buildTowerCost[i])` | 玩家在战斗内点击建塔位,每槽位独立计费 |
| **UpgradeTower** | `TryConsumeCoin(upgradeCost)` | 玩家升级已有塔 |
| **DestroyTower** | 无消耗;返回 `destroyGain` Coin | 玩家拆除已有塔,Coin 返还 |
建塔槽位共 4 个(对应 `BuildOptionCount = 4`),每个槽位可独立消耗 Coin。每槽位的 `buildTowerCost[i]` 由关卡或战斗阶段配置。
#### 12.2.3 追踪
`CombatRunResourceStore.GainedCoin` 记录本场战斗累计获得的 Coin。战斗结束时 `GainedCoin` 计入 `RunStats.coinsEarned`(跨战斗累计),但 Coin 本身不清零重置于下一个战斗,而是**每个新战斗重新从 `DRLevel.StartCoin` 开始**。
```
// 每场新战斗开始时
CurrentCoin = DRLevel.StartCoin // 从关卡配置重置,非累加
GainedCoin = 0 // 重置,用于本场统计
```
#### 12.2.4 数据驱动约束
| 约束 | 值 | 说明 |
|------|---|------|
| `DRLevel.StartCoin` | ≥ 0 | 建议普通关卡 50–200,Boss 关卡 100–300 |
| `DREnemy.DropCoin` | ≥ 0 | 建议普通敌人 5–15,精英 20–50,Boss 0(避免重复计费) |
| `buildTowerCost[i]` | ≥ 0 | 由 `CombatSelectFormUseCase` 从关卡配置读取,建议 20–80 每槽位 |
| `upgradeCost` | ≥ 0 | 由 `CombatSelectFormUseCase` 从关卡配置读取,建议 50–150 |
| `destroyGain` | ≥ 0 | 建议 = `buildTowerCost * 0.5`(返还 50%),与商店售价机制一致 |
---
### 12.3 Gold(战斗内获取部分)
战斗过程中 Gold 获取有两个来源:
#### 12.3.1 敌人击杀掉落
```
// DREnemy 字段
DropGold // 掉落金币数额
DropPercent // 掉落概率 [0.0, 1.0]
```
战斗胜利时(敌人被击杀或波次结束),若随机值 `≤ DropPercent`,则 `AddEnemyDefeatedReward(gainedCoin, gainedGold)` 被调用,`gainedGold = DropGold`。
#### 12.3.2 关卡胜利奖励
战斗胜利结算时(`CombatSettlementState`),`CombatSettlementCalculator` 计算本场战斗总 Gold 奖励:`DRLevel.RewardGold`,通过 `CombatRunResourceStore.AddSettlementGold()` 入账。
战斗失败时:无 `RewardGold`,`GainedGold = 0`。
#### 12.3.3 追踪
`CombatRunResourceStore.GainedGold` 记录本场战斗累计获得的 Gold(击杀掉落 + 胜利奖励)。战斗结束时通过 `GetRewardInventorySnapshot()` 合并至玩家主背包(`PlayerInventoryComponent.MergeInventory()`),触发 `MaxPlayerGold = 9999` 上限检查(溢出部分丢弃)。
---
### 12.4 Coin 与 Gold 的运行时边界
```
战斗开始 → InitializeForCombat(level)
└→ CurrentCoin = level.StartCoin // Coin 重置
└→ GainedCoin = 0, GainedGold = 0 // 本场统计重置
战斗过程中(敌人死亡)→ AddEnemyDefeatedReward(dropCoin, dropGold)
└→ CurrentCoin += dropCoin
└→ CurrentGold (奖励库存) += dropGold // 不影响主背包
└→ GainedCoin += dropCoin
└→ GainedGold += dropGold
战斗胜利 → AddSettlementGold(RewardGold)
└→ GainedGold += RewardGold
战斗结束 → GetRewardInventorySnapshot() → MergeInventory()
└→ 主背包 Gold += min(rewardGold, MaxPlayerGold - currentGold)
└→ 主背包组件 += 奖励组件
└→ RunStats.coinsEarned += GainedCoin // 仅统计,不持久化 Coin
```
---
### 12.5 Progression 的 coinEarned 字段
| 字段 | 类型 | 说明 |
|------|------|------|
| `RunStats.coinsEarned` | int | **跨战斗累计**:本 run 所有战斗累计获得的 Coin 总和(战斗失败也计入) |
`coinsEarned` 用于统计目的,不产生任何游戏内效果。它记录玩家在一局 run 中总共获得了多少 Coin — 可用于未来功能(如”累计获得 10000 Coin”成就),但当前无对应解锁或奖励。
---
### 12.6 与商店经济的隔离
Coin 仅在战斗域内流通。商店系统(`design/gdd/shop.md`)处理的 buy/sell 交易仅涉及 Gold,不涉及 Coin。
- 战斗内**不会**触发商店交易
- 商店内**不会**消耗或获得 Coin
- 战斗结束时,Coin 不转换为 Gold(`GainedCoin` 仅计入 `RunStats`,不进入玩家背包)
---
### 12.7 调试与一致性检查
| 检查项 | 预期结果 |
|--------|----------|
| 新战斗开始时 `CurrentCoin == DRLevel.StartCoin` | 相等 |
| 战斗结束时 `GainedCoin ≥ 0` | 大于等于零 |
| 战斗失败时 `GainedGold == 0` | 战斗失败不发放 Gold 奖励 |
| `MaxPlayerGold` 上限检查 | `MergeInventory` 后背包 Gold ≤ 9999 |
| Coin 消耗后 `CurrentCoin ≥ 0` | `TryConsumeCoin` 失败时 `CurrentCoin` 不变 |
Binary file not shown.
+63
View File
@@ -0,0 +1,63 @@
# 《几何塔防》
## 游戏定位
塔防肉鸽
## 游戏主循环
1. 进入游戏,使用当前样例库存中的组件组装初始防御塔并开始游戏
2. 在关卡中玩家选择不同的节点推进关卡:
- 战斗节点:布置防御塔抵御敌人进攻并收集防御塔组件
- 事件节点:随机事件(不含战斗)
- 商店节点:购买防御塔组件
3. 节点后调整:玩家可使用三组件自由组装防御塔以抵御更强的敌人进攻
4. 开始新一轮关卡
## 具体说明
### 一、战前准备:
1. 当前 M1 以样例库存和仓库内三组件组装链为准,不再要求独立的“开局二选三组件并组出两座塔”前置流程
### 二、节点:
1. 当前 M1 只实现单一固定 Run:每个大关固定 10 个节点,最后一个节点固定为 Boss 战斗节点;多主题地图与大关间选择保留到后续阶段
2. 主题地图示例(后续阶段):
- 火山:高温(战斗节点中会随机触发火山喷发在地图上生成岩浆格)
- 对于防御塔:部分组件在该高温下能发挥更强/更弱性能,若岩浆生成在防御塔上将会进一步强化高温对组件性能的影响
- 对于敌人:敌人多具有火焰抗性,部分敌人能在岩浆格上更快的行走
- 山地:地势起伏(地图格子有额外的高度条件,悬崖格子)
- 对于防御塔:不同高度的攻击会有攻击范围变化,高打低范围加强,低打高范围缩小
- 对于敌人:不同高度会有移动速度的差异,高往低走移速加快,低往高走移速降低,陆地敌人被击退到悬崖格子立即死亡
3. 当前每个大关有 10 个固定节点,完成 Boss 节点后进入正式结束态并返回主菜单,不在 M1 内继续展开下一大关选择
#### 1. 战斗节点
1. 玩家携带一定数量的防御塔进入关卡,在关卡内规定的位置布置防御塔,具体的游戏逻辑与一般的塔防游戏类似:
- 关卡开始有一些资源用于布置防御塔,击杀敌人获取资源来布置或升级防御塔。
- 敌人会选择最短路径由出怪口向玩家基地前进;道路阻挡与更复杂的路径改写机制保留到后续阶段
2. 击杀敌人除了获取关卡内使用的资源外,还有概率掉落防御塔组件;每个小关卡结束后也会奖励组件与金币用于后续节点
3. 关卡内设定一个胜利波次,当玩家存活的波次达到后会根据基地生命产生不同的结算:
- = 100% :获得额外 30% 的金币,以及额外 1 次组件 3 选 1
- >= 80% :获得额外 10% 的金币
- >= 50% :无加成
- < 50%:当前 M1 不再追加额外耐久惩罚;耐久已按“每场战斗结算后对本场参战塔固定扣 1”收口
4. “胜利后继续挑战”保留到后续阶段,当前 M1 以正常结算回流节点地图为准
#### 2. 事件节点
玩家经历一些有选项的随机事件,获取额外的奖励/惩罚。
示例:
1. 玩家花费 100 金币赌马,有两个选择:(1) 30% 赢,赢了获得 250 金币。(2) 70% 赢,赢了获得 150 金币
2. 玩家提供 2 个防御塔组件,获得 1 个不低于原来品质的防御塔组件
3. 耐久换金币事件保留到后续阶段;当前 M1 只保留最小耐久闭环
#### 3. 商店节点
1. 当前 M1 只实现组件商店的基础购买;商店内出售、刷新、复杂定价、卖塔加成与耐久折价保留到后续阶段
2. 道具系统保留到后续阶段
### 三、节点后的调整
1. 组装防御塔:每个防御塔都必须有枪口(攻击组件)、轴承(旋转组件)、底座(功能组件)三个组件,防御塔的品质由组件决定。当前 M1 已实现三组件完整合法参战、品质统一计算、组件实例 Tag 生成、塔级 Tag 汇总,以及首发 7 个 Tag 的战斗效果
- 组件功能(某些组件还有全局性的属性倍率,比如高伤害穿透攻击的枪口会绑一个 0.5x攻击速度 的属性倍率来约束防御塔性能):
- 枪口(攻击组件):决定攻击伤害,攻击方式(普通子弹、范围伤害、穿透激光……)
- 轴承(旋转组件):决定枪口转速(某些攻击方式只有对准敌人后才能进行攻击),攻击范围
- 底座(功能组件):决定攻击频率,攻击属性(火焰、毒素、冰……)
- 品质计算:每个组件提供一定的品质权重(白:1,绿:2,蓝:3,紫:4,红:5),比如三个组件是 2 绿 1 白,那么防御塔的品质是 (2+2+1)/3=1.67,四舍五入后为 2 也就是绿色品质。
- 各品质的配件槽与更深的配件系统保留到后续阶段
- 组件 Tag 当前正式采用 `Tag.txt + RarityTagBudget.txt + TagConfig.txt` 三表方案:`Tag.txt` 负责基础字典、生成门槛、权重与启用态,`RarityTagBudget.txt` 负责按品质的 Tag 数量预算,`TagConfig.txt` 负责触发阶段、描述与效果参数
2. 拆解与耐久:当前 M1 只保留最小耐久闭环,即每场战斗结算后按本场参战塔真实扣减 `1` 点耐久、`0` 耐久失效并拦截参战 / 战斗入口;连续属性衰减、自动销毁与维修系统保留到后续阶段
+667
View File
@@ -0,0 +1,667 @@
# 程序集三层拆分方案
最后更新:2026-04-30
## 1. 设计目标
将项目拆分为三层,逐步解耦 Unity 依赖,实现核心业务逻辑的可测试性和可移植性:
- **L0(Domain)**:纯 C# 业务层,引用 GameFramework.dll 作为基础设施,可在独立解决方案中构建
- **L1(Infrastructure)**:胶水层,连接 L0 与 Unity Runtime 类型
- **L2(Presentation)**:表现层,Unity MonoBehaviour、UGuiForm、Entity 实现
---
## 2. GameFramework.dll 复用
项目自带的 `Assets/GameFramework/Libraries/GameFramework.dll` 是 **纯 C# 实现**的 GameFramework 核心库,包含 19 个模块。L0 直接引用此 DLL,无需重新实现。
### 2.1 可直接使用的模块
| 模块 | 提供内容 | L0 使用方式 |
|------|---------|-----------|
| **Event** | `EventManager`, `GameEventArgs` | `GameEntry.Event` 替换为 `EventManager` |
| **ObjectPool** | `ObjectPoolManager`, `ObjectBase` | 继承 `ObjectBase`,无需自己实现池 |
| **Fsm** | `FsmManager`, `FsmState`, `FsmState<T>` | 继承 `FsmState<T>` 构建状态机 |
| **ReferencePool** | `ReferencePool` | `ReferencePool.Acquire<T>()` / `Release()` |
| **DataNode** | 树状数据结构 | 直接使用 |
| **Utility** | 通用工具类 | 直接使用 |
| **Config** | 配置管理 | 直接使用 |
| **DataTable** | 表格加载解析 | 数据行类依赖 GameFramework |
### 2.2 需要 Unity 适配的模块
以下模块需要 L1 提供 Unity 特定实现:
| 模块 | 原因 | L1 适配职责 |
|------|------|------------|
| **Resource** | 依赖 Unity Resources/AssetDatabase | 实现 `IResourceManager` |
| **Scene** | 依赖 Unity SceneManager | 实现 `ISceneManager` |
| **Entity** | 依赖 Unity GameObject | 实现 `IEntityManager` |
| **UI** | 依赖 Unity UGUI | 实现 `IUIFormManager` |
| **Sound** | 依赖 Unity Audio | 实现 `ISoundManager` |
### 2.3 复用带来的简化
```
传统方案(自研基础设施)
├── 需要自己实现事件系统
├── 需要自己实现对象池基类
├── 需要自己实现状态机框架
└── 大量基础设施代码
GameFramework.dll 方案
├── 事件 → GameFramework.Event.EventManager
├── 对象池 → GameFramework.ObjectPool
├── 状态机 → GameFramework.Fsm
└── 专注业务逻辑实现
```
---
## 3. 三层职责定义
### L0 - Domain(纯 C# 业务层)
- 引用 `GameFramework.dll`,使用其提供的 Event/ObjectPool/Fsm 等基础设施
- 包含所有业务规则、状态机、领域逻辑
- 无 `using UnityEngine`、`using UnityGameFramework.Runtime`
- 通过接口与 L1 通信
- 在独立 .sln 中构建,输出 DLL 导入 Unity
### L1 - Infrastructure(胶水层)
- 实现 GameFramework 的 Unity 特定接口(Resource/Scene/Entity/UI/Sound)
- 实现 L0 定义的扩展接口(如 `ICombatEventHandler`)
- 持有 L0 服务实例,管理 Unity 生命周期
- 直接依赖 UnityEngine 和 GameFramework.Runtime
### L2 - Presentation(表现层)
- 所有 `MonoBehaviour` 类
- View 层(UGuiForm 具体实现)
- Entity Logic(Player、Enemy、Tower 具体实体)
- ECS 组件(MovementComponent、ShooterBullet 等)
---
## 4. 程序集映射
### 4.1 L0 程序集
```
GeometryTD.Domain/ ← 引用 GameFramework.dll
├── Definition/
│ ├── Enum/ # 所有枚举类型
│ ├── Constant/ # 常量定义
│ ├── DataStruct/ # 纯数据结构(AttackPayload, TowerStatsData 等)
│ └── Tag/
│ ├── Aggregation/ # Tag 汇总服务
│ ├── Generation/ # Tag 生成规则
│ ├── Metadata/ # Tag 定义元数据
│ └── Combat/ # Tag 效果解析
│
├── GameFramework/ # GameFramework.dll 直接使用
│ └── Event/ # 自定义事件 args,继承 GameEventArgs
│
├── CustomComponent/
│ ├── CombatNode/ # 战斗域
│ │ ├── CombatScheduler # 基于 GameFramework.Fsm
│ │ ├── EnemyManager # 敌人生成、追踪
│ │ ├── CombatRunResourceStore # 战斗资源存储
│ │ └── CombatSettlementCalculator
│ ├── PlayerInventory/ # 背包、交易、组装服务
│ └── InventoryGeneration/ # 掉落、商店、奖励生成
│
├── UI/
│ ├── Base/
│ │ ├── IUIUseCase.cs
│ │ └── UIContext.cs
│ ├── Combat/
│ │ ├── UseCase/ # CombatSelectFormUseCase, CombatInfoFormUseCase
│ │ ├── RawData/
│ │ └── Context/
│ ├── Game/
│ │ ├── UseCase/ # EventFormUseCase, NodeMapFormUseCase, RepoFormUseCase
│ │ ├── RawData/
│ │ └── Context/
│ └── General/
│ ├── UseCase/
│ ├── RawData/
│ └── Context/
│
└── Utility/
├── InventoryCloneUtility.cs
├── EnumUtility.cs
└── InventoryRarityRuleService.cs
```
### 4.2 L1 程序集
```
GeometryTD.Infrastructure/
├── GameFramework/ # GameFramework Unity 适配
│ ├── Resource/ # 实现 IResourceManager
│ ├── Scene/ # 实现 ISceneManager
│ ├── Entity/ # 实现 IEntityManager
│ ├── UI/ # 实现 IUIFormManager
│ └── Sound/ # 实现 ISoundManager
│
├── CustomComponent/
│ ├── CombatNode/
│ │ └── CombatNodeComponent.cs # Unity Component
│ ├── PlayerInventory/
│ │ └── PlayerInventoryComponent.cs
│ ├── InventoryGeneration/
│ │ └── InventoryGenerationComponent.cs
│ ├── EventNodeComponent.cs
│ ├── ShopNodeComponent.cs
│ ├── TagRegistry/
│ │ └── TagRegistryComponent.cs
│ └── BuiltinDataComponent.cs
│
├── Scene/
│ └── Map/
│ ├── MapTopologyService.cs # Tilemap 扫描
│ ├── TowerPlacementService.cs # 依赖 GameEntry.Entity
│ ├── TowerSelectionPresenter.cs
│ └── MapCombatRuntimeBridge.cs
│
├── Entity/
│ ├── EntityLogic/
│ │ ├── EntityBase.cs
│ │ ├── CombatSelectInputService.cs
│ │ ├── CombatSelectUseCaseConfigurator.cs
│ │ └── MapEntity.cs
│ ├── EntityData/ # Unity 可序列化
│ │ ├── MapData.cs
│ │ ├── TowerData.cs
│ │ ├── EnemyData.cs
│ │ └── BulletData.cs
│ └── EntityExtension.cs
│
├── UI/
│ ├── Base/ # Unity 相关基类
│ │ ├── UGuiForm.cs
│ │ ├── UGuiGroupHelper.cs
│ │ ├── UIFormControllerCommonBase.cs
│ │ ├── UIExtension.cs
│ │ └── UIFormControllerBase.cs
│ └── Combat/
│ └── Controller/
│
├── DataTable/ # GameFramework.DataTable 依赖
│ └── DR*.cs
│
└── Procedure/ # GameFramework.Procedure
└── ProcedureMain/
```
### 4.3 L2 程序集
```
GeometryTD.Presentation/
├── UI/
│ ├── Combat/
│ │ └── View/
│ ├── Game/
│ │ └── View/
│ ├── General/
│ │ └── View/
│ ├── Templates/
│ └── Common/
│
├── Entity/
│ └── EntityLogic/
│ ├── Player.cs
│ ├── Enemy.cs
│ ├── TowerEntity.cs
│ ├── BulletEntity.cs
│ └── EnemyTagStatusRuntime.cs
│
└── Components/
├── MovementComponent.cs
├── ShooterBullet.cs
├── ShooterMuzzleComp.cs
├── TowerController.cs
├── InputComponent.cs
├── BasicBaseComp.cs
└── BasicBearingComp.cs
```
---
## 5. GameFramework 集成方式
### 5.1 事件系统
L0 使用 GameFramework 内置的 `EventManager`,无需自己实现事件系统:
```csharp
// L0: 定义游戏事件,继承 GameFramework.Event.GameEventArgs
public class CombatCoinChangedEventArgs : GameEventArgs
{
public const int EventId = typeof(CombatCoinChangedEventArgs).GetHashCode();
public int CurrentCoin { get; private set; }
public int Delta { get; private set; }
public CombatCoinChangedEventArgs() { }
public CombatCoinChangedEventArgs(int currentCoin, int delta)
{
CurrentCoin = currentCoin;
Delta = delta;
}
}
// L0: 服务中使用
public class CombatRunResourceStore
{
private IEventManager _event;
public CombatRunResourceStore(IEventManager eventManager)
{
_event = eventManager;
}
public void AddCoin(int amount)
{
CurrentCoin += amount;
_event.Fire(this, CombatCoinChangedEventArgs.Create(CurrentCoin, amount));
}
}
```
### 5.2 状态机
L0 使用 GameFramework 的 Fsm 模块:
```csharp
// L0: 战斗状态机基于 GameFramework.Fsm
public interface ICombatFsm { } // 空接口,用于泛型约束
public class CombatScheduler
{
private IFsmManager _fsmManager;
private CombatSchedulerRuntime _runtime;
public CombatScheduler(IFsmManager fsmManager)
{
_fsmManager = fsmManager;
_runtime = new CombatSchedulerRuntime();
}
public void Start()
{
_fsmManager.CreateFsm<ICombatFsm>(this,
new CombatLoadingState(),
new CombatRunningPhaseState(),
new CombatWaitingForPhaseEndState(),
new CombatSettlementState());
}
}
// L0: 具体状态继承 FsmState
public class CombatRunningPhaseState : FsmState<ICombatFsm>
{
protected internal override void OnEnter(ICombatFsm fsmOwner)
{
// 初始化
}
protected internal override void OnUpdate(ICombatFsm fsmOwner, float elapseSeconds)
{
// 每帧更新
}
protected internal override void OnLeave(ICombatFsm fsmOwner, bool isShutdown)
{
// 清理
}
}
```
### 5.3 对象池
L0 使用 GameFramework 的 ObjectPool:
```csharp
// L0: 敌人继承 ObjectBase
public class EnemyObject : ObjectBase
{
public int EnemyId { get; private set; }
public int Health { get; private set; }
protected internal override void OnSpawn(bool isRecycle)
{
// 激活时调用
}
protected internal override void OnDespawn(bool isRecycle)
{
// 回收时调用
}
public void Initialize(int enemyId, int health)
{
EnemyId = enemyId;
Health = health;
}
}
// L0: 使用对象池
public class EnemyManager
{
private IObjectPoolManager _poolManager;
public EnemyManager(IObjectPoolManager poolManager)
{
_poolManager = poolManager;
}
public void SpawnEnemy(int enemyId, int health)
{
var pool = _poolManager.GetOrCreateObjectPool<EnemyObject>("EnemyPool");
var enemy = EnemyObject.Create(enemyId, health);
pool.Register(enemy, true);
}
}
```
---
## 6. L1 桥接设计
### 6.1 GameFramework Unity 适配接口
GameFramework.dll 定义了以下接口,L1 需要提供 Unity 实现:
```csharp
// GameFramework 定义的接口(L0 引用)
namespace GameFramework.Resource
{
public interface IResourceManager
{
void LoadAsset(string assetName, LoadAssetCallbacks callbacks, object userData);
void UnloadAsset(string assetName);
// ...
}
}
// L1: Unity 实现
public class UnityResourceManager : IResourceManager
{
public void LoadAsset(string assetName, LoadAssetCallbacks callbacks, object userData)
{
// 使用 Unity Resources.Load 或 Addressables
}
}
```
### 6.2 L0 服务生命周期管理
```csharp
// L1: Unity Component 持有 L0 服务实例
public class CombatNodeComponent : GameFrameworkComponent
{
private IEventManager _eventManager;
private IObjectPoolManager _poolManager;
private IFsmManager _fsmManager;
// L0 服务
private CombatScheduler _scheduler;
private EnemyManager _enemyManager;
private void Awake()
{
// 初始化 GameFramework Unity 适配器
_eventManager = GameEntry.GetComponent<EventComponent>();
_poolManager = GameEntry.GetComponent<ObjectPoolComponent>();
_fsmManager = GameEntry.GetComponent<FsmComponent>();
// 初始化 L0 服务(注入依赖)
_enemyManager = new EnemyManager(_poolManager);
_scheduler = new CombatScheduler(_fsmManager, _enemyManager);
}
}
```
---
## 7. Unity 类型处理策略
### 7.1 Vector3 / Quaternion / Color
| 类型 | 方案 |
|------|------|
| `Vector3` | 使用 `System.Numerics.Vector3`,GameFramework 内部已使用 |
| `Quaternion` | 使用 `System.Numerics.Quaternion` |
| `Color` | 定义纯 C# Color 结构 `{ float r, g, b, a; }` |
### 7.2 [Serializable]
```csharp
// L0: POCO 数据结构
public class TowerStatsData
{
public int[] AttackDamage { get; set; }
public float[] AttackSpeed { get; set; }
}
// L1: Unity 可序列化 DTO
[Serializable]
public class TowerStatsDataDto
{
[SerializeField] private int[] _attackDamage;
[SerializeField] private float[] _attackSpeed;
public TowerStatsData ToDomain() => new()
{
AttackDamage = _attackDamage,
AttackSpeed = _attackSpeed
};
}
```
### 7.3 Sprite 引用
```csharp
// L0: 使用资源路径而非 Sprite 引用
public class TowerSelectItemRawData
{
public string IconPath { get; set; } // "UI/Icons/TowerIcon"
}
// L1: Controller 负责路径到 Sprite 的解析
public class CombatSelectFormController
{
private SpriteCacheComponent _spriteCache;
private TowerSelectItemContext BuildContext(TowerSelectItemRawData raw)
{
return new TowerSelectItemContext
{
Icon = _spriteCache.GetSprite(raw.IconPath)
};
}
}
```
---
## 8. 迁移顺序
### Phase 1: 基础设施搭建
| 任务 | 说明 |
|------|------|
| 创建 L0 项目,引用 GameFramework.dll | 验证基础依赖 |
| 验证 GameFramework Event/Fsm/ObjectPool 可用 | 核心模块连通性测试 |
| 创建 L1 基础结构 | Unity Component 基类、GameFramework 适配器 |
### Phase 2: 核心业务迁移(低风险优先)
| 迁移项 | 说明 | 依赖 |
|--------|------|------|
| `Definition/Enum/*` | 枚举类型 | 无 |
| `Definition/Constant/*` | 常量定义 | 无 |
| `UI/*/RawData/*` | 原始数据 | 无 |
| `UI/*/Context/*` | 上下文 | 无 |
| `Utility/*` | 工具类 | 无 |
### Phase 3: 业务域迁移
| 迁移项 | 说明 | 依赖 |
|--------|------|------|
| `InventoryGeneration/*` | 掉落、商店、奖励 | Phase 1-2 |
| `PlayerInventory/*` | 背包、交易、组装 | Phase 1-2 |
| `UI/*/UseCase/*` | 用例 | Phase 2 + Sprite 路径化 |
| `Definition/Tag/*` | Tag 系统 | Phase 1-2 |
### Phase 4: 战斗域(核心难点)
| 迁移项 | 说明 | 依赖 |
|--------|------|------|
| `CombatNode/CombatScheduler` | 状态机 | GameFramework.Fsm |
| `CombatNode/EnemyManager/*` | 敌人域 | GameFramework.ObjectPool |
| `CombatNode/CombatRunResourceStore` | 资源存储 | GameFramework.Event |
| `CombatNode/CombatSettlementCalculator` | 结算 | Phase 3 |
### Phase 5: 胶水和表现层迁移
| 迁移项 | 说明 |
|--------|------|
| `CustomComponent/*Component` | Unity Component |
| `UI/Base/*` | Controller 基类 |
| `UI/*/Controller/*` | Controller 实现 |
| `UI/*/View/*` | View 实现 |
| `Entity/EntityLogic/*` | Entity 实现 |
---
## 9. 项目文件组织
### 9.1 L0 项目文件
```xml
<!-- GeometryTD.Domain.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<RootNamespace>GeometryTD.Domain</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Reference Include="GameFramework">
<HintPath>..\..\Assets\GameFramework\Libraries\GameFramework.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
```
### 9.2 L1 项目文件
```xml
<!-- GeometryTD.Infrastructure.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\GeometryTD.Domain\GeometryTD.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="UnityEngine">
<HintPath>Unity\Editor\Data\Managed\UnityEngine\UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityGameFramework.Runtime">
<!-- Unity GameFramework Runtime 引用 -->
</Reference>
</ItemGroup>
</Project>
```
### 9.3 Unity 资产结构
```
Assets/
├── GameFramework/
│ └── Libraries/
│ ├── GameFramework.dll ← L0 直接引用
│ └── GameFramework.xml ← 文档
│
├── GameMain/
│ ├── L0/ # L0 DLL 输出
│ ├── L1/ # L1 源码或 DLL
│ └── Scripts/
│ └── L2/ # L2 表现层
```
---
## 10. 验收标准
1. **L0 可独立编译**:不包含 UnityEngine.dll、UnityGameFramework.Runtime 引用
2. **GameFramework.dll 正确引用**:L0 可使用 Event/ObjectPool/Fsm/ReferencePool
3. **无循环依赖**:L2 → L1 → L0,单向依赖
4. **接口隔离**:L0 与 Unity 的交互通过 L1 适配器
5. **游戏流程完整**:拆分后战斗、商店、组装流程正常运行
---
## 11. 附录:GameFramework.dll 模块清单
```
GameFramework.dll 包含以下 19 个模块(纯 C# 实现):
配置与数据
├── Config - 全局配置管理
├── DataNode - 树状数据结点
├── DataTable - 表格数据管理
└── Setting - 键值对存储
核心设施
├── Event - 事件管理(EventPool)
├── ObjectPool - 对象池(ObjectPoolManager)
├── ReferencePool - 引用计数池
├── Fsm - 有限状态机
└── Procedure - 流程(ProcedureBase)
资源管理
├── Resource - 资源加载(需要 Unity 适配器)
├── Scene - 场景管理(需要 Unity 适配器)
├── Entity - 实体管理(需要 Unity 适配器)
├── UI - 界面管理(需要 Unity 适配器)
└── Sound - 声音管理(需要 Unity 适配器)
网络与下载
├── Network - Socket 长连接
├── WebRequest - HTTP 短连接
└── Download - 文件下载
辅助模块
├── Localization - 多语言(需要资源适配器)
├── FileSystem - 虚拟文件系统
├── Debugger - 调试窗口
└── Utility - 通用工具类
```
---
## 12. 附录:当前 Unity 依赖渗透点
以下文件包含 `using UnityEngine` 或 `using UnityGameFramework.Runtime`,需要迁移:
| 文件 | Unity 依赖 | 目标层 |
|------|-----------|-------|
| `TowerPlacementService.cs` | `GameEntry.Entity` | L1 |
| `MapTopologyService.cs` | `Tilemap`, `Vector3Int` | L1 |
| `CombatSelectFormUseCase.cs` | `UnityEngine.Sprite` | L0 → 路径化 |
| `TowerStatsData.cs` | `[Serializable]`, `Color` | L0 → POCO + DTO |
| `AttackPayload.cs` | `Vector3` | L0 → System.Numerics |
| `MapEntity.cs` | `MonoBehaviour` | L1 |
| `EnemyEntity.cs` | `Transform` | L2 |
| `GameEntry.Builtin.cs` | `GameFrameworkComponent` | L1 |
+199
View File
@@ -0,0 +1,199 @@
# 《几何塔防》MVP 范围说明(流程验证版)
---
## 一、MVP目标
本阶段目标:
> 验证游戏完整流程是否可运行,包括
> 战斗节点 → 事件节点 → 商店节点 → 节点后组装 → 下一节点
> 直至完成一大关。
本阶段不关注:
* 数值平衡
* 构筑深度
* 美术质量
* 商业化系统
* 长期留存
---
## 二、本阶段包含内容
---
### 1️⃣ 基础游戏结构
* 单一主题地图(无环境机制)
* 1个大关(固定10节点)
* 最后节点为Boss战
* 固定节点顺序(例如:战斗 → 事件 → 战斗 → 商店 → 战斗 → Boss)
不做节点地图选择 UI。
---
### 2️⃣ 战斗节点(核心可运行)
包含:
* 基地生命系统
* 固定路径(单路径)
* 敌人按最短路径移动
* 5~8波敌人
* 1种普通敌人 + 1种精英敌人 + 1种Boss
* 胜利条件:达到波次
* 失败条件:基地血量为0
战斗奖励:
* 敌人可概率掉落组件与金币
* 战斗结算提供金币奖励
* 基地满血通关时额外提供 1 次组件 3选1 奖励
不做:
* 多路径
* 地图机制
* 精细敌人AI
---
### 3️⃣ 事件节点(流程占位)
实现:
* 3个固定事件模板
* 简单二选一结构
* 直接修改金币或组件数量
例如:
* 获得100金币
* 损失1个随机组件换取高品质组件
不做:
* 复杂概率算法
* 连锁事件
* 条件触发事件
---
### 4️⃣ 商店节点
实现:
* 显示4个随机组件
* 可购买组件
不做:
* 商店内出售组件
* 商店刷新
* 动态定价
* 经济平衡
* 道具系统
* 广告
* 内购
---
### 5️⃣ 节点后组装系统
实现:
* 组件栏
* 塔槽位
* 拖拽组装
* 组件替换
* 基础属性与 Tag 展示
组件结构(精简):
* 枪口
* 轴承
* 底座
* 基础 Tag 系统(首发 7 个:`Fire`、`Ice`、`Crit`、`Execution`、`Shatter`、`Inferno`、`AbsoluteZero`)
不做:
* 完整维修 / 折价 / 自动销毁系统
* 高级 Tag 联动
* 复杂触发矩阵
---
## 三、明确本阶段不做内容
以下内容全部排除在本阶段之外:
* ❌ 多主题地图
* ❌ 地图机制(火山/山地)
* ❌ 局外成长系统
* ❌ 广告系统
* ❌ 内购系统
* ❌ 成就系统
* ❌ 排行榜
* ❌ 多流派深度平衡
* ❌ 大规模敌人种类
* ❌ 复杂Boss阶段机制
* ❌ 数值精细调优
* ❌ 高级特效优化
---
## 四、组件规模限制
为避免膨胀,本阶段限制:
* 总组件数量 ≤ 20
* Tag数量 ≤ 8
* 品质等级:白 / 绿 / 蓝 / 紫 / 红
* 仅要求三组件主结构,不扩展更深的配件槽系统
---
## 五、UI范围
包含:
* 主界面(开始游戏)
* 节点流程界面
* 战斗界面
* 商店界面
* 组装界面
* 结算界面
允许:
* 使用临时UI
* 使用占位图
* 不做动画优化
---
## 六、完成标准(验收条件)
本阶段完成定义为:
1. 可从开始游戏一路完成一个大关
2. 节点之间流程无阻塞
3. 组件可组装并生效
4. 战斗可胜可负
5. 商店可购买
6. 不出现流程死锁
7. 无严重崩溃或逻辑错误
只要流程跑通,即视为完成。
---
## 七、本阶段不评估指标
* 不评估留存
* 不评估爽感强度
* 不评估平衡性
* 不评估变现能力
+170
View File
@@ -0,0 +1,170 @@
# MapEntity 设计规范(开发约束)
最后更新:2026-03-06
## 1. 目标与边界
`MapEntity` 现在是战斗地图域的编排层(orchestrator),目标是:
- 对外暴露地图能力(格子查询、路径查询、建造操作入口)。
- 在 Unity 生命周期中初始化/清理各子服务。
- 承担输入与 UI 用例的连接,不承载具体业务算法细节。
`MapEntity` 不应再直接实现以下细节:
- Tile 扫描与路径缓存算法。
- 防御塔映射字典的增删改查细节。
- 选中状态与攻击范围显示细节。
- 鼠标拾取与 `CombatSelectFormUserData` 组装细节。
- 战斗选择 UI 的库存快照解析、颜色映射与 Build 选项配置细节。
---
## 2. 模块划分(当前标准)
### 2.1 MapEntity(编排层)
文件:`Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs`
职责:
- 生命周期编排:`OnInit / OnShow / OnUpdate / OnHide`
- 子服务初始化与清理
- UI 用例绑定(`CombatSelectFormUseCase`)
- 将输入结果分发到选择器/建造器
- 收集运行时上下文并委托给专用服务配置战斗选择 UI
### 2.2 MapTopologyService(地图拓扑层)
文件:`Assets/GameMain/Scripts/Scene/Map/MapTopologyService.cs`
职责:
- 扫描 Tilemap,构建 `PathCells` / `FoundationCells`
- 缓存 `Spawner -> 默认路径`
- 提供路径查询:
- `TryGetNearestPathCell`
- `TryGetDefaultPathCells`
- `TryFindPathCells`
- `TryFindPathWorldPoints`
约束:
- 只处理“地图拓扑与路径”,不处理经济、建塔、UI。
### 2.3 CombatSelectInputService(输入解析层)
文件:`Assets/GameMain/Scripts/Entity/EntityLogic/CombatSelectInputService.cs`
职责:
- 鼠标屏幕坐标 -> 世界坐标
- 判定点击对象类型(`None/Foundation/Tower`)
- 组装 `CombatSelectFormUserData`
约束:
- 只读上下文,不改变游戏状态。
- Foundation/Tower 点击时,UI 定位由 Cell 中心决定(稳定定位)。
### 2.4 CombatSelectUseCaseConfigurator(战斗选择 UI 配置层)
文件:`Assets/GameMain/Scripts/Entity/EntityLogic/CombatSelectUseCaseConfigurator.cs`
职责:
- 读取库存快照与参战塔快照
- 构建组件映射与塔外观颜色
- 配置 `CombatSelectFormUseCase` 的 Build/Upgrade/Destroy action 与显示参数
- 缓存当前 Build 槽位对应的视觉信息,供建塔流程复用
约束:
- 只负责 UI 选项配置与只读数据组装,不直接改变战斗状态。
- 不处理鼠标输入、地图拓扑、塔实体生命周期。
### 2.5 TowerPlacementService(塔部署层)
当前文件:`Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs`
职责:
- 建造 / 升级 / 销毁(含升级失败回滚)
- 维护塔映射状态:
- `foundationCell -> towerEntityId`
- `towerEntityId -> foundationCell`
- `towerEntityId -> towerStats`
- 提供整局清理接口
约束:
- 仅处理塔生命周期与映射,不处理选中态和 UI。
### 2.6 TowerSelectionPresenter(选择展示层)
文件:`Assets/GameMain/Scripts/Scene/Map/TowerSelectionPresenter.cs`
职责:
- 维护当前选中对象
- 根据选中状态切换攻击范围显示(通过 `TowerEntity.SetAttackRangeVisible`)
约束:
- 不做建造/升级/销毁。
---
## 3. 运行时主流程(简版)
1. `MapEntity.OnInit` 初始化并绑定 6 个对象:
- `MapTopologyService`
- `CombatSelectInputService`
- `CombatSelectUseCaseConfigurator`
- `TowerPlacementService`
- `TowerSelectionPresenter`
- `CombatSelectFormUseCase`
2. `MapEntity.OnShow` 刷新地图拓扑,配置 UI action 与 Build 视觉数据。
3. 每帧 `OnUpdate`:
- 采集输入(InputService)
- 更新选中对象(SelectionPresenter)
- 打开/刷新 UI
4. UI Build/Upgrade/Destroy action 回调:
- 通过 PlacementService 改变塔状态
- 通过 SelectionPresenter 同步选中和范围显示
5. `OnHide`:
- 关闭 UI
- 隐藏并清理塔实体
- 清理选择态与塔映射运行时状态
- 清理拓扑缓存
---
## 4. 核心不变量(必须保持)
1. `MapEntity` 只编排,不承载业务细节实现。
2. `TowerPlacementService` 是塔映射状态的唯一写入口。
3. `MapTopologyService` 是 Path/Foundation 数据的唯一来源。
4. “当前可见攻击范围”同一时刻最多一个塔。
5. 清场顺序固定:先关闭 UI,再隐藏塔,再清空选择与映射,再清理拓扑缓存。
---
## 5. 扩展开发规范
### 5.1 新增地图规则(地形、禁建、动态阻挡)
优先改 `MapTopologyService`,不要直接改 `MapEntity`。
### 5.2 新增建造规则(费用、限制、回滚策略)
优先改 `TowerPlacementService`,不要在 `MapEntity` 写分支。
### 5.3 新增点击交互或 UI 定位策略
优先改 `CombatSelectInputService`。
### 5.4 新增 Build 选项展示、塔外观颜色或库存驱动 UI 配置
优先改 `CombatSelectUseCaseConfigurator`。
### 5.5 新增选中表现(特效、描边、信息面板联动)
优先改 `TowerSelectionPresenter`。
---
## 6. 代码变更检查清单(PR 自检)
1. 是否把新业务放进了对应服务,而不是 `MapEntity`?
2. 是否破坏了“服务唯一写入口”不变量?
3. Build/Upgrade 失败是否有完整回滚和金币返还?
4. 清理路径是否覆盖了 `OnHide` 与重进地图场景?
5. `dotnet build GeometryTD.sln` 是否通过?
+238
View File
@@ -0,0 +1,238 @@
# Tag System Design
最后更新:2026-03-13
> 目标:这是 GeometryTD 当前 Tag 系统的唯一正式口径。
> 本文档只记录当前真实实现、当前边界与正式规则。
> 长期扩展预案见 `docs/TagSystemRoadmap.md`;若两者冲突,以本文件为准。
## 1. 当前范围
M1 已完成 Tag 最小闭环:
- 组件实例 Tag 已统一生成,不再直接复制 `PossibleTag`
- 塔级 Tag 已统一汇总为 `TagRuntimeData[]`
- UI 展示已优先消费 `TagRuntimes`
- 首发 7 个 Tag 已进入战斗实际生效
当前正式首发 7 个 Tag:
- `Fire`
- `Ice`
- `Crit`
- `Execution`
- `Shatter`
- `Inferno`
- `AbsoluteZero`
当前明确后移的 5 个 Tag:
- `BurnSpread`
- `IgniteBurst`
- `FreezeMask`
- `Pierce`
- `Overpenetrate`
当前不展开:
- 高级传播 / 多命中 / 击杀链式效果
- 复杂流派激活矩阵
- Tag 等级成长
- `TagGroup` 运行时规则
## 2. 正式规则
1. Tag 在组件实例创建时随机;组塔阶段只汇总,不重新随机。
2. 组件表的 `PossibleTag` 只表示候选池,不代表实例最终持有结果。
3. 组塔后重复 Tag 不丢弃,而是转为塔级 `Stack`。
4. 配置层正式采用 `Tag.txt + RarityTagBudget.txt + TagConfig.txt` 三表结构。
5. `Tag.txt` 是 `IsImplemented` 的正式真相源;`TagConfig.txt` 只负责触发阶段、描述与运行时参数。
6. 战斗中的 Tag 统一挂在 `AttackPayload -> HitContext -> TagEffectResolver` 链路上。
7. 新逻辑应优先使用 `TagRuntimes`;`Tags` 只保留兼容投影职责。
8. `TagGroup` 当前只作为展示元数据,不进入生成、汇总或战斗规则。
## 3. 配置与生成
### 3.1 三表职责
`Tag.txt`
- 负责基础字典与生成规则
- 当前字段:`TagType`、`Name`、`TagGroup`、`MinRarity`、`Weight`、`IsImplemented`
`RarityTagBudget.txt`
- 负责按品质定义组件实例本次可抽取的 Tag 数量预算
- 当前字段:`Rarity`、`MinCount`、`MaxCount`
`TagConfig.txt`
- 负责战斗与展示相关配置
- 当前字段:`TriggerPhase`、`Description`、`ParamJson`
### 3.2 当前消费链
- `Tag.txt -> DRTag -> TagGenerationRuleRegistry`
- 当前负责 `MinRarity`、`Weight`
- `Tag.txt + TagConfig.txt -> TagDefinitionRegistry.ReloadFromRows(...)`
- 当前负责 `IsImplemented`、`TriggerPhase`、`Description` 与正式运行时参数
- `ProcedureMain -> GameEntry.TagRegistry.OnInit() -> TagRegistryComponent`
- 当前在主流程进入时统一基于已加载数据表重建 Tag 定义层与生成规则层
- `TagRegistryComponent` 对 `TagConfig`、`Tag`、`RarityTagBudget` 三张表采用 fail-fast 依赖约束;缺表时直接暴露初始化错误,不再静默跳过
- `RarityTagBudget.txt -> DRRarityTagBudget -> RarityTagBudgetRuleRegistry`
- 当前负责按品质读取 `MinCount / MaxCount`
### 3.3 运行时结构
- 组件实例保存 `TagType[]`
- 塔实例保存 `TagRuntimeData[]`
- 塔实例同时保留 `Tags` 作为兼容投影
- 战斗载荷保存塔级 `TagRuntimeData[]`
```csharp
public sealed class TagRuntimeData
{
public TagType TagType { get; set; }
public int TotalStack { get; set; }
}
```
### 3.4 组件 Tag 生成
当前统一入口是 `ComponentTagGenerationService`,以下链路共用同一套规则:
- `InventorySeedUtility`
- `InventoryGenerationComponent.BuildShopGoods(...)`
- `InventoryGenerationComponent.ResolveEnemyDrop(...)`
- `InventoryGenerationComponent.BuildRewardCandidates(...)`
当前流程:
1. 读取组件配置的 `PossibleTag`
2. 读取 `Tag.txt` 中对应的生成规则
3. 过滤掉 `None`、非法枚举、当前未首发支持的 Tag
4. 过滤掉 `MinRarity > 当前组件品质` 的 Tag
5. 根据 `RarityTagBudget.txt` 决定本次抽取数量
6. 在候选池内按 `Weight` 抽取
7. 单组件内不重复抽取同一 Tag
8. 候选池不足时允许少于预算,不强行补满
### 3.5 可复现合同
Tag 随机结果的正式上下文为:
- `RunSeed`
- `SourceType`
- `ItemInstanceId`
- `ConfigId`
当前运行时通过 `InventoryGenerationRandomContext + InventoryTagRandomContext` 统一承载上述字段:
- `InventoryGenerationRandomContext`
- 统一承载 `runSeed + nodeSequenceIndex + sourceType + localOrdinal`
- 统一派生产出链路自己的稳定随机流
- 统一派生稳定的临时组件 `InstanceId`
- `InventoryTagRandomContext`
- 承载 Tag 生成所需的 `RunSeed / SourceType / ItemInstanceId / ConfigId`
- 由 `InventoryGenerationRandomContext` 进一步派生
各来源构造口径:
- `Seed`:使用真实 `RunSeed` 与初始组件实例 Id
- `Shop`:使用 `RunSeed + nodeSequenceIndex + goodsIndex + configId`
- `Drop`:使用 `RunSeed + nodeSequenceIndex + dropOrdinal + configId`
- `Reward`:使用 `RunSeed + nodeSequenceIndex + rewardOrdinal + configId`
## 4. 汇总、展示与战斗
### 4.1 汇总与展示
- 同一组件内不允许重复同一个 Tag
- 不同组件之间允许重复
- 组塔时不做重新随机
- 汇总时按 `TagType` 分组并累加 `Stack`
- 组件展示仍显示组件实例自己的 `Tags`
- 塔展示优先显示 `TagRuntimes`
- 若缺少 `TagRuntimes`,允许通过 `Tags` 回退构建兼容结果
- 重复 Tag 以 `xN` 文本显示,例如 `Fire x2`
- `TowerStatsData.Tags` 不是新的真相源,只用于兼容旧展示链路与旧数据
### 4.2 战斗上下文
`AttackPayload` 当前字段:
- `BaseDamage`
- `AttackPropertyType`
- `SourceEntityId`
- `ProjectileEntityId`
- `OriginPosition`
- `TagRuntimes`
`HitContext` 当前字段:
- `AttackPayload`
- `FinalDamage`
- `IsCriticalHit`
- `IsKilled`
- `TargetEntityId`
- `TargetPosition`
- `TargetCurrentHealthBeforeHit`
- `TargetCurrentHealthAfterHit`
- `TargetMaxHealth`
- `TargetMoveSpeedMultiplierBeforeHit`
- `TargetStatusTagsBeforeHit`
- `TargetStatusRuntime`
- `CritRoll`
- `StatusModifierContext`
`HitContext` 当前还提供:
- `HasTargetStatus(TagType)`
- `HasSlowStatusBeforeHit`
### 4.3 分类与触发阶段
| 分类 | 说明 | 当前主触发阶段 |
|------|------|----------------|
| `Status` | 命中后在敌人身上形成可持续状态 | `OnAfterHit` |
| `NumericModifier` | 命中前修正最终伤害 | `OnBeforeHit` |
| `AttackShape` | 穿透、传播、爆炸等攻击形态变化 | `OnHit` / `OnKill` |
| `StatusModifier` | 强化同次命中的状态类 Tag,但不独立生成敌人持有状态 | `OnAfterHit` |
### 4.4 当前已实现的首发 7 Tag
| Tag | 分类 | 配置阶段 | 当前真实行为 |
|-----|------|----------|--------------|
| `Fire` | `Status` | `OnAfterHit` | 命中后施加燃烧 DOT,且 `MaxEffectiveStack` 已进入基础层数结算 |
| `Ice` | `Status` | `OnAfterHit` | 命中后施加减速 |
| `Crit` | `NumericModifier` | `OnBeforeHit` | 按概率暴击并放大伤害 |
| `Execution` | `NumericModifier` | `OnBeforeHit` | 对低血量目标增伤 |
| `Shatter` | `NumericModifier` | `OnBeforeHit` | 对已减速目标增伤 |
| `Inferno` | `StatusModifier` | `OnAfterHit` | 先由 resolver 解析为命中期修饰,再强化同次命中的 `Fire` 时长与伤害 |
| `AbsoluteZero` | `StatusModifier` | `OnAfterHit` | 先由 resolver 解析为命中期修饰,再强化同次命中的 `Ice` 时长与减速强度 |
### 4.5 当前已后移的 5 Tag
| Tag | 分类 | 当前状态 |
|-----|------|----------|
| `BurnSpread` | `AttackShape` | 仅保留元数据、占位配置与占位路由,未实际生效 |
| `IgniteBurst` | `AttackShape` | 仅保留元数据、占位配置与占位路由,未实际生效 |
| `FreezeMask` | `AttackShape` | 仅保留元数据、占位配置与占位路由,未实际生效 |
| `Pierce` | `AttackShape` | 仅保留元数据、占位配置与占位路由,未实际生效 |
| `Overpenetrate` | `AttackShape` | 仅保留元数据、占位配置与占位路由,未实际生效 |
## 5. 当前运行时边界
- 数值类 Tag 在 `ResolveBeforeHit` 阶段生效
- 状态类 Tag 通过 `EnemyTagStatusRuntime` 管理敌人持有状态
- `StatusModifier` 已独立按 `OnAfterHit` 路由,并写入 `StatusModifierContext`
- `Inferno` 与 `AbsoluteZero` 不生成敌人持有状态;没有 `Fire` / `Ice` 时不会单独产生效果
- 后移 5 个攻击形态 Tag 的 `TriggerPhase` 当前正式口径为 `None`
- 后移 5 个攻击形态 Tag 的 `ParamJson` 当前只视为占位说明,不是正式运行时字段
## 6. 非当前范围
- `BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 的真实战斗实现
- 更复杂的多 Tag 联动与流派激活矩阵
- 冻结积累条、击杀爆炸传播链、多命中弹道系统
- Tag 等级成长、TagGroup 运行时规则、额外 Tag 元数据扩展表
+62
View File
@@ -0,0 +1,62 @@
# Tag System Roadmap
最后更新:2026-03-12
> 目标:记录 Tag 系统的长期扩展预案。
> 本文档不是当前实现真相源;若与 `docs/TagSystemDesign.md` 冲突,以 `docs/TagSystemDesign.md` 为准。
## 1. 文档定位
- `TagSystemDesign.md` 负责当前正式口径
- 本文档只负责后续扩展方向
- 这里出现的能力都不代表当前已经实现,也不代表已经排进最近一期开发
## 2. 可继续推进的方向
### 2.1 第二批攻击形态 Tag
后续可基于现有 `AttackPayload -> HitContext -> TagEffectResolver` 合同继续推进:
- `BurnSpread`
- `IgniteBurst`
- `FreezeMask`
- `Pierce`
- `Overpenetrate`
进入实现前提:
- 不破坏当前 `HitContext` 主干
- 优先补上下文字段,不回退到散装参数扩展
- 每个 Tag 进入真实战斗效果前,都要先明确它需要的命中信息与回归测试
### 2.2 更复杂的多 Tag 联动
可考虑的后续方向:
- 更深的状态连锁
- 击杀传播或爆炸传播链
- 多 Tag 组合触发的特殊行为
进入实现前提:
- 先定义联动的真相源与优先级
- 不把展示元数据直接升级成运行时规则
### 2.3 成长与元数据扩展
可考虑的后续方向:
- Tag 等级成长
- 更多 Tag 元数据扩展表
- 运行时需要时再讨论 `TagGroup` 是否升级为规则输入
进入实现前提:
- 当前三表职责仍保持清晰
- 新字段必须同时明确配置真相源、运行时消费者与展示口径
## 3. 当前默认约束
- 第二批 Tag 未进入真实战斗效果前,继续保持占位配置与占位路由
- `TagGroup` 继续只作为展示元数据,不提前接入运行时
- 新预案优先进入本文件,不回写到 `TagSystemDesign.md` 主干
+182
View File
@@ -0,0 +1,182 @@
# UI 五层架构设计规范(RawData / Controller / View / Context / UseCase)
## 1. 适用范围
- 适用目录:`Assets/GameMain/Scripts/UI/*`
- 重点对象:采用五层拆分的 UI 模块(`MenuScene`、`GameScene`、`General` 下的分层 UI)
- 本文不展开 Unity GameFramework 底层实现细节,仅约束项目内 UI 代码组织与协作方式
## 2. 架构总览
UI 模块采用“输入数据 -> 业务编排 -> 展示数据 -> 渲染表现”的分层方式,核心链路如下:
1. 外部流程(Procedure/GameState)创建并绑定 UseCase
2. 通过 `GameEntry.UIRouter` 打开指定 UI
3. Controller 从 UseCase 取 RawData,并转换为 Context
4. View 使用 Context 渲染
5. View 通过事件回传交互,Controller 处理后驱动 UseCase 更新,再刷新 View
简化关系图:
```text
Procedure/GameState
-> UIRouter
-> Controller <-> UseCase
-> Context -> View
View --(CustomEvent)--> Controller
```
## 3. 五层职责定义
### 3.1 RawData 层
职责:承载“业务原始数据”,作为 UseCase 到 Controller 的传输模型。
约束:
- 命名:`XXXFormRawData`
- 只描述数据,不包含 UI 渲染行为
- 可保留领域对象或数据表对象(例如 `DRLevelUpReward`、`WeaponBase`)
- 不依赖具体 View 组件
参考:
- `Assets/GameMain/Scripts/UI/Template/GameScene/RawData/ShopFormRawData.cs`
- `Assets/GameMain/Scripts/UI/Template/GameScene/RawData/LevelUpFormRawData.cs`
- `Assets/GameMain/Scripts/UI/Template/MenuScene/RawData/SelectRoleFormRawData.cs`
### 3.2 UseCase 层
职责:封装 UI 对应业务用例,负责业务规则、状态推进、数据生成。
约束:
- 实现 `IUIUseCase`
- 命名:`XXXFormUseCase`
- 对外提供 `CreateInitialModel / TryRefresh / Select / Confirm` 等语义化方法
- 返回 RawData(或结果对象),不直接操作具体 View
参考:
- `Assets/GameMain/Scripts/UI/Template/GameScene/UseCase/ShopFormUseCase.cs`
- `Assets/GameMain/Scripts/UI/Template/GameScene/UseCase/LevelUpFormUseCase.cs`
- `Assets/GameMain/Scripts/UI/Template/MenuScene/UseCase/SelectRoleFormUseCase.cs`
### 3.3 Controller 层
职责:UI 编排层,连接 UseCase 与 View,管理 UI 生命周期、事件订阅、数据转换。
约束:
- 继承 `UIFormControllerCommonBase<TContext, TForm>`
- 命名:`XXXFormController`
- 通过 `BindUseCase(IUIUseCase)` 注入用例并做类型校验
- `OpenUI(object userData = null)` 支持:`Context`、`RawData`、`null`
- 负责 RawData -> Context 的转换(常见 `BuildContext`)
- 在 `SubscribeCustomEvents / UnsubscribeCustomEvents` 成对管理事件
- 可做局部刷新(避免整窗重建)
参考:
- `Assets/GameMain/Scripts/UI/Template/GameScene/Controller/ShopFormController.cs`
- `Assets/GameMain/Scripts/UI/Template/GameScene/Controller/LevelUpFormController.cs`
- `Assets/GameMain/Scripts/UI/Template/MenuScene/Controller/SelectRoleFormController.cs`
### 3.4 Context 层
职责:承载“可直接驱动 UI 展示”的上下文数据。
约束:
- 继承 `UIContext`
- 命名:`XXXFormContext` 或 `XXXItemContext`
- 字段以展示友好为目标(标题、描述、图标、稀有度、列表等)
- 允许组合子 Context(例如列表区 + 条目)
参考:
- `Assets/GameMain/Scripts/UI/Template/GameScene/Context/ShopFormContext.cs`
- `Assets/GameMain/Scripts/UI/Template/GameScene/Context/DisplayListAreaContext.cs`
- `Assets/GameMain/Scripts/UI/Template/MenuScene/Context/SelectRoleFormContext.cs`
### 3.5 View 层
职责:纯表现层,负责控件绑定、显示刷新、交互事件抛出。
约束:
- Form 类继承 `UGuiForm`,子组件通常继承 `MonoBehaviour`
- 命名:`XXXForm` / `XXXItem` / `XXXArea`
- 提供 `RefreshUI(Context)`、`OnInit(Context)`、`OnReset()` 等渲染入口
- 用户交互通过 `GameEntry.Event.Fire(...)` 通知 Controller
- 不承载业务规则(计算、流程推进、数据筛选应在 UseCase)
参考:
- `Assets/GameMain/Scripts/UI/Template/GameScene/View/ShopForm.cs`
- `Assets/GameMain/Scripts/UI/Template/GameScene/View/DisplayListArea.cs`
- `Assets/GameMain/Scripts/UI/Template/MenuScene/View/SelectRoleForm.cs`
## 4. 标准交互流程
### 4.1 初始化与绑定
1. Procedure/GameState 创建 UseCase
2. 调用 `GameEntry.UIRouter.BindUIUseCase(UIFormType.X, useCase)`
### 4.2 打开 UI
1. 调用 `GameEntry.UIRouter.OpenUI(UIFormType.X)`
2. Controller 从 UseCase 取 RawData(或接收外部 RawData/Context)
3. Controller 构建 Context 后打开/刷新 Form
4. View 在 `OnOpen` 中校验 Context 类型并执行 `RefreshUI`
### 4.3 用户交互到刷新
1. View 触发事件(如购买、刷新、选择)
2. Controller 监听事件并调用 UseCase
3. UseCase 返回新数据或操作结果
4. Controller 更新 Context 并刷新全部或局部 UI
### 4.4 关闭 UI
1. 调用 `GameEntry.UIRouter.CloseUI(UIFormType.X)`
2. Controller 解除事件订阅并关闭窗体
3. View `OnClose` 清理本地状态
## 5. 目录与命名规范
- 目录:`UI/<SceneDomain>/RawData|UseCase|Controller|Context|View`
- 五层同名前缀保持一致:`ShopForm*`、`LevelUpForm*`、`SelectRoleForm*`
- 子组件上下文命名:`RoleItemContext`、`DisplayItemContext`、`LevelUpRewardItemContext`
- 新增 UI Form 时优先建立完整五层;仅纯静态展示可降级为 View-only
## 6. 依赖方向约束
允许依赖:
- `UseCase -> RawData / 领域对象`
- `Controller -> UseCase + RawData + Context + View + Event`
- `View -> Context + Event`
禁止依赖:
- `View -> UseCase`
- `View -> 领域状态修改`
- `Context/RawData -> View`
## 7. 新增一个五层 UI 的落地步骤
1. 在目标场景目录创建 `RawData / UseCase / Context / Controller / View` 对应类型
2. 在 UseCase 中实现模型创建与交互方法
3. 在 Controller 中实现 `BindUseCase`、`OpenUI`、`BuildContext`、事件订阅
4. 在 View 中实现 `RefreshUI` 和交互事件抛出
5. 在对应 Procedure/GameState 里完成 UseCase 绑定与 Open/Close 调用
6. 自测三条主链路:首次打开、交互刷新、关闭重开
## 8. 项目当前实践说明
- `ShopForm`、`LevelUpForm`、`SelectRoleForm` 是当前五层模式的主要样板
- `DialogForm` 也有 Controller/Context/RawData,但 UseCase 为可选
- `HudForm`、`StartMenuForm` 当前为轻用例场景,可不强制 UseCase
- `SettingForm`、`AboutForm` 属于历史直连型 UI,不属于五层完整样板