|
|
|
|
# 方案-出怪逻辑实现
|
|
|
|
|
|
|
|
|
|
> 状态:出怪 + 到达扣营地血 + 营地血条 + 胜负已落地。审阅基线见 Cursor Plan v2。
|
|
|
|
|
|
|
|
|
|
## 1. 目标与范围
|
|
|
|
|
|
|
|
|
|
| 项 | 说明 |
|
|
|
|
|
|---|---|
|
|
|
|
|
| 对战 | **PVE**:玩家 vs **AI**(程序控制),双阵营地图对称出怪 |
|
|
|
|
|
| 进关 | 首页开始 → 按当天 Const 31~37 地图池随机 Level.id;GM「固定地图」可锁 id |
|
|
|
|
|
| 出怪 | `Level.monster` + `Level.boss` + `Level.bosspro` |
|
|
|
|
|
| 出怪口 | 玩家 port1,AI port2,**同一套波次配置** |
|
|
|
|
|
| 队列 | 每波口内 **顺序段**:先小怪段,再 Boss 段(`WaveSpawner` segment 顺序) |
|
|
|
|
|
| 预制体 | `Monster.icon` → `battleGameMonster/prefab/monster/{icon}` |
|
|
|
|
|
| 本期不做 | 塔攻击 |
|
|
|
|
|
|
|
|
|
|
## 2. 配表
|
|
|
|
|
|
|
|
|
|
### Level.monster(四列)
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
waveIndex,monsterId,count,spawnInterval
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`|` 分隔 25 波。例:`1,1001,10,1.6`
|
|
|
|
|
- 第 4 列:该波每只怪出现间隔(秒)
|
|
|
|
|
- **Monster1 行 id = waveIndex**(不是第 4 列)
|
|
|
|
|
|
|
|
|
|
### Level.boss
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
waveIndex|monsterId,...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
例:`5|1101,10|1101` → 第 5、10 波排队出 Boss 1101(在小怪段之后)。
|
|
|
|
|
|
|
|
|
|
### Level.bosspro(万分比)
|
|
|
|
|
|
|
|
|
|
- Boss **链未激活**时,按 `boss` 顺序在对应波次掷一次概率。
|
|
|
|
|
- **命中** → 该波出 Boss,链激活;**之后所有 boss 配置波必出**,不再掷概率。
|
|
|
|
|
- **未命中** → 该波不出 Boss,等下一条 boss 配置再掷。
|
|
|
|
|
|
|
|
|
|
实现:[`LevelWaveParser.resolveBossSpawns`](../../assets/script/battle/config/LevelWaveParser.ts)
|
|
|
|
|
掷点须无符号再 `% 10000`:JS 有符号 `%` 会得到负数,`负数 < bosspro` 恒成立,链会提前激活。
|
|
|
|
|
|
|
|
|
|
### Monster / Monster1
|
|
|
|
|
|
|
|
|
|
- **Monster**:`icon` 为预制体名;`speed` 寻路;`hp` 参与血量公式;`type`:1 小怪 / 2 BOSS / 3 召唤。
|
|
|
|
|
- **Monster1**:行 = `waveIndex`;列 = `Rank.diffcult` / Level 难度组。
|
|
|
|
|
- **血量计算时机**:**出怪当下**套公式 `floor(Monster.hp × hp_multiple × port_hp × Monster1[波次] × 战力修正 × weapon_para)`,不以开局缓存为准。波次怪、营召唤、`atk_type=8`(含哮天犬)、万神幡 Boss 同一套;Monster1 行取**当前波** `waveIndex`。
|
|
|
|
|
|
|
|
|
|
#### 表现(全部怪物:不设皮肤、不区分阵营)
|
|
|
|
|
|
|
|
|
|
Spine 资源仅 `default` 皮;双口同一种表现,**禁止 setSkin / 阵营皮后缀**。
|
|
|
|
|
|
|
|
|
|
| type | 预制体 | 皮肤 | 行进 | 死亡 |
|
|
|
|
|
|------|--------|------|------|------|
|
|
|
|
|
| 1 五行小怪 / 3 同 icon 召唤 | `prefab/monster/{icon}`(如 `小怪金`) | **不 setSkin** | `{系}小怪`(如 `金小怪`) | `{系}小怪2`(如 `金小怪2`) |
|
|
|
|
|
| 2 Boss | `prefab/monster/{icon}`(如 `BOSS金`) | **不 setSkin** | `走路`(或资源轨) | 按 Boss 资源轨(缺则 error) |
|
|
|
|
|
|
|
|
|
|
`Monster.icon` → 行进 / 死亡:`小怪金`→`金小怪`/`金小怪2`,木/水/火/土同理。映射失败或动画不存在 → `console.error`,禁止静默兜底。
|
|
|
|
|
|
|
|
|
|
实现:`MonsterDataManager.skipsBattleSpineSkin` 恒 true;`resolveMarchAnim` / `resolveDeathAnim` + `Monster.playWalk`;Presenter / Boss 预览 / 额外 Boss 召唤均不换皮。
|
|
|
|
|
|
|
|
|
|
#### 营字强化出怪(Hero.id=5)
|
|
|
|
|
|
|
|
|
|
| 项 | 约定 |
|
|
|
|
|
|---|---|
|
|
|
|
|
| 触发 | 地图上每个激活的「营」独立计时、独立出怪 |
|
|
|
|
|
| 配表 | `Skill.atk_para`:`怪物id,数量`(如 `-1,1`);**间隔用 `Skill.cd`**(1~5 级 10/8/6/5/4 秒) |
|
|
|
|
|
| `-1` | 当前/最近一波**小怪段** monsterId;准备期/Boss/波间仍按该 id 出小怪 |
|
|
|
|
|
| 出生 | 「攻击」0.5s 草字头打开后,怪出现在**营格心上偏 30px**(光的位置)、0.5 倍(相对落地)缩放,0.3s 放到 **Monster.size×1.3**;血条挂怪根上随身体放大;停 1s 再抛物线跳出 |
|
|
|
|
|
| 落点 | 同行最近可达进路格(**出怪口格除外**);与口同行或没有横向落点时,用普通怪出怪口跳跃落点(进路第一格)。出怪口不能作为跳跃目标 |
|
|
|
|
|
| 逻辑口 | `UnitMetaComp.spawnPoint` 仍记己方营 port1 / 敌方营 port2(皮肤、阵营、复活) |
|
|
|
|
|
| 表现 | 营预制体播「攻击」(开→持有→1.17s 合上)后回「待机」;无口特效、无冒怪 |
|
|
|
|
|
| 皮肤 | `Monster.res[营等级-1]`;敌方阵营加后缀 `2`;预制体仍用 `icon` |
|
|
|
|
|
| 加成 | `atk_buff1/2`:type3 移速、type4 血量上限,均为 `×(1+万分比/10000)`(如 1000=+10%)。上限变化后**保持原血量比**(100/70 → 120/84);**出生时不播** `怪物加血` |
|
|
|
|
|
| 实现 | `YingCampSummonHost` + `WaveSpawner.spawnExtraEnemyAtCell` + `MovementSystem.assignPathVia`;营不挂普攻 Ability |
|
|
|
|
|
|
|
|
|
|
#### Skill.atk_type=8 召唤
|
|
|
|
|
|
|
|
|
|
`atk_para`:`怪物id,数量,间隔,出怪类型`(1=施法处出现后跳入进路,2=自身周围出现)。非法第 4 段 `warn`,不猜默认。
|
|
|
|
|
|
|
|
|
|
| 出怪类型 | 逻辑 | 表现 |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| 1 施法处跳入 | `spawnExtraEnemyAtCell`:施法格出生,横向最近进路汇入(不含出怪口);与口同行或没有则用普通怪出怪口跳跃落点。逻辑口仍记己方 port1 / 敌方 port2 | `召唤哮天犬` 播完后,在预兆点上偏 45px 出现,缩放 0→0.5,停 1s 再抛物线跳出,停顿+跳跃期间放到 1 倍 |
|
|
|
|
|
| 2 自身周围 | 施法者格及邻格生成,阵营与施法者一致 | `unitSpawned.spawnPort=0`,落在实体格,不出跳 |
|
|
|
|
|
|
|
|
|
|
杨戬主动(10176~10178)`10001,1,0,1` → 施法处出 **哮天犬**(Monster.id=10001)再跳入进路。实现:`SkillTableSummonExecutor`。血量按**召唤当时波次**与波次怪同一公式,不锁开局第 1 波。
|
|
|
|
|
|
|
|
|
|
## 3. 代码结构
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
LevelWaveParser ← 解析 + bosspro + 生成 WaveDef(双口)
|
|
|
|
|
ChapterLevelBuilder ← 组装 LevelDef,wallHp 默认 3
|
|
|
|
|
WaveSpawner ← port.playerId,口内 segment 队列;spawnExtraEnemy / spawnExtraEnemyAtCell
|
|
|
|
|
YingCampSummonHost ← 营强化:营格水平进路出怪
|
|
|
|
|
SkillTableSummonExecutor ← atk_type=8(营地 / 自身周围)
|
|
|
|
|
UIBattleGame ← 地图 + 拓扑 + Presenter + startCombat
|
|
|
|
|
UIPanelHome ← pickRandomBattleLevelId 进关
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 数据流
|
|
|
|
|
|
|
|
|
|
1. `UIPanelHome.click_btnStart` → 随机 Level
|
|
|
|
|
2. `GameController.enterBattle` → 预载 → `gg.battle.start`
|
|
|
|
|
3. `ChapterLevelBuilder.build` → `LevelWaveParser.toWaveDefs`
|
|
|
|
|
4. `UIBattleGame` → `loadMap` → `applyMapTopologyToSession(..., 'pvp')`
|
|
|
|
|
5. `CocosBattleMonsterPresenter.bind`;**本机玩家第一次把数字放到地图后**,或开局 **10 秒仍未放置**,`startCombat` → 双口出怪 + 寻路表现
|
|
|
|
|
|
|
|
|
|
## 4. 默认参数
|
|
|
|
|
|
|
|
|
|
- 双方基地 HP:**3**([`DEFAULT_BATTLE_WALL_HP`](../../assets/script/battle/config/ChapterLevelBuilder.ts))
|
|
|
|
|
- 中间波结波:`waveClearRequiresKillAll: false`(出怪队列耗尽即下一波)
|
|
|
|
|
- 最后一波:出完且场上**进攻本机**的怪清零 → `endBattle(true)` → `BattleRuntime.gameOver(true)` 弹 `BattleSuccess`
|
|
|
|
|
- 敌方营地 `wallHp` 归零 → 胜利;己方归零 → 复活或失败
|
|
|
|
|
- 到达攻击点:`ReachGoalBaseDamageSystem` 对 `ownerId` 槽 `-1` 并 despawn(默认 3 血)
|
|
|
|
|
|
|
|
|
|
## 5. 营地血条与胜负
|
|
|
|
|
|
|
|
|
|
- `ReachGoalBaseDamageSystem`(priority 55):`reachedGoal && hasAttackGoal` → `PlayerSlot.damageWall(1)` → `lifecycle.despawn`
|
|
|
|
|
- 血条预制体 `battleGame/prefab/营地血条`:挂在地图根上,对齐该图 `spawnPoint_N`(营地外观);**己方在营下、敌方在营上**,相对贴图 AABB 内收以免悬空;`血量` 下格数=当前血,宽=`60/maxHp`,扣血渐隐回收;血格与根节点同样打 Sorting2D,避免被血量底盖成空槽
|
|
|
|
|
- spawnPoint_1 → 玩家 0(己营),spawnPoint_2 → 玩家 1(敌营)
|
|
|
|
|
- 胜负:`BattleRuntime` 听 `WallDamaged` / `BattleEnd`;己方口怪不挡住最后一波清场胜利
|
|
|
|
|
|
|
|
|
|
## 6. 战斗预载(进关)
|
|
|
|
|
|
|
|
|
|
[`ResPkgBuilder`](../../assets/script/game/ResPkgBuilder.ts) 仅预载:
|
|
|
|
|
|
|
|
|
|
- 地图 `mapLayerN`、出怪口
|
|
|
|
|
- 本关怪物 + 状态特效 + 怪物血条
|
|
|
|
|
- 营地血条 `营地血条`
|
|
|
|
|
- **battleHero 包全部英雄体**(`prefab/hero/`)+ **全部攻效**(`prefab/` 根目录;后续迁入 `prefab/skill/` 时在 [`BattleHeroResCollector`](../../assets/script/battle/res/BattleHeroResCollector.ts) 维护)
|
|
|
|
|
|
|
|
|
|
已移除老项目:芭蕉扇/围栏/机制包、局内飘字与受击特效 bulk 预载、按章节裁剪 HERO_ACTOR_RES。
|
|
|
|
|
|
|
|
|
|
## 7. 验收(出怪阶段)
|
|
|
|
|
|
|
|
|
|
- [ ] 首页多次进关按当天 Const 31~37 地图池随机 Level.id
|
|
|
|
|
- [ ] 玩家/AI 同时从出怪口 1/2 按表出怪
|
|
|
|
|
- [ ] Boss 概率链符合 bosspro 规则
|
|
|
|
|
- [ ] 同波小怪出完后排队出 Boss
|
|
|
|
|
- [ ] 怪物沿本阵营路径移动
|
|
|
|
|
- [ ] 皮肤来自 Monster.icon
|
|
|
|
|
- [ ] 双方营地血条贴营地:己方在下、敌方在上;进关即 3 格绿条、格宽=20
|
|
|
|
|
- [ ] 对方怪到达营地后消失且该营 -1 格(渐隐)
|
|
|
|
|
- [ ] 己方营地扣完失败(或复活);敌方扣完胜利
|
|
|
|
|
- [ ] 敌方波次怪全部击杀完胜利(己方口怪不挡住)
|