Files
pixelheros/assets/script/game/map/MissionMonComp.ts
pan 6c2f1defa9 refactor(monster): 重构波次怪物管理逻辑,优化配置与流程
1.  更新怪物配置注释,修正近战/远程怪物的描述与分类
2.  移除MissionComp中过时的自适应刷怪逻辑
3.  重构MissionMonComp:删除插队刷怪队列、简化波次流程、统一怪物生成逻辑
4.  移除冗余日志与注释,优化代码可读性
5.  调整波次准备阶段的怪物生成时机与数据处理
2026-06-23 17:05:07 +08:00

217 lines
7.9 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统一刷出怪物。
*
* 关键设计:
* - 采用 12 个硬编码的网格位置点 (MON_POSITIONS3行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>(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() {}
}