6.3 KiB
6.3 KiB
CombatNode 架构摘要
最后更新:2026-02-28
1. 目标与边界
CombatNode 子系统的目标是把“战斗节点”拆成三个稳定层:
CombatNodeComponent:节点入口与配置缓存(门面层)CombatScheduler:单局战斗状态机(编排层)EnemyManager:刷怪与敌人生命周期(执行层)
边界约束:
- 只负责战斗节点,不负责菜单流程切换,不负责 UI 细节。
- 只依赖数据表和 Entity 系统,不直接持有场景流程 FSM。
- 地图寻路由
MapEntity提供能力,敌人移动由EnemyEntity自治。
2. 模块职责划分
2.1 CombatNodeComponent(门面层)
文件:Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatNodeComponent.cs
职责:
- 读取并缓存
DRLevel / DRLevelPhase / DRLevelSpawnEntry。 - 按主题筛选关卡,维护
Level -> Phase -> SpawnEntry映射。 - 提供
OnInit / StartCombat / OnUpdate / OnShutdown外部接口。 - 触发节点事件:
NodeEnterEventArgsNodeCompleteEventArgs
不做的事:
- 不做阶段推进。
- 不做刷怪时序。
- 不做实体显示/隐藏细节。
2.2 CombatScheduler(编排层)
文件:Assets/GameMain/Scripts/CustomComponent/CombatNode/CombatScheduler.cs
职责:
- 管理战斗状态机:
Idle -> WaitingForMap -> RunningPhase -> Completed/Failed。 - 加载地图实体,接收地图 show/hide 成功失败事件。
- 按 phase 结束条件推进到下一阶段。
- 在开始新战斗或销毁时做统一清场(地图 + 本局敌人)。
不做的事:
- 不直接计算每条刷怪规则的触发细节(交给
EnemyManager)。
2.3 EnemyManager(执行层)
文件:Assets/GameMain/Scripts/CustomComponent/CombatNode/EnemyManager.cs
职责:
- 将
DRLevelSpawnEntry转为运行时任务(stream/burst/boss)。 - 按时间推进刷怪任务并生成
EnemyData。 - 维护本局敌人数量
AliveEnemyCount。 - 维护“本局生成敌人 ID 集合”,用于准确计数与清场。
关键点:
- 生命周期归属按
entityId跟踪,不再按实体组名粗粒度统计。 - 清场可覆盖“已加载 + 加载中”的本局敌人。
3. 关键实体职责
3.1 MapEntity
文件:Assets/GameMain/Scripts/Entity/EntityLogic/MapEntity.cs
职责:
- 读取 Tilemap,识别 Path/Foundation 格子。
- 为每个
Spawner缓存默认可达路径。 - 提供:
TryGetDefaultPathCellsTryFindPathCellsTryFindPathWorldPoints
3.2 EnemyEntity
文件:Assets/GameMain/Scripts/Entity/EntityLogic/EnemyEntity.cs
职责:
- 按路径点移动。
- 到达终点后调用
HideEntity自行退出。
4. 运行时时序(简版)
- 菜单触发战斗:
TestMenuFormController -> GameEntry.CombatNode.StartCombat() CombatNodeComponent.StartCombat()选关并把关卡配置交给CombatScheduler.Start()CombatScheduler.Start()先清理上局残留,再ShowMap(...),状态进入WaitingForMap- 地图加载成功事件到达,
_currentMap就绪 BeginNextPhase()进入RunningPhase,EnemyManager.BeginPhase(...)- 每帧
OnUpdate:CombatScheduler更新时间与结束条件EnemyManager推进刷怪任务并创建敌人
- 当前 phase 满足结束条件,
CompleteCurrentPhase(),然后进入下一 phase - 全 phase 完成后隐藏地图,
GameEntry.CombatNode.EndCombat(),抛NodeComplete
5. 数据契约与 ID 规则
数据来源:
DRLevelDRLevelPhaseDRLevelSpawnEntry
当前约定(依赖 ID 编码):
levelId = phaseId / 1000phaseId = spawnEntryId / 1000
影响:
- 新增配置时必须遵守该编码,否则 phase 和 entry 无法被正确归属。
建议:
- 若后续改数据结构,优先改成显式外键字段,减少 ID 推导耦合。
6. 结束条件模型
枚举:PhaseEndType
TimeElapsed:按EndParam(可解析 float)或DurationSecondsEnemiesCleared:IsPhaseSpawnCompleted && AliveEnemyCount <= 0BossDead:当前与EnemiesCleared同判定None:有 duration 走 duration,否则退化为清怪判定
实现点:CombatScheduler.ShouldEndCurrentPhase()
7. 生命周期与清场策略(当前实现)
7.1 地图生命周期
- 通过
ShowMap异步加载。 - 通过
ShowEntitySuccess/Failure绑定运行态地图引用。 - 完成关卡时隐藏地图。
- 新战斗开始和调度器销毁前都会尝试清理地图实体。
7.2 敌人生命周期
- 创建时登记
entityId到_trackedEnemyEntityIds。 ShowEntitySuccess仅对 tracked id 计数。HideEntityComplete仅对 tracked id 减计数并移除跟踪。- 清场时按 tracked id 隐藏(覆盖加载中和已加载实体)。
7.3 设计目标
- 防止跨局残留实体污染新局状态。
- 防止其他系统
Enemy组实体干扰战斗计数。
8. 扩展指南(按需求)
8.1 新增阶段结束条件
- 改
PhaseEndType枚举。 - 在
ShouldEndCurrentPhase()增加分支。
8.2 新增刷怪条目类型
- 改
EntryType枚举。 - 在
EnemyManager.BuildSpawnRuntime()与UpdateSpawnRuntimes()增加处理。
8.3 改选关策略
- 当前是随机选关:
TrySelectRandomLevel()。 - 可改为按难度、解锁进度、权重池。
8.4 动态阻挡/重算路径
- 当前
SpawnEnemies调TryFindPathWorldPoints(spawner, null, ...)。 - 若支持塔阻挡,传入
blockedCells并处理“无路可走”策略。
8.5 结算与奖励
- 推荐挂在
CombatScheduler完成分支(所有 phase 完成处),不要塞进EnemyManager。
9. 维护时的硬性不变量
CombatNodeComponent只做缓存和入口,不写战斗细节。CombatScheduler是唯一 phase 状态机,不在别处推进 phase。AliveEnemyCount必须由 tracked id 驱动,不回退到组名统计。- 新战斗开始前必须清场。
ResetRuntime()必须清空地图相关运行态引用和 id。
10. 快速阅读路径(新人 10 分钟)
CombatNodeComponent.StartCombat()CombatScheduler.Start() / OnUpdate() / BeginNextPhase()EnemyManager.BeginPhase() / OnUpdate() / SpawnEnemies()MapEntity.TryFindPathWorldPoints()EnemyEntity.DespawnOnReachHouse()
这 5 个点读完,基本能覆盖 90% 战斗节点行为。