14 KiB
塔防战斗 — 项目层接入指南
旧 BattleCore 已移除,逻辑内核为 MX
BattleSession+ 项目层BattleFacade。
战斗 UI 脚本:assets/script/ui/BattleGame/UIBattleGame.ts(保留@ccclass('UIBattleGame')以兼容预制体)。
怪物表现组件仍在ui/BattleGame/Monster.ts(prefab 绑定),通过IBattleMonsterView契约与 battle 层解耦。
外围 UI(结算/技能选择/机制等)直接读写BattleRuntime上的状态字段与桥接方法。
本文说明:如何配地图、如何配表、如何初始化战斗。
逻辑内核在 MX 通用模块(assets/mx/module/battle),项目层在 assets/script/battle。
需求与架构约束:docs/需求说明.md
技能逻辑层方案:docs/方案-战斗技能逻辑层.md
术语速查
| 说法 | 配表 | 含义 |
|---|---|---|
| 技能 | Skill / ITableSkill |
战斗技能(普攻/主动/被动) |
| 普攻 | Hero.atk_id[level-1] |
随合成等级 |
| 英雄技能 | Hero.act_skill[level-1] |
可无 |
| 武器技能 | Weapon.act_skill |
局外装备;type=1 主动 / type=2 被动 |
一、架构概览
assets/mx/module/battle/ ← 通用战斗内核(可跨项目复用,无 Cocos 依赖)
assets/script/battle/ ← 本项目适配层
BattleFacade.ts ← 唯一对外入口
config/ ← 配表 → MX 配置
runtime/ ← 战斗运行时
map/ ← 地图规格 & 预制体加载
view/ ← Cocos 表现桥接(Presenter、契约接口)
assets/script/ui/BattleGame/ ← 战斗 UI + 怪物 prefab 脚本(Monster)
数据流:
Excel/JSON 配表
→ gg.data.table
→ TableBattleConfigProvider + ChapterLevelBuilder
→ MX BattleSession(放置、出怪、技能、伤害)
→ BattleEventBus(UnitSpawned / WaveStart / …)
→ CocosBattleMonsterPresenter → Monster + MapAgent
出怪与移动(表现层):
WaveSpawner.tick → UnitSpawned
→ CocosBattleMonsterPresenter.spawnVisual
→ spawnPoint_N 世界坐标(精确,不偏移)
→ Monster.bindPathTarget → MapAgent.setTarget
→ MapManager 8 方向距离场(getPathShortestTargetGrid)
→ MapAgent.move(COMBAT 阶段 CanMove=true)
地图须满足制作规范(MapManager.validateMapPoints 在 initMapGrids 后校验):
- 出怪口
spawnPoint_N落在 可走格 上 - 攻击点(目标点)可映射到最近可走格(城墙节点常在不可走格,属正常设计)
- 地图就绪后广播
GEvent.MapGridsReady,禁止用定时器重试寻路
二、如何配地图
2.1 格子规格(全局常量)
| 项 | 值 | 说明 |
|---|---|---|
| 行数 | 12 | 竖向 |
| 列数 | 10 | 横向 |
| 格大小 | 75×75 px | 与 BattleMapSpec 一致 |
代码常量:assets/script/battle/map/BattleMapSpec.ts
2.2 地图预制体
- 路径:
assets/bundles/battleMap/prefab/地图/mapLayer{mapId}.prefab - 命名:
mapLayer1、mapLayer2… 与 Chapter 表mapId对应 - 根节点:挂
MapManager组件,字段必须为:mapRows = 12mapCols = 10mapGridSize = 75
2.3 预制体节点结构
mapLayer{N}
├── 格子 ← 120 个子节点,每个挂 MapGrid
├── 出怪口 ← spawnPoint_1 … spawnPoint_N(顶部出怪)
├── 目标点 ← 怪物攻击终点(城墙侧)
└── …(装饰层可选)
MapGrid 常用标记:
| 字段 | 单人闯关 | 说明 |
|---|---|---|
isCanPutObstacle / 可建造 |
全图可建区域 | 塔防改为 isCanPutUnit 语义(后续 MapGrid 扩展) |
isCanWalk |
出怪路径可走 | 放塔默认 不挡路 |
isCanRemove |
是否可铲除 | 按关卡设计 |
2.4 单人 vs 双阵营
| 模式 | 可编辑区域 | 显示 |
|---|---|---|
| solo | 全图 | 逻辑坐标即显示坐标 |
| pvp | 全图(落子按 CampAreaID 隔离) |
逻辑坐标即显示坐标;双方各一玩家槽 |
阵营对抗用同一张全图 + CampAreaPolicy,不再做半图镜像。
2.5 新建地图步骤
- 复制
mapLayerTemplate.prefab→mapLayer{新id}.prefab - 设置
MapManager:12 / 10 / 75 - 在「格子」下摆放 120 个
mapGrid子预制体(12 行 × 10 列) - 配置出怪口、目标点位置
- 在 Chapter 表 把
mapId设为该 id
2.6 运行时加载
import { CocosBattleMapHost } from 'db://assets/script/battle';
// 组件方式:挂 CocosBattleMapHost,调用 loadMap(mapId)
mapHost.loadMap(chapterMapId);
或由 CocosBattleRoot 在 onLoad 时自动按当前章节 mapId 加载。
三、如何配表
3.1 必须注册的表名
在 ConfigTableData.ts → TableNames:
| 表名 | 文件 | 用途 |
|---|---|---|
Chapter |
Chapter.json |
章节、mapId、出怪串、经济 |
Weapon1 |
Weapon1.json |
塔单位数值(攻击、CD、射程) |
Weapon2 |
Weapon2.json |
棋子/字块类型(背包 UI,type 1/2/3/4) |
BagWeapon |
BagWeapon.json |
背包棋子池、权重 |
Monster |
Monster.json |
敌人属性(HP、速度、模型) |
3.2 Weapon1 — 塔战斗数值
每个可放置的攻击单位一条:
{
"id": 1,
"name": "刀",
"weapon_para": 1,
"cd": 1,
"range": 200,
"critical_chance": 1000,
"critical_dam": 20000,
"size": 1,
"group": [1]
}
映射规则(TableBattleConfigProvider):
| 字段 | 战斗含义 |
|---|---|
weapon_para |
攻击力基数 ×10 |
cd |
普攻间隔(秒) |
range |
索敌射程(像素,与 base_atk.range 一致;-1=全屏) |
critical_* |
暴击率 / 暴击伤害 |
运行时 unitId:tower_w1_{id},技能 id:ability_w1_{id}。
3.3 Weapon2 — 棋子/字块(背包)
{ "id": 1, "type": 1, "resource": "刀", "name": "刀", "weight": 10, "unlock": 1 }
| type | 含义 |
|---|---|
| 1 | 单字武器 |
| 2 | 组合字 |
| 3 | 格子(扩展可建造区域) |
| 4 | 金手指 |
背包逻辑在 UI/背包模块扩展;战斗放置时传入 Weapon1 id。
3.4 BagWeapon — 背包池
{ "id": 11, "type": 1, "weaponid": 1, "weaponlevel": 1, "weight": 10 }
weaponid→ 对应 Weapon1.idweaponlevel→ 等级(后续扩展属性倍率)
3.5 Chapter — 关卡与出怪
关键字段:
| 字段 | 说明 |
|---|---|
mapId |
对应 mapLayer{mapId}.prefab |
base_coin |
初始银币(按难度数组或单值) |
monster_group / hp_multiple |
难度系数(项目层扩展 HP 用) |
monster1 |
出怪端口 1(主端口) |
monster2_1 … |
端口 2 按难度 |
monster3_1 … |
端口 3 按难度 |
出怪串语法(MonsterWaveParser):
波1 | 波2 | 波3
↑ 竖线分隔波次
波内:101,10,1;102,5,2
↑ 分号 = 同波顺序段(前段出完再出下段)
↑ 逗号 = monsterId, 数量, 难度行(Monster2表)
示例:
|13,6,10|12,3,10;10012,3,10
- 第 1 波:空(
|开头) - 第 2 波:怪物 13 ×6,难度行 10
- 第 3 波:先 12×3,再 10012×3(顺序段)
敌人 unitId:monster_{Monster表id},开战前自动从 Monster.json 生成 UnitDef。
3.6 扩展技能 / Buff
mx 内核只提供通用塔防原语(DealDamage / Heal / ApplyBuff / SpawnProjectile / ModifyAttribute 等,由 registerBuiltinBattleEffects 注册)。业务特有效果在业务层注册,不改 mx:
// assets/script/battle/registry/ProjectEffectTypes.ts
export const ProjectEffect = {
ChainLightning: 'ChainLightning',
} as const;
// assets/script/battle/registry/registerProjectEffects.ts
import { EffectRegistry } from 'db://assets/mx/module/battle';
import { ProjectEffect } from './ProjectEffectTypes';
EffectRegistry.register(ProjectEffect.ChainLightning, (services, ctx, params) => {
// services: BattleServices 聚合(world / damage / buffs / targets ...)
// ctx: AbilityContext(caster / targets / rng ...)
// params: 配表 effect.params
const amount = services.damage.calc(ctx, params);
for (const targetId of ctx.targets) {
services.damage.apply(ctx.caster, targetId, amount, 'electric', ctx);
}
});
registerProjectEffects() 在 createBattleRuntime 时自动调用(幂等)。配表 effect.type 填 ProjectEffect 中声明的字符串即可。
四、如何初始化战斗
4.1 游戏启动(一次)
import { BattleFacade } from 'db://assets/script/battle';
BattleFacade.init(); // 加载 Weapon1 → 塔单位映射
建议放在进入大厅前或 GameController 初始化处。
4.2 进入关卡
import { BattleFacade } from 'db://assets/script/battle';
import { ChapterDifficulty } from 'db://assets/script/manager/ChapterDataManager';
const battle = BattleFacade.start({
chapterId: 1,
difficulty: ChapterDifficulty.Normal,
mode: 'solo', // 或 'pvp'
seed: 12345, // 可选,对战/回放
});
if (!battle) {
// 章节不存在或配表错误
return;
}
内部流程:
ChapterLevelBuilder.build→ 生成LevelDef(波次、金币、12×10)TableBattleConfigProvider.registerLevel→ 注册敌人 UnitBattleFactory.create→ 创建 MXBattleSession- 默认进入 PREP 阶段
4.3 加载地图(Cocos 场景)
方式 A — 场景挂组件(推荐)
- 战斗场景建空节点
BattleRoot - 挂
CocosBattleRoot+CocosBattleMapHost - Inspector 设置
chapterId、mode - 可选:
autoStartCombat、debugDeployWeapon1Id做快速测试
方式 B — 代码
import { CocosBattleMapHost } from 'db://assets/script/battle';
const battle = BattleFacade.start({ chapterId: 1, mode: 'solo' });
mapHost.loadMap(battle.mapId);
4.4 部署阶段(PREP)
// 放置塔:Weapon1.id = 1 的「刀」在 (row=8, col=4)
BattleFacade.deploy({
weapon1Id: 1,
row: 8,
col: 4,
});
4.5 波次制流程(单人闯关)
进关 → PREP(放置单位)
→ 玩家点「继续游戏」→ COMBAT(出当前波怪)
→ 本波怪出完且全部击杀 → PREP
→ 再点「继续游戏」→ 下一波 COMBAT
→ … 最后一波清空 → BattleEnd(win)
- PREP:可
BattleFacade.deploy()放置塔(PrepPhaseOnlyRule限制仅备战可放) - COMBAT:
WaveSpawner出怪;塔自动攻击(待表现层接入) - 胜利:所有波次怪物击杀完毕(
onWaveCleared→ 无下一波 →endBattle(true))
// 备战结束,玩家主动开战
BattleFacade.startCombat(); // 仅 PREP 有效,开始下一波
4.6 每帧驱动
// CocosBattleRoot.update 已自动调用
BattleFacade.tick(dt);
4.7 监听事件(UI / 表现)
BattleFacade.current?.on('UnitPlaced', (e) => { /* 创建塔 Node */ });
BattleFacade.current?.on('DamageDealt', (e) => { /* 飘字 */ });
BattleFacade.current?.on('WaveStart', (e) => { /* 波次 UI */ });
BattleFacade.current?.on('BattleEnd', (e) => { /* 结算 */ });
4.8 退出战斗
BattleFacade.end();
五、目录速查
| 路径 | 职责 |
|---|---|
battle/BattleFacade.ts |
对外 API |
battle/runtime/BattleRuntime.ts |
单次战斗生命周期 |
battle/config/TableBattleConfigProvider.ts |
Weapon1/Monster → MX Unit |
battle/config/ChapterLevelBuilder.ts |
Chapter → LevelDef |
battle/config/MonsterWaveParser.ts |
出怪串解析 |
battle/map/BattleMapSpec.ts |
12×10×75 常量 |
battle/map/CocosBattleMapHost.ts |
加载 mapLayer 预制体 |
battle/view/CocosBattleMonsterPresenter.ts |
UnitSpawned → 怪物 Node |
battle/view/IBattleMonsterView.ts |
怪物视图组件契约 |
battle/runtime/BattleRuntime.ts |
运行时 + UI/NewMechanism 状态桥接 |
ui/BattleGame/UIBattleGame.ts |
战斗 HUD、地图加载、tick |
ui/BattleGame/Monster.ts |
怪物 prefab 视图 + 寻路绑定 |
ui/BattleGame/MapAgent.ts |
格子寻路移动 |
六、快速验证清单
Weapon1.json已打入 bundle,TableNames.Weapon1已注册Chapter.json中mapId指向存在的mapLayer{id}.prefab- 地图 prefab
MapManager= 12 / 10 / 75 - 战斗场景已挂
CocosBattleRoot - 调用
BattleFacade.init()后再start() deploy使用 Weapon1.id,不是 Weapon2.id
七、与 MX 通用模块关系
- 项目层只做:读表、拼 Level、加载地图 prefab、Cocos 表现
- MX 层负责:ECS、放置规则、Ability/Effect、波次、伤害、单人/对战模式
跨项目复用时:复制 assets/mx/module/battle,在新项目实现各自的 TableBattleConfigProvider 等价物即可。
八、编码规范(MX / 项目层 / UI 统一)
| 规则 | 做法 |
|---|---|
| 禁止魔法字符串 | 事件用 BattleEventType + BattleEventFactory;标签用 EntityTag;Effect/Condition/Trigger 类型用业务层 ProjectEffect / ProjectCondition / ProjectTrigger 常量集(mx 内置原语直接用字符串字面量如 'DealDamage') |
| 配置 ID | 使用 UnitDefId 等类型别名,构造 ID 用 makeMonsterUnitDefId() |
| import 路径 | MX 模块内跨目录用 db://assets/mx/module/battle/...,避免 IDE 报红 |
| 文档 | 每个 public 类/模块须有文件头 JSDoc;复杂流程写接入文档数据流 |
| 失败处理 | 配表/地图错误 console.error 并中止,不用静默 fallback |