feat(刷怪): 支持大型怪物占用多个槽位
- 在 IWaveSlot 配置中增加 slotsPerMon 字段,用于指定每个怪物占用的槽位数量 - 修改 pickAssignSlotIndex 方法以寻找连续且类型匹配的空闲槽位 - 调整 enqueueMonsterRequest 和 addMonsterBySlot 方法以处理多槽位怪物 - 更新波次配置,为 MeleeBoss 和 LongBoss 设置 slotsPerMon: 2 - 大型怪物生成时会居中放置在占用的多个槽位上
This commit is contained in:
@@ -44,9 +44,11 @@ export class MissionMonCompComp extends CCComp {
|
||||
isBoss: boolean,
|
||||
upType: UpType,
|
||||
monLv: number,
|
||||
slotsPerMon: number,
|
||||
}>> = [];
|
||||
private slotOccupiedEids: Array<number | null> = [];
|
||||
private slotRangeTypes: Array<number> = [];
|
||||
private slotSizes: Array<number> = []; // 记录每个槽位原本被配置为多大尺寸的怪,用于后续校验
|
||||
/** 全局生成顺序计数器,用于层级管理(预留) */
|
||||
private globalSpawnOrder: number = 0;
|
||||
/** 插队刷怪处理计时器 */
|
||||
@@ -112,13 +114,15 @@ export class MissionMonCompComp extends CCComp {
|
||||
private updateSpecialQueue(dt: number) {
|
||||
if (this.MonQueue.length <= 0) return;
|
||||
this.queueTimer += dt;
|
||||
// 轻微节流,避免同帧内突发大量插队导致瞬间堆怪
|
||||
if (this.queueTimer < 0.15) return;
|
||||
this.queueTimer = 0;
|
||||
const item = this.MonQueue.shift();
|
||||
if (!item) return;
|
||||
const upType = this.getRandomUpType();
|
||||
this.enqueueMonsterRequest(item.uuid, BossList.includes(item.uuid), upType, Math.max(1, Number(item.level ?? 1)), true);
|
||||
const isBoss = BossList.includes(item.uuid);
|
||||
// 简单推断:如果是 boss 默认给 2 格(你也可以从配置里反查或者加专门的英雄表配置)
|
||||
const slotsPerMon = isBoss ? 2 : 1;
|
||||
this.enqueueMonsterRequest(item.uuid, isBoss, upType, Math.max(1, Number(item.level ?? 1)), slotsPerMon, true);
|
||||
}
|
||||
|
||||
private updateWaveSpawn(dt: number) {
|
||||
@@ -129,14 +133,14 @@ export class MissionMonCompComp extends CCComp {
|
||||
if (this.isBossWave() && !this.bossSpawnedInWave) {
|
||||
const bossUuid = this.getRandomBossUuid();
|
||||
const bossUpType = this.getRandomUpType();
|
||||
this.enqueueMonsterRequest(bossUuid, true, bossUpType);
|
||||
this.enqueueMonsterRequest(bossUuid, true, bossUpType, 1, 2);
|
||||
this.bossSpawnedInWave = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const uuid = this.getRandomNormalMonsterUuid();
|
||||
const upType = this.getRandomUpType();
|
||||
this.enqueueMonsterRequest(uuid, false, upType);
|
||||
this.enqueueMonsterRequest(uuid, false, upType, 1, 1);
|
||||
this.waveSpawnedCount += 1;
|
||||
}
|
||||
|
||||
@@ -217,7 +221,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
for (let i = 0; i < burstCount; i++) {
|
||||
const uuid = this.getRandomNormalMonsterUuid();
|
||||
const upType = this.getRandomUpType();
|
||||
this.enqueueMonsterRequest(uuid, false, upType);
|
||||
this.enqueueMonsterRequest(uuid, false, upType, 1, 1);
|
||||
}
|
||||
this.waveSpawnedCount += burstCount;
|
||||
}
|
||||
@@ -227,7 +231,8 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
let totalSlots = 0;
|
||||
for (const slot of config) {
|
||||
totalSlots += slot.count;
|
||||
const slotsPerMon = slot.slotsPerMon || 1;
|
||||
totalSlots += slot.count * slotsPerMon;
|
||||
}
|
||||
|
||||
this.slotSpawnQueues = Array.from(
|
||||
@@ -243,9 +248,14 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
private confirmWaveSlotTypes(config: IWaveSlot[]) {
|
||||
this.slotRangeTypes = [];
|
||||
this.slotSizes = [];
|
||||
for (const slot of config) {
|
||||
const slotsPerMon = slot.slotsPerMon || 1;
|
||||
for (let i = 0; i < slot.count; i++) {
|
||||
this.slotRangeTypes.push(slot.type);
|
||||
for (let s = 0; s < slotsPerMon; s++) {
|
||||
this.slotRangeTypes.push(slot.type);
|
||||
this.slotSizes.push(slotsPerMon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,24 +305,41 @@ export class MissionMonCompComp extends CCComp {
|
||||
return MonType.Melee;
|
||||
}
|
||||
|
||||
private pickAssignSlotIndex(uuid: number): number {
|
||||
private pickAssignSlotIndex(uuid: number, slotsPerMon: number): number {
|
||||
const expectedType = this.resolveMonType(uuid);
|
||||
let bestLoad = Number.MAX_SAFE_INTEGER;
|
||||
let bestIndex = -1;
|
||||
|
||||
for (let i = 0; i < this.slotRangeTypes.length; i++) {
|
||||
if (this.slotRangeTypes[i] !== expectedType) continue;
|
||||
const load = this.getSlotQueueLoad(i);
|
||||
// 尝试找到一个连续的、且类型匹配并且 slotSizes 符合的空位
|
||||
for (let i = 0; i <= this.slotRangeTypes.length - slotsPerMon; i++) {
|
||||
let valid = true;
|
||||
let load = 0;
|
||||
for (let j = 0; j < slotsPerMon; j++) {
|
||||
if (this.slotRangeTypes[i + j] !== expectedType || this.slotSizes[i + j] !== slotsPerMon) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
load += this.getSlotQueueLoad(i + j);
|
||||
}
|
||||
if (!valid) continue;
|
||||
|
||||
if (load < bestLoad) {
|
||||
bestLoad = load;
|
||||
bestIndex = i;
|
||||
}
|
||||
// 步进跨过这个怪的槽位,避免重叠判断
|
||||
i += slotsPerMon - 1;
|
||||
}
|
||||
|
||||
if (bestIndex >= 0) return bestIndex;
|
||||
|
||||
for (let i = 0; i < this.slotRangeTypes.length; i++) {
|
||||
const load = this.getSlotQueueLoad(i);
|
||||
// 降级寻找任意能容纳大小的槽位组合
|
||||
bestLoad = Number.MAX_SAFE_INTEGER;
|
||||
for (let i = 0; i <= this.slotRangeTypes.length - slotsPerMon; i++) {
|
||||
let load = 0;
|
||||
for (let j = 0; j < slotsPerMon; j++) {
|
||||
load += this.getSlotQueueLoad(i + j);
|
||||
}
|
||||
if (load < bestLoad) {
|
||||
bestLoad = load;
|
||||
bestIndex = i;
|
||||
@@ -326,10 +353,16 @@ export class MissionMonCompComp extends CCComp {
|
||||
isBoss: boolean,
|
||||
upType: UpType,
|
||||
monLv: number = 1,
|
||||
slotsPerMon: number = 1,
|
||||
priority: boolean = false,
|
||||
) {
|
||||
const slotIndex = this.pickAssignSlotIndex(uuid);
|
||||
const request = { uuid, isBoss, upType, monLv };
|
||||
const slotIndex = this.pickAssignSlotIndex(uuid, slotsPerMon);
|
||||
const request = { uuid, isBoss, upType, monLv, slotsPerMon };
|
||||
|
||||
// 如果怪占用多个槽位,它应该存在于它占据的所有槽位的队列中(这样别的怪才会认为这里很挤)
|
||||
// 但为了避免在 trySpawnFromSlotQueues 中被多次生成,我们只把实际的 request 放进它的起始槽位
|
||||
// 其他被占用的槽位可以放一个占位符,或者通过其它方式处理
|
||||
// 为了简便,我们只将它推入首个槽位,但排队检查的时候只要其中一个满了就算占用
|
||||
if (priority) {
|
||||
this.slotSpawnQueues[slotIndex].unshift(request);
|
||||
return;
|
||||
@@ -340,10 +373,26 @@ export class MissionMonCompComp extends CCComp {
|
||||
private trySpawnFromSlotQueues() {
|
||||
if (smc.mission.stop_spawn_mon) return;
|
||||
for (let i = 0; i < this.slotSpawnQueues.length; i++) {
|
||||
if (this.slotOccupiedEids[i]) continue;
|
||||
const request = this.slotSpawnQueues[i].shift();
|
||||
if (!request) continue;
|
||||
this.addMonsterBySlot(i, request.uuid, request.isBoss, request.upType, request.monLv);
|
||||
const queue = this.slotSpawnQueues[i];
|
||||
if (queue.length === 0) continue;
|
||||
|
||||
const request = queue[0];
|
||||
const slotsPerMon = request.slotsPerMon;
|
||||
|
||||
// 检查这个怪需要的所有的槽位是否都空闲
|
||||
let canSpawn = true;
|
||||
for (let j = 0; j < slotsPerMon; j++) {
|
||||
if (i + j >= this.slotOccupiedEids.length || this.slotOccupiedEids[i + j]) {
|
||||
canSpawn = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canSpawn) continue;
|
||||
|
||||
// 可以生成了,弹出请求
|
||||
queue.shift();
|
||||
this.addMonsterBySlot(i, request.uuid, request.isBoss, request.upType, request.monLv, slotsPerMon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,16 +402,25 @@ export class MissionMonCompComp extends CCComp {
|
||||
isBoss: boolean = false,
|
||||
upType: UpType = UpType.AP1_HP1,
|
||||
monLv: number = 1,
|
||||
slotsPerMon: number = 1,
|
||||
) {
|
||||
let mon = ecs.getEntity<Monster>(Monster);
|
||||
let scale = -1;
|
||||
const spawnX = MissionMonCompComp.MON_SLOT_START_X + slotIndex * MissionMonCompComp.MON_SLOT_X_INTERVAL;
|
||||
// 如果占用了多个格子,出生坐标居中处理
|
||||
const centerXOffset = (slotsPerMon - 1) * MissionMonCompComp.MON_SLOT_X_INTERVAL / 2;
|
||||
const spawnX = MissionMonCompComp.MON_SLOT_START_X + slotIndex * MissionMonCompComp.MON_SLOT_X_INTERVAL + centerXOffset;
|
||||
const landingY = BoxSet.GAME_LINE + (isBoss ? 6 : 0);
|
||||
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
|
||||
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
|
||||
|
||||
mon.load(spawnPos, scale, uuid, isBoss, landingY, monLv);
|
||||
this.slotOccupiedEids[slotIndex] = mon.eid;
|
||||
|
||||
// 将它占用的所有格子都标记为这个 eid
|
||||
for (let j = 0; j < slotsPerMon; j++) {
|
||||
if (slotIndex + j < this.slotOccupiedEids.length) {
|
||||
this.slotOccupiedEids[slotIndex + j] = mon.eid;
|
||||
}
|
||||
}
|
||||
const move = mon.get(MoveComp);
|
||||
if (move) {
|
||||
move.spawnOrder = isBoss
|
||||
|
||||
@@ -37,7 +37,8 @@ export const SpawnPowerBias = 1
|
||||
|
||||
export interface IWaveSlot {
|
||||
type: number; // 对应 MonType
|
||||
count: number;
|
||||
count: number; // 怪物数量
|
||||
slotsPerMon?: number; // 每个怪占用几个位置,默认 1
|
||||
}
|
||||
|
||||
// 每波怪物占位数量配置:数组顺序即为占位从左到右的排列顺序
|
||||
@@ -53,14 +54,14 @@ export const WaveSlotConfig: { [wave: number]: IWaveSlot[] } = {
|
||||
],
|
||||
3: [
|
||||
{ type: MonType.Melee, count: 3 },
|
||||
{ type: MonType.MeleeBoss, count: 1 },
|
||||
{ type: MonType.MeleeBoss, count: 1, slotsPerMon: 2 },
|
||||
{ type: MonType.Long, count: 2 }
|
||||
],
|
||||
4: [
|
||||
{ type: MonType.Melee, count: 2 },
|
||||
{ type: MonType.Long, count: 2 },
|
||||
{ type: MonType.Support, count: 1 },
|
||||
{ type: MonType.LongBoss, count: 1 }
|
||||
{ type: MonType.LongBoss, count: 1, slotsPerMon: 2 }
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user