You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
AdLike2048/assets/script/battle/接入指南.md

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 = 12
    • mapCols = 10
    • mapGridSize = 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 新建地图步骤

  1. 复制 mapLayerTemplate.prefab → mapLayer{新id}.prefab
  2. 设置 MapManager:12 / 10 / 75
  3. 在「格子」下摆放 120 个 mapGrid 子预制体(12 行 × 10 列)
  4. 配置出怪口、目标点位置
  5. 在 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.id
  • weaponlevel → 等级(后续扩展属性倍率)

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;
}

内部流程:

  1. ChapterLevelBuilder.build → 生成 LevelDef(波次、金币、12×10)
  2. TableBattleConfigProvider.registerLevel → 注册敌人 Unit
  3. BattleFactory.create → 创建 MX BattleSession
  4. 默认进入 PREP 阶段

4.3 加载地图(Cocos 场景)

方式 A — 场景挂组件(推荐)

  1. 战斗场景建空节点 BattleRoot
  2. 挂 CocosBattleRoot + CocosBattleMapHost
  3. Inspector 设置 chapterId、mode
  4. 可选: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