geometry-tower-defense-base/docs/CombatNodeArchitecture.md

737 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` 较低515精英敌人较高2050`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 | 建议普通关卡 50200Boss 关卡 100300 |
| `DREnemy.DropCoin` | ≥ 0 | 建议普通敌人 515精英 2050Boss 0避免重复计费 |
| `buildTowerCost[i]` | ≥ 0 | 由 `CombatSelectFormUseCase` 从关卡配置读取,建议 2080 每槽位 |
| `upgradeCost` | ≥ 0 | 由 `CombatSelectFormUseCase` 从关卡配置读取,建议 50150 |
| `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` 不变 |