# 方案-出怪逻辑实现 > 状态:出怪 + 到达扣营地血 + 营地血条 + 胜负已落地。审阅基线见 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 格(渐隐) - [ ] 己方营地扣完失败(或复活);敌方扣完胜利 - [ ] 敌方波次怪全部击杀完胜利(己方口怪不挡住)