refactor(rogue): 重构肉鸽刷怪系统,新增动态难度与分批刷怪

1. 重构RogueConfig:替换旧强化系统为动态难度调节器,调整波次配置为20波通关,新增技能池与阵营适配
2. 优化刷怪逻辑:实现每波3批10秒间隔的分批刷怪,调整怪物上限为18只
3. 新增驻场技能支持:添加resolveFieldByLv处理等级化驻场技能,扩展FieldSkillHelper支持多阵营统计
4. 调整关卡判定:将最大波次从15改为20,新增英雄死亡计数与难度动态调节
5. 优化代码结构:移除废弃字段,统一技能注入方式,修复槽位复用逻辑
This commit is contained in:
panFD
2026-08-04 20:53:59 +08:00
parent 1b1244c33c
commit 988c669bba
5 changed files with 572 additions and 362 deletions

View File

@@ -4,7 +4,8 @@
*
* 职责:
* 1. 管理每一波怪物的生成计划:根据 RogueConfig 生成怪物。
* 2. 自动推进波次在准备阶段结束时PhasePrepareEnd把怪物转入逐个刷出队列
* 2. 自动推进波次在准备阶段结束时PhasePrepareEnd启动分批释放
* 3. 分批刷怪:每波固定 3 批,每 10 秒释放一批,批内按 MON_SPAWN_INTERVAL 逐个刷出。
*
* 关键设计:
* - 所有怪物统一从右侧 X=400 出生点逐个刷出MON_SPAWN_INTERVAL 节奏控制)。
@@ -21,7 +22,7 @@ import { Monster } from "../hero/Mon";
import { smc } from "../common/SingletonModuleComp";
import { GameEvent } from "../common/config/GameEvent";
import { BoxSet, FacSet } from "../common/config/GameSet";
import { spawningEngine, GeneratedMonster, TestModeConfig } from "./RogueConfig";
import { spawningEngine, GeneratedMonster, TestModeConfig, MAX_MONSTERS, BATCH_COUNT, BATCH_INTERVAL } from "./RogueConfig";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { MonMoveComp } from "../hero/MonMoveComp";
@@ -32,8 +33,6 @@ const { ccclass, property } = _decorator;
export class MissionMonCompComp extends CCComp {
// ======================== 常量 ========================
/** 怪物最多 12 个 */
private static readonly MAX_MONSTERS = 12;
/** 怪物出生掉落高度 */
private static readonly MON_DROP_HEIGHT = 0;
/** 怪物统一从右侧 X=400 出生点逐个刷出,随后向左推进 */
@@ -42,9 +41,10 @@ export class MissionMonCompComp extends CCComp {
private static readonly MON_SPAWN_INTERVAL = 0.3;
/**
* 12 个怪物占位点:所有槽位统一以右侧出生点 (X=400) 作为坐标,
* 怪物占位点:所有槽位统一以右侧出生点 (X=400) 作为坐标,
* 实际阵型由 MonMoveComp 在战斗中向左推进时自然拉开。
* 槽位索引仍保留 3 行 × 4 列结构,供 monGrid 寻路与 SCastSystem 索敌使用。
* 超出 12 个槽位的怪物(放松波)按 spawnIndex % 12 复用槽位。
*/
public static readonly MON_POSITIONS: Vec3[] = [
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 0: Col1-Top
@@ -65,7 +65,7 @@ export class MissionMonCompComp extends CCComp {
@property({ tooltip: "是否启用调试日志" })
private debugMode: boolean = false;
// ======================== 运行时状态 ========================
/** 全局生成顺序计数器(用于渲染层级排序) */
@@ -76,9 +76,15 @@ export class MissionMonCompComp extends CCComp {
private waveTargetCount: number = 0;
/** 当前波已生成的怪物数量 */
private waveSpawnedCount: number = 0;
/** 等待生成的怪物队列(波次总池 */
private pendingMonsters: GeneratedMonster[] = [];
/** 逐个刷怪队列:从 pendingMonsters 转入,按节奏在 update 中释放 */
/** 等待生成的怪物队列(按批次分组batch 0~2 */
private pendingBatches: GeneratedMonster[][] = [[], [], []];
/** 当前正在释放的批次索引 */
private currentBatch: number = 0;
/** 批次释放计时器(秒) */
private batchTimer: number = 0;
/** 是否正在分批释放中 */
private isReleasing: boolean = false;
/** 逐个刷怪队列:从当前批次转入,按节奏在 update 中释放 */
private spawnQueue: GeneratedMonster[] = [];
/** 逐个刷怪累计计时(秒) */
private spawnTimer: number = 0;
@@ -92,8 +98,21 @@ export class MissionMonCompComp extends CCComp {
}
protected update(dt: number): void {
// pending_mon_num 同时统计刷出的总池与正在释放的队列,确保 UI 与全灭判定一致
smc.vmdata.mission_data.pending_mon_num = this.pendingMonsters.length + this.spawnQueue.length;
// 统计刷出的怪物总数(未释放的批次 + 正在释放的队列)
let pendingCount = this.spawnQueue.length;
for (let i = this.currentBatch; i < BATCH_COUNT; i++) {
pendingCount += this.pendingBatches[i].length;
}
smc.vmdata.mission_data.pending_mon_num = pendingCount;
// 分批释放:按 BATCH_INTERVAL 节奏推进批次
if (this.isReleasing) {
this.batchTimer += dt;
if (this.batchTimer >= BATCH_INTERVAL && this.currentBatch < BATCH_COUNT - 1) {
this.batchTimer = 0;
this.advanceBatch();
}
}
// 逐个刷怪:按 MON_SPAWN_INTERVAL 节奏从队列释放
if (this.spawnQueue.length > 0) {
@@ -101,7 +120,7 @@ export class MissionMonCompComp extends CCComp {
if (this.spawnTimer >= MissionMonCompComp.MON_SPAWN_INTERVAL) {
this.spawnTimer = 0;
const monData = this.spawnQueue.shift()!;
const targetPosIndex = this.waveSpawnedCount % MissionMonCompComp.MAX_MONSTERS;
const targetPosIndex = this.waveSpawnedCount % MissionMonCompComp.MON_POSITIONS.length;
this.addMonsterAtGrid(targetPosIndex, monData, this.currentWave);
this.waveSpawnedCount++;
}
@@ -111,10 +130,16 @@ export class MissionMonCompComp extends CCComp {
start() {}
private setupWaveData(monsters: GeneratedMonster[]) {
this.pendingMonsters = monsters.slice(0, MissionMonCompComp.MAX_MONSTERS);
smc.vmdata.mission_data.pending_mon_num = this.pendingMonsters.length;
this.waveTargetCount = this.pendingMonsters.length;
// 按批次分组
this.pendingBatches = [[], [], []];
for (const m of monsters) {
const batch = Math.min(m.batch, BATCH_COUNT - 1);
this.pendingBatches[batch].push(m);
}
this.waveTargetCount = monsters.length;
smc.vmdata.mission_data.pending_mon_num = this.waveTargetCount;
let hasBoss = monsters.some(m => m.isBoss);
mLogger.log(this.debugMode, 'MissionMonComp', `[MissionMonComp] 波次 ${this.currentWave} 生成怪物总数: ${this.waveTargetCount}`);
@@ -136,10 +161,13 @@ export class MissionMonCompComp extends CCComp {
this.currentWave = 1;
this.waveTargetCount = 0;
this.waveSpawnedCount = 0;
this.pendingMonsters = [];
this.pendingBatches = [[], [], []];
this.currentBatch = 0;
this.batchTimer = 0;
this.isReleasing = false;
this.spawnQueue = [];
this.spawnTimer = 0;
// 预生成第一波数据以获取数量和 Boss 信息
const monsters = spawningEngine.generateWave(this.currentWave);
this.setupWaveData(monsters);
@@ -159,7 +187,7 @@ export class MissionMonCompComp extends CCComp {
private onTimeUpAdvanceWave() {
this.currentWave += 1;
smc.vmdata.mission_data.level = this.currentWave;
const monsters = spawningEngine.generateWave(this.currentWave);
this.setupWaveData(monsters);
}
@@ -167,17 +195,53 @@ export class MissionMonCompComp extends CCComp {
private onPhasePrepareEnd() {
this.resetSlotSpawnData();
// 准备结束阶段:把本波待刷怪物转入逐个释放队列
// 实际生成时机由 update()MON_SPAWN_INTERVAL 节奏触发,
// 让怪物从 X=400 出生点排成纵队依次向左推进。
if (this.pendingMonsters.length > 0) {
const count = Math.min(this.pendingMonsters.length, MissionMonCompComp.MAX_MONSTERS);
for (let i = 0; i < count; i++) {
this.spawnQueue.push(this.pendingMonsters.shift()!);
}
// 让首个怪物在下一帧立即刷出,避免额外延迟
this.spawnTimer = MissionMonCompComp.MON_SPAWN_INTERVAL;
// 准备结束阶段:启动分批释放
// 第一批立即转入 spawnQueue后续批次由 update 按 BATCH_INTERVAL 推进。
this.startBatchRelease();
}
// ======================== 分批释放 ========================
/** 启动分批释放:立即释放第一批,启动批次计时器 */
private startBatchRelease() {
this.currentBatch = 0;
this.batchTimer = 0;
this.isReleasing = true;
this.releaseCurrentBatch();
}
/** 推进到下一批次 */
private advanceBatch() {
if (this.currentBatch >= BATCH_COUNT - 1) {
this.isReleasing = false;
return;
}
this.currentBatch++;
this.releaseCurrentBatch();
}
/** 将当前批次的怪物转入 spawnQueue等待逐个刷出 */
private releaseCurrentBatch() {
const batch = this.pendingBatches[this.currentBatch];
if (batch.length === 0) {
// 当前批次为空,尝试推进到下一批
if (this.currentBatch < BATCH_COUNT - 1) {
this.currentBatch++;
this.releaseCurrentBatch();
} else {
this.isReleasing = false;
}
return;
}
// 将批次怪物转入逐个刷怪队列
for (const m of batch) {
this.spawnQueue.push(m);
}
batch.length = 0;
// 让首个怪物在下一帧立即刷出,避免额外延迟
this.spawnTimer = MissionMonCompComp.MON_SPAWN_INTERVAL;
}
// ======================== 槽位管理 ========================
@@ -197,6 +261,9 @@ export class MissionMonCompComp extends CCComp {
// 同步丢弃上一波未释放的队列,避免与新一波混合
this.spawnQueue = [];
this.spawnTimer = 0;
this.isReleasing = false;
this.currentBatch = 0;
this.batchTimer = 0;
}
// ======================== 怪物生成 ========================
@@ -211,19 +278,20 @@ export class MissionMonCompComp extends CCComp {
) {
let mon = ecs.getEntity<Monster>(Monster);
let scale = -1;
const basePos = MissionMonCompComp.MON_POSITIONS[posIndex % MissionMonCompComp.MON_POSITIONS.length];
const spawnX = basePos.x;
const landingY = basePos.y + (monData.isBoss ? 6 : 0);
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
if (monData.testSkills) {
(mon as any)._testSkills = monData.testSkills;
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
// 技能套装注入:通过 _testSkills 通道传递给 Mon.load()
if (monData.skills) {
(mon as any)._testSkills = monData.skills;
}
mon.load(spawnPos, scale, monData.uuid, monData.isBoss, landingY, monLv, posIndex);
const move = mon.get(MonMoveComp);
if (move) {
move.spawnOrder = this.globalSpawnOrder;