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

312 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file MissionMonComp.ts
* @description 怪物Monster波次刷新管理组件逻辑层
*
* 职责:
* 1. 管理每一波怪物的生成计划:根据 RogueConfig 生成怪物。
* 2. 自动推进波次在准备阶段结束时PhasePrepareEnd启动分批释放。
* 3. 分批刷怪:每波固定 3 批,每 10 秒释放一批,批内按 MON_SPAWN_INTERVAL 逐个刷出。
*
* 关键设计:
* - 所有怪物统一从右侧 X=400 出生点逐个刷出MON_SPAWN_INTERVAL 节奏控制)。
* - 实际阵型与推进由 MonMoveComp 在战斗中向左移动自然形成,不再使用固定网格点。
* - 槽位索引3行×4列仅用于 monGrid 寻路与 SCastSystem 索敌定位。
* - 上一波残留怪在波次结束/开始时统一清理。
*/
import { _decorator, v3, Vec3 } from "cc";
import { mLogger } from "../common/Logger";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
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, MAX_MONSTERS, BATCH_COUNT, BATCH_INTERVAL } from "./RogueConfig";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { MonMoveComp } from "../hero/MonMoveComp";
const { ccclass, property } = _decorator;
@ccclass('MissionMonCompComp')
@ecs.register('MissionMonComp', false)
export class MissionMonCompComp extends CCComp {
// ======================== 常量 ========================
/** 怪物出生掉落高度 */
private static readonly MON_DROP_HEIGHT = 0;
/** 怪物统一从右侧 X=400 出生点逐个刷出,随后向左推进 */
private static readonly MON_SPAWN_X = 400;
/** 逐个刷怪间隔(秒):保证怪物排成纵队,避免堆叠 */
private static readonly MON_SPAWN_INTERVAL = 0.3;
/**
* 怪物占位点:所有槽位统一以右侧出生点 (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
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 1: Col1-Mid
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 2: Col1-Bot
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 3: Col2-Top
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 4: Col2-Mid
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 5: Col2-Bot
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 6: Col3-Top
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 7: Col3-Mid
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 8: Col3-Bot
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 9: Col4-Top
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 10: Col4-Mid
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 11: Col4-Bot
];
// ======================== 编辑器属性 ========================
@property({ tooltip: "是否启用调试日志" })
private debugMode: boolean = false;
// ======================== 运行时状态 ========================
/** 全局生成顺序计数器(用于渲染层级排序) */
private globalSpawnOrder: number = 0;
/** 当前波数 */
private currentWave: number = 0;
/** 当前波的目标怪物总数 */
private waveTargetCount: number = 0;
/** 当前波已生成的怪物数量 */
private waveSpawnedCount: number = 0;
/** 等待生成的怪物队列按批次分组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;
// ======================== 生命周期 ========================
onLoad() {
this.on(GameEvent.FightReady, this.fight_ready, this);
this.on("PhasePrepareEnd", this.onPhasePrepareEnd, this);
this.on("TimeUpAdvanceWave", this.onTimeUpAdvanceWave, this);
}
protected update(dt: number): void {
// 统计待刷出的怪物总数(未释放的批次 + 正在释放的队列)
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) {
this.spawnTimer += dt;
if (this.spawnTimer >= MissionMonCompComp.MON_SPAWN_INTERVAL) {
this.spawnTimer = 0;
const monData = this.spawnQueue.shift()!;
const targetPosIndex = this.waveSpawnedCount % MissionMonCompComp.MON_POSITIONS.length;
this.addMonsterAtGrid(targetPosIndex, monData, this.currentWave);
this.waveSpawnedCount++;
}
}
}
start() {}
private setupWaveData(monsters: GeneratedMonster[]) {
// 按批次分组
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}`);
oops.message.dispatchEvent(GameEvent.NewWave, {
wave: this.currentWave,
total: this.waveTargetCount,
bossWave: hasBoss,
});
}
/**
* 战斗准备:重置所有运行时状态并开始第一波。
*/
fight_ready() {
smc.vmdata.mission_data.mon_num = 0;
smc.mission.stop_spawn_mon = false;
this.globalSpawnOrder = 0;
this.currentWave = 1;
this.waveTargetCount = 0;
this.waveSpawnedCount = 0;
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);
if (TestModeConfig.enable) {
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] 测试模式已开启");
}
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System");
}
// ======================== 波次管理 ========================
/**
* 开始下一波:波数 +1 并预生成数据
*/
private onTimeUpAdvanceWave() {
this.currentWave += 1;
smc.vmdata.mission_data.level = this.currentWave;
const monsters = spawningEngine.generateWave(this.currentWave);
this.setupWaveData(monsters);
}
private onPhasePrepareEnd() {
this.resetSlotSpawnData();
// 准备结束阶段:启动分批释放,
// 第一批立即转入 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;
}
// ======================== 槽位管理 ========================
/**
* 清理上一波残留怪物,并重置生成计数与逐个刷怪队列
*/
private resetSlotSpawnData() {
ecs.query(ecs.allOf(HeroAttrsComp)).forEach(e => {
const attrs = e.get(HeroAttrsComp);
if (attrs && attrs.fac === FacSet.MON && !attrs.is_dead) {
e.destroy();
}
});
this.waveSpawnedCount = 0;
// 同步丢弃上一波未释放的队列,避免与新一波混合
this.spawnQueue = [];
this.spawnTimer = 0;
this.isReleasing = false;
this.currentBatch = 0;
this.batchTimer = 0;
}
// ======================== 怪物生成 ========================
/**
* 在指定位置索引处生成一个怪物
*/
private addMonsterAtGrid(
posIndex: number,
monData: GeneratedMonster,
monLv: number = 1
) {
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;
// 技能套装注入:通过 _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;
}
// 应用新引擎计算好的最终属性
const model = mon.get(HeroAttrsComp);
if (model) {
model.ap = monData.ap;
model.hp_max = monData.hp;
model.hp = model.hp_max;
}
}
/** ECS 组件移除时触发 */
reset() {}
}