1. 更新怪物配置注释,修正近战/远程怪物的描述与分类 2. 移除MissionComp中过时的自适应刷怪逻辑 3. 重构MissionMonComp:删除插队刷怪队列、简化波次流程、统一怪物生成逻辑 4. 移除冗余日志与注释,优化代码可读性 5. 调整波次准备阶段的怪物生成时机与数据处理
217 lines
7.9 KiB
TypeScript
217 lines
7.9 KiB
TypeScript
/**
|
||
* @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>(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() {}
|
||
}
|