1. 近战AI新增警戒线机制,无威胁时退回编队 2. 替换多回合波次为单场Boss战,简化刷怪与回合逻辑 3. 重构招募系统为开局免费三选一流程 4. 调整英雄与怪物基础属性平衡 5. 精简HUD与配置文件冗余代码
178 lines
7.3 KiB
TypeScript
178 lines
7.3 KiB
TypeScript
/**
|
||
* @file MissionMonComp.ts
|
||
* @description 怪物(Monster)登场管理组件(逻辑层)
|
||
*
|
||
* 职责:
|
||
* 1. 预生成战斗怪物:FightReady 时通过 RogueSpawningEngine.generateBossCombo 随机一组 Boss 组合,并立即登场展示(供玩家侦查后再选英雄)。
|
||
* 2. 战斗开始:PhasePrepareEnd 时恢复怪物行动(in_fight 前怪物仅原地待命,不会推进/索敌)。
|
||
*
|
||
* 关键设计:
|
||
* - 5 只怪在 X∈[100,320] 内按登场序号均匀铺开,Boss/小怪同时登场,玩家看清阵容后三选一。
|
||
* - 实际阵型与推进由 MonMoveComp 在战斗中向左移动自然形成,不再使用固定网格点。
|
||
* - 槽位索引仅用于 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 { 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 范围下限(靠近英雄侧) */
|
||
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>(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 + (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);
|
||
|
||
// oops.message 全局事件总线:Boss 登场(震屏/音效由表现层消费)
|
||
if (monData.isBoss) {
|
||
oops.message.dispatchEvent(GameEvent.BossSpawn, { pos: spawnPos.clone() });
|
||
}
|
||
|
||
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() { }
|
||
}
|