feat(monster): 调整怪物AI并重构刷怪系统
重构怪物移动逻辑,移除移动追击相关代码,改为定点攻击模式,无敌人时原地待机,仅根据攻击范围切换攻击状态与朝向。 重构波次刷怪逻辑,删除分段刷怪阶段处理,改为波次准备结束后批量生成所有怪物。 将原6路刷怪改为3行网格布局,调整怪物出生点的X轴起点与间距。 限制单波最大怪物数量为12,简化刷怪分配逻辑为按生成顺序自动排列行列。 清理冗余的运行时状态变量与废弃函数,优化代码整体结构。 BREAKING CHANGES: 怪物攻击逻辑从移动追击改为定点攻击,移除了MonMoveComp中的moveEntity和resolveCombatRange函数;刷怪系统从6路改为3行网格布局,移除了分段刷怪功能与相关状态变量。
This commit is contained in:
@@ -51,14 +51,16 @@ const { ccclass, property } = _decorator;
|
||||
export class MissionMonCompComp extends CCComp {
|
||||
// ======================== 常量 ========================
|
||||
|
||||
/** 怪物最多 12 个 */
|
||||
private static readonly MAX_MONSTERS = 12;
|
||||
/** 怪物出生点起点 X */
|
||||
private static readonly MON_SPAWN_START_X = 460;
|
||||
/** 怪物出生的 X 间距 */
|
||||
private static readonly MON_SPAWN_GAP_X = 50;
|
||||
private static readonly MON_SPAWN_START_X = 60;
|
||||
/** 怪物出生的 X 间距 (列距) */
|
||||
private static readonly MON_SPAWN_GAP_X = 80;
|
||||
/** 怪物出生掉落高度 */
|
||||
private static readonly MON_DROP_HEIGHT = 0;
|
||||
/** 6路高度偏移(在三路的y轴范围内,实现 6路 进军) */
|
||||
private static readonly LANE_Y_OFFSETS = [100, 60, 20, -20, -60, -100];
|
||||
/** 3行高度偏移 (行距) */
|
||||
private static readonly ROW_Y_OFFSETS = [80, 0, -80];
|
||||
|
||||
// ======================== 编辑器属性 ========================
|
||||
|
||||
@@ -82,8 +84,6 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
// ======================== 运行时状态 ========================
|
||||
|
||||
/** 记录每条线当前排到的索引 */
|
||||
private laneIndices: number[] = [0, 0, 0, 0, 0, 0];
|
||||
/** 全局生成顺序计数器(用于渲染层级排序) */
|
||||
private globalSpawnOrder: number = 0;
|
||||
/** 插队刷怪处理计时器 */
|
||||
@@ -96,18 +96,6 @@ export class MissionMonCompComp extends CCComp {
|
||||
private waveSpawnedCount: number = 0;
|
||||
/** 等待生成的怪物队列(由新肉鸽引擎提供) */
|
||||
private pendingMonsters: GeneratedMonster[] = [];
|
||||
/** 增量刷怪计时器 */
|
||||
private spawnTimer: number = 0;
|
||||
/** 分段刷怪阶段 (1, 2, 3) */
|
||||
private currentSpawnPhase: number = 1;
|
||||
/** 下一阶段刷怪的延迟计时器 */
|
||||
private phaseDelayTimer: number = 0;
|
||||
/** 当前阶段目标生成的怪物总数 */
|
||||
private phaseTargetCount: number = 0;
|
||||
/** 当前阶段已生成的怪物数 */
|
||||
private phaseSpawnedCount: number = 0;
|
||||
/** 本波怪物总数量(分段计算基准,避免 shift 后长度变化) */
|
||||
private waveTotalForPhase: number = 0;
|
||||
|
||||
// ======================== 生命周期 ========================
|
||||
|
||||
@@ -133,43 +121,6 @@ export class MissionMonCompComp extends CCComp {
|
||||
if(!smc.mission.in_fight) return;
|
||||
|
||||
this.updateSpecialQueue(dt);
|
||||
|
||||
if(smc.mission.stop_spawn_mon) return;
|
||||
|
||||
// 逐步刷怪逻辑 (分 3 段刷出)
|
||||
if (this.pendingMonsters.length > 0) {
|
||||
if (this.phaseSpawnedCount >= this.phaseTargetCount && this.currentSpawnPhase < 3) {
|
||||
this.phaseDelayTimer -= dt;
|
||||
if (this.phaseDelayTimer <= 0) {
|
||||
this.currentSpawnPhase++;
|
||||
this.phaseSpawnedCount = 0;
|
||||
this.phaseDelayTimer = 3.0;
|
||||
const base = this.waveTotalForPhase;
|
||||
if (this.currentSpawnPhase === 2) {
|
||||
this.phaseTargetCount = Math.ceil(base / 3);
|
||||
} else {
|
||||
this.phaseTargetCount = base - this.phaseTargetCount * 2;
|
||||
if (this.phaseTargetCount <= 0) this.phaseTargetCount = this.pendingMonsters.length;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.spawnTimer += dt;
|
||||
if (this.spawnTimer > 0.2) {
|
||||
this.spawnTimer = 0;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
if (this.pendingMonsters.length === 0 || this.phaseSpawnedCount >= this.phaseTargetCount) break;
|
||||
const monData = this.pendingMonsters.shift()!;
|
||||
const lane = this.pickBalancedLane();
|
||||
console.log(`[MissionMonComp] [Phase ${this.currentSpawnPhase}] 准备生成怪物 UUID=${monData.uuid}, 剩余数量=${this.pendingMonsters.length}`);
|
||||
this.addMonsterAt(lane, this.laneIndices[lane], monData);
|
||||
this.laneIndices[lane]++;
|
||||
this.waveSpawnedCount++;
|
||||
this.phaseSpawnedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 事件处理 ========================
|
||||
@@ -195,15 +146,9 @@ export class MissionMonCompComp extends CCComp {
|
||||
}
|
||||
|
||||
private setupWaveData(monsters: GeneratedMonster[]) {
|
||||
this.pendingMonsters = monsters;
|
||||
this.pendingMonsters = monsters.slice(0, MissionMonCompComp.MAX_MONSTERS);
|
||||
smc.vmdata.mission_data.pending_mon_num = this.pendingMonsters.length;
|
||||
this.waveTargetCount = monsters.length;
|
||||
this.waveTotalForPhase = monsters.length;
|
||||
|
||||
this.currentSpawnPhase = 1;
|
||||
this.phaseSpawnedCount = 0;
|
||||
this.phaseTargetCount = Math.ceil(monsters.length / 3);
|
||||
this.phaseDelayTimer = 3.0;
|
||||
this.waveTargetCount = this.pendingMonsters.length;
|
||||
|
||||
let hasBoss = monsters.some(m => m.isBoss);
|
||||
|
||||
@@ -231,8 +176,6 @@ export class MissionMonCompComp extends CCComp {
|
||||
this.waveSpawnedCount = 0
|
||||
this.MonQueue = []
|
||||
this.pendingMonsters = []
|
||||
this.spawnTimer = 0
|
||||
this.laneIndices = [0, 0, 0, 0, 0, 0];
|
||||
|
||||
// 预生成第一波数据以获取数量和 Boss 信息
|
||||
const monsters = spawningEngine.generateWave(this.currentWave);
|
||||
@@ -243,25 +186,9 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
// ======================== 插队刷怪 ========================
|
||||
|
||||
/** 选取当前排数最少的路(均衡分配,数量相同时随机选一个) */
|
||||
private pickBalancedLane(): number {
|
||||
let min = this.laneIndices[0];
|
||||
let candidates = [0];
|
||||
for (let i = 1; i < 6; i++) {
|
||||
if (this.laneIndices[i] < min) {
|
||||
min = this.laneIndices[i];
|
||||
candidates = [i];
|
||||
} else if (this.laneIndices[i] === min) {
|
||||
candidates.push(i);
|
||||
}
|
||||
}
|
||||
return candidates[Math.floor(Math.random() * candidates.length)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理插队刷怪队列(每 0.15 秒尝试消费一个):
|
||||
* 1. 根据指定路或均衡分路。
|
||||
* 2. 找到后从队列中移除并生成怪物。
|
||||
* 1. 找到后从队列中移除并生成怪物。
|
||||
*/
|
||||
private updateSpecialQueue(dt: number) {
|
||||
if (this.MonQueue.length <= 0) return;
|
||||
@@ -274,7 +201,9 @@ export class MissionMonCompComp extends CCComp {
|
||||
const isBoss = MonList[MonType.MeleeBoss].includes(item.uuid) ||
|
||||
MonList[MonType.LongBoss].includes(item.uuid);
|
||||
|
||||
const lane = item.flyLane !== undefined && item.flyLane >= 0 && item.flyLane <= 5 ? item.flyLane : this.pickBalancedLane();
|
||||
const spawnIndex = this.waveSpawnedCount++;
|
||||
const row = spawnIndex % 3;
|
||||
const col = Math.floor(spawnIndex / 3);
|
||||
|
||||
// 构造一个模拟的 GeneratedMonster 数据传递给 addMonsterAt
|
||||
const base = HeroInfo[item.uuid];
|
||||
@@ -287,8 +216,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
isBoss: isBoss,
|
||||
spawnIndex: 0
|
||||
};
|
||||
this.addMonsterAt(lane, this.laneIndices[lane], monData, item.level);
|
||||
this.laneIndices[lane]++;
|
||||
this.addMonsterAtGrid(row, col, monData, item.level);
|
||||
}
|
||||
|
||||
// ======================== 波次管理 ========================
|
||||
@@ -309,6 +237,21 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
private onPhasePrepareEnd() {
|
||||
this.resetSlotSpawnData(this.currentWave);
|
||||
|
||||
// 准备结束阶段,立即刷出本波所有怪物
|
||||
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 row = this.waveSpawnedCount % 3;
|
||||
const col = Math.floor(this.waveSpawnedCount / 3);
|
||||
console.log(`[MissionMonComp] [PhasePrepareEnd] 准备生成怪物 UUID=${monData.uuid}, 当前已生成数量=${this.waveSpawnedCount}`);
|
||||
this.addMonsterAtGrid(row, col, monData);
|
||||
this.waveSpawnedCount++;
|
||||
}
|
||||
// 生成完毕后清空 pendingMonsters
|
||||
this.pendingMonsters = [];
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 槽位管理 ========================
|
||||
@@ -316,7 +259,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
/**
|
||||
* 重新分配本波所有怪物状态:
|
||||
* 1. 清理上一波残留怪物。
|
||||
* 2. pendingMonsters 已在 onTimeUpAdvanceWave / fight_ready 中准备好,只需重置 laneIndices 即可。
|
||||
* 2. pendingMonsters 已在 onTimeUpAdvanceWave / fight_ready 中准备好。
|
||||
*
|
||||
* @param wave 当前波数
|
||||
*/
|
||||
@@ -330,7 +273,6 @@ export class MissionMonCompComp extends CCComp {
|
||||
});
|
||||
|
||||
// 2. 重置排号索引
|
||||
this.laneIndices = [0, 0, 0, 0, 0, 0];
|
||||
this.waveSpawnedCount = 0;
|
||||
}
|
||||
|
||||
@@ -339,14 +281,14 @@ export class MissionMonCompComp extends CCComp {
|
||||
/**
|
||||
* 在指定层级、指定索引处生成一个怪物:
|
||||
*
|
||||
* @param laneIndex 三路索引 (0 上, 1 中, 2 下) - 已扩展为6路
|
||||
* @param monIndex 该路级的第几个怪 (0, 1, 2...)
|
||||
* @param row 行 (0, 1, 2)
|
||||
* @param col 列 (0, 1, 2, 3)
|
||||
* @param monData 新引擎生成的怪物数据 (含 uuid, hp, ap, affixes 等)
|
||||
* @param monLv 怪物等级 (仅对旧有的 level 参数做兼容,实际属性由 monData 决定)
|
||||
*/
|
||||
private addMonsterAt(
|
||||
laneIndex: number,
|
||||
monIndex: number,
|
||||
private addMonsterAtGrid(
|
||||
row: number,
|
||||
col: number,
|
||||
monData: GeneratedMonster,
|
||||
monLv: number = 1
|
||||
) {
|
||||
@@ -354,13 +296,13 @@ export class MissionMonCompComp extends CCComp {
|
||||
let scale = -1;
|
||||
|
||||
// 计算坐标
|
||||
const spawnX = MissionMonCompComp.MON_SPAWN_START_X + monIndex * MissionMonCompComp.MON_SPAWN_GAP_X;
|
||||
const spawnX = MissionMonCompComp.MON_SPAWN_START_X + col * MissionMonCompComp.MON_SPAWN_GAP_X;
|
||||
const randomY = Math.random() * 20 - 10; // -10 到 10 的随机Y轴偏移
|
||||
const landingY = BoxSet.GAME_LINE + MissionMonCompComp.LANE_Y_OFFSETS[laneIndex] + randomY + (monData.isBoss ? 6 : 0);
|
||||
const landingY = BoxSet.GAME_LINE + MissionMonCompComp.ROW_Y_OFFSETS[row] + randomY + (monData.isBoss ? 6 : 0);
|
||||
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
|
||||
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
|
||||
|
||||
mon.load(spawnPos, scale, monData.uuid, monData.isBoss, landingY, monLv, laneIndex);
|
||||
mon.load(spawnPos, scale, monData.uuid, monData.isBoss, landingY, monLv, row);
|
||||
|
||||
// 设置渲染排序
|
||||
const move = mon.get(MonMoveComp);
|
||||
|
||||
Reference in New Issue
Block a user