/** * @file MissionMonComp.ts * @description 怪物(Monster)波次刷新管理组件(逻辑层) * * 职责: * 1. 管理每一波怪物的生成计划:根据 RogueConfig 生成怪物。 * 2. 自动推进波次:在准备阶段结束时(PhasePrepareEnd)统一刷出怪物。 * * 关键设计: * - 采用 12 个硬编码的网格位置点 (MON_POSITIONS,3行x4列) * - 每次生成最多 12 个怪物,固定在位置点。 * - 上一波残留怪在波次结束/开始时统一清理。 */ 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 } 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 { // ======================== 常量 ======================== /** 怪物最多 12 个 */ private static readonly MAX_MONSTERS = 12; /** 怪物出生掉落高度 */ private static readonly MON_DROP_HEIGHT = 0; /** 硬编码的 12 个怪物占位点 (3行4列) */ public static readonly MON_POSITIONS: Vec3[] = [ // 第 1 列 (X=60) v3(0, BoxSet.GAME_LINE + 100, 0), // index 0: Top v3(0, BoxSet.GAME_LINE, 0), // index 1: Mid v3(0, BoxSet.GAME_LINE - 100, 0), // index 2: Bot // 第 2 列 (X=140) v3(90, BoxSet.GAME_LINE + 100, 0), // index 3: Top v3(90, BoxSet.GAME_LINE, 0), // index 4: Mid v3(90, BoxSet.GAME_LINE - 100, 0), // index 5: Bot // 第 3 列 (X=220) v3(180, BoxSet.GAME_LINE + 100, 0), // index 6: Top v3(180, BoxSet.GAME_LINE, 0), // index 7: Mid v3(180, BoxSet.GAME_LINE - 100, 0), // index 8: Bot // 第 4 列 (X=300) v3(270, BoxSet.GAME_LINE + 100, 0), // index 9: Top v3(270, BoxSet.GAME_LINE, 0), // index 10: Mid v3(270, BoxSet.GAME_LINE - 100, 0), // index 11: Bot ]; // ======================== 编辑器属性 ======================== @property({ tooltip: "是否启用调试日志" }) private debugMode: boolean = false; // ======================== 运行时状态 ======================== /** 全局生成顺序计数器(用于渲染层级排序) */ private globalSpawnOrder: number = 0; /** 当前波数 */ private currentWave: number = 0; /** 当前波的目标怪物总数 */ private waveTargetCount: number = 0; /** 当前波已生成的怪物数量 */ private waveSpawnedCount: number = 0; /** 等待生成的怪物队列 */ private pendingMonsters: GeneratedMonster[] = []; // ======================== 生命周期 ======================== 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 { smc.vmdata.mission_data.pending_mon_num = this.pendingMonsters.length; } 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; 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.pendingMonsters = []; // 预生成第一波数据以获取数量和 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(); // 准备结束阶段,立即刷出本波所有怪物 if (this.pendingMonsters.length > 0) { let count = Math.min(this.pendingMonsters.length, MissionMonCompComp.MAX_MONSTERS); for (let i = 0; i < count; i++) { const monData = this.pendingMonsters.shift()!; const targetPosIndex = this.waveSpawnedCount % MissionMonCompComp.MAX_MONSTERS; this.addMonsterAtGrid(targetPosIndex, monData, this.currentWave); this.waveSpawnedCount++; } this.pendingMonsters = []; } } // ======================== 槽位管理 ======================== /** * 清理上一波残留怪物,并重置生成计数 */ 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; } // ======================== 怪物生成 ======================== /** * 在指定位置索引处生成一个怪物 */ private addMonsterAtGrid( posIndex: number, monData: GeneratedMonster, monLv: number = 1 ) { let mon = ecs.getEntity(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; } 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() {} }