/** * @file MissionMonComp.ts * @description 怪物(Monster)登场管理组件(逻辑层) * * 职责: * 1. 预生成战斗怪物:FightReady 时通过 RogueSpawningEngine.generateBossCombo 随机一组 Boss 组合,并立即登场展示(供玩家侦查后再选英雄)。 * 2. 战斗开始:PhasePrepareEnd 时恢复怪物行动(in_fight 前怪物仅原地待命,不会推进/索敌)。 * * 关键设计: * - 5 只怪在 X∈[100,320] 内按登场序号均匀铺开,Boss/小怪同时登场,玩家看清阵容后三选一。 * - 实际推进由统一 MoveComp 在战斗中向敌人移动自然形成,不再使用固定网格点。 * - 槽位索引仅用于 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 } from "../common/config/GameSet"; import { spawningEngine, GeneratedMonster, TestModeConfig, BOSS_COUNT, MINION_COUNT } from "./RogueConfig"; import { HeroAttrsComp } from "../hero/HeroAttrsComp"; import { MoveComp } from "../hero/MoveComp"; const { ccclass, property } = _decorator; @ccclass('MissionMonCompComp') @ecs.register('MissionMonComp', false) export class MissionMonCompComp extends CCComp { // ======================== 常量 ======================== /** 怪物出生掉落高度 */ private static readonly MON_DROP_HEIGHT = 0; /** 怪物登场 X 范围下限(靠近英雄侧) */ private static readonly SPAWN_X_MIN = 100; /** 怪物登场 X 范围上限(靠近右侧) */ private static readonly SPAWN_X_MAX = 320; /** 槽位基准 Y(3行×4列结构仅供 monGrid 寻路与 SCastSystem 索敌定位) */ public static readonly MON_POSITIONS: Vec3[] = [ v3(0, BoxSet.GAME_LINE, 0), // index 0: Col1-Top v3(0, BoxSet.GAME_LINE, 0), // index 1: Col1-Mid v3(0, BoxSet.GAME_LINE, 0), // index 2: Col1-Bot v3(0, BoxSet.GAME_LINE, 0), // index 3: Col2-Top v3(0, BoxSet.GAME_LINE, 0), // index 4: Col2-Mid v3(0, BoxSet.GAME_LINE, 0), // index 5: Col2-Bot v3(0, BoxSet.GAME_LINE, 0), // index 6: Col3-Top v3(0, BoxSet.GAME_LINE, 0), // index 7: Col3-Mid v3(0, BoxSet.GAME_LINE, 0), // index 8: Col3-Bot v3(0, BoxSet.GAME_LINE, 0), // index 9: Col4-Top v3(0, BoxSet.GAME_LINE, 0), // index 10: Col4-Mid v3(0, BoxSet.GAME_LINE, 0), // index 11: Col4-Bot ]; // ======================== 编辑器属性 ======================== @property({ tooltip: "是否启用调试日志" }) private debugMode: boolean = false; // ======================== 运行时状态 ======================== /** 全局生成顺序计数器(用于渲染层级排序) */ private globalSpawnOrder: number = 0; /** 战斗场次(本局恒为 1,保留供 HUD/事件协议) */ private currentWave: number = 1; /** 本场战斗的怪物总数 */ private waveTargetCount: number = 0; /** 已登场的怪物数量 */ private waveSpawnedCount: number = 0; /** 待登场队列(FightReady 时整组进入,PhasePrepareEnd 时一次全量刷出) */ private spawnQueue: GeneratedMonster[] = []; // ======================== 生命周期 ======================== onLoad() { this.on(GameEvent.FightReady, this.fight_ready, this); } protected update(dt: number): void { // 待登场统计(战斗结束检测与 HUD 进度消费) smc.vmdata.mission_data.pending_mon_num = this.spawnQueue.length; } start() { } /** * 战斗准备:重置运行时状态并预生成本场 Boss 组合。 */ 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.spawnQueue = []; // 随机一组 Boss 组合(最终 Boss 战强度) const monsters = spawningEngine.generateBossCombo(); this.spawnQueue = monsters; this.waveTargetCount = monsters.length; smc.vmdata.mission_data.pending_mon_num = this.waveTargetCount; // 怪物先行登场:玩家侦查 Boss 组合后再做英雄三选一(in_fight 前怪物原地待命,不推进不索敌) const squad = this.spawnQueue; this.spawnQueue = []; smc.vmdata.mission_data.pending_mon_num = 0; for (const monData of squad) { this.addMonsterAtGrid(this.waveSpawnedCount, monData, this.currentWave); this.waveSpawnedCount++; } if (TestModeConfig.enable) { mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] 测试模式已开启"); } mLogger.log(this.debugMode, 'MissionMonComp', `[MissionMonComp] Boss 组合生成完成,怪物总数: ${this.waveTargetCount}`); oops.message.dispatchEvent(GameEvent.NewWave, { wave: this.currentWave, total: this.waveTargetCount, bossWave: monsters.some(m => m.isBoss), }); } // ======================== 怪物生成 ======================== /** * 在指定位置索引处生成一个怪物 * 5 只怪在 X∈[100,320] 内按登场序号均匀铺开,Boss/小怪同时登场直接开打 */ 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 = MissionMonCompComp.SPAWN_X_MIN + (MissionMonCompComp.SPAWN_X_MAX - MissionMonCompComp.SPAWN_X_MIN) * (posIndex / Math.max(1, BOSS_COUNT + MINION_COUNT - 1)); const landingY = basePos.y; 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); // oops.message 全局事件总线:Boss 登场(震屏/音效由表现层消费) if (monData.isBoss) { oops.message.dispatchEvent(GameEvent.BossSpawn, { pos: spawnPos.clone() }); } const move = mon.get(MoveComp); 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() { } }