refactor(game): 重构怪物波次生成逻辑,移除排队机制
- 移除 IWaveSlot 中的 monCount 字段,改为固定 6 个槽位 - Boss 现在固定占用 2 个槽位且只能放置在 1、3、5 号位 - 每波开始时立即生成所有怪物,不再支持死亡后排队刷怪 - 简化 MissionMonComp 中的槽位管理逻辑,移除 slotSpawnQueues 等复杂结构
This commit is contained in:
@@ -31,16 +31,9 @@ export class MissionMonCompComp extends CCComp {
|
||||
level: number,
|
||||
}> = [];
|
||||
|
||||
private slotSpawnQueues: Array<Array<{
|
||||
uuid: number,
|
||||
isBoss: boolean,
|
||||
upType: UpType,
|
||||
monLv: number,
|
||||
slotsPerMon: number,
|
||||
}>> = [];
|
||||
private slotOccupiedEids: Array<number | null> = [];
|
||||
private slotRangeTypes: Array<number> = [];
|
||||
private slotSizes: Array<number> = []; // 记录每个槽位原本被配置为多大尺寸的怪,用于后续校验
|
||||
private static readonly MAX_SLOTS = 6;
|
||||
private slotOccupiedEids: Array<number | null> = Array(6).fill(null);
|
||||
|
||||
/** 全局生成顺序计数器,用于层级管理(预留) */
|
||||
private globalSpawnOrder: number = 0;
|
||||
/** 插队刷怪处理计时器 */
|
||||
@@ -91,7 +84,6 @@ export class MissionMonCompComp extends CCComp {
|
||||
if(smc.mission.stop_mon_action) return;
|
||||
if(!smc.mission.in_fight) return;
|
||||
this.refreshSlotOccupancy();
|
||||
this.trySpawnFromSlotQueues();
|
||||
this.tryAdvanceWave();
|
||||
if(!smc.mission.in_fight) return;
|
||||
if(smc.mission.stop_spawn_mon) return;
|
||||
@@ -102,14 +94,35 @@ export class MissionMonCompComp extends CCComp {
|
||||
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();
|
||||
|
||||
const item = this.MonQueue[0];
|
||||
const isBoss = MonList[MonType.MeleeBoss].includes(item.uuid) || MonList[MonType.LongBoss].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);
|
||||
const slotsPerMon = isBoss ? 2 : 1;
|
||||
|
||||
let slotIndex = -1;
|
||||
if (slotsPerMon === 2) {
|
||||
// Boss 只能放在 0, 2, 4
|
||||
for (const idx of [0, 2, 4]) {
|
||||
if (!this.slotOccupiedEids[idx] && !this.slotOccupiedEids[idx + 1]) {
|
||||
slotIndex = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
|
||||
if (!this.slotOccupiedEids[i]) {
|
||||
slotIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (slotIndex !== -1) {
|
||||
this.MonQueue.shift();
|
||||
this.queueTimer = 0;
|
||||
const upType = this.getRandomUpType();
|
||||
this.addMonsterBySlot(slotIndex, item.uuid, isBoss, upType, Math.max(1, Number(item.level ?? 1)), slotsPerMon);
|
||||
}
|
||||
}
|
||||
|
||||
private startNextWave() {
|
||||
@@ -133,7 +146,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
}
|
||||
|
||||
private tryAdvanceWave() {
|
||||
if (this.hasPendingSlotQueue()) return;
|
||||
if (this.MonQueue.length > 0) return;
|
||||
if (this.hasActiveSlotMonster()) return;
|
||||
if (smc.vmdata.mission_data.mon_num > 0) return;
|
||||
this.startNextWave();
|
||||
@@ -171,73 +184,80 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
private resetSlotSpawnData(wave: number = 1) {
|
||||
const config: IWaveSlot[] = WaveSlotConfig[wave] || DefaultWaveSlot;
|
||||
this.slotOccupiedEids = Array(MissionMonCompComp.MAX_SLOTS).fill(null);
|
||||
|
||||
let bosses: any[] = [];
|
||||
let normals: any[] = [];
|
||||
|
||||
let totalSlots = 0;
|
||||
let totalMonsters = 0;
|
||||
for (const slot of config) {
|
||||
const slotsPerMon = slot.slotsPerMon || 1;
|
||||
const monCount = slot.monCount || 1;
|
||||
totalSlots += slot.count * slotsPerMon;
|
||||
totalMonsters += slot.count * monCount;
|
||||
}
|
||||
|
||||
this.waveTargetCount = totalMonsters;
|
||||
this.waveSpawnedCount = 0;
|
||||
|
||||
this.slotSpawnQueues = Array.from(
|
||||
{ length: totalSlots },
|
||||
() => []
|
||||
);
|
||||
this.slotOccupiedEids = Array.from(
|
||||
{ length: totalSlots },
|
||||
() => null
|
||||
);
|
||||
this.slotRangeTypes = [];
|
||||
this.slotSizes = [];
|
||||
|
||||
let slotIndex = 0;
|
||||
for (const slot of config) {
|
||||
const slotsPerMon = slot.slotsPerMon || 1;
|
||||
const monCount = slot.monCount || 1;
|
||||
const isBoss = slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss;
|
||||
|
||||
for (let i = 0; i < slot.count; i++) {
|
||||
const currentSlotIndex = slotIndex;
|
||||
|
||||
// 设置槽位类型和大小
|
||||
for (let s = 0; s < slotsPerMon; s++) {
|
||||
this.slotRangeTypes.push(slot.type);
|
||||
this.slotSizes.push(slotsPerMon);
|
||||
slotIndex++;
|
||||
const uuid = this.getRandomUuidByType(slot.type);
|
||||
const upType = this.getRandomUpType();
|
||||
const req = { uuid, isBoss, upType, monLv: wave, slotsPerMon };
|
||||
if (isBoss || slotsPerMon === 2) {
|
||||
bosses.push(req);
|
||||
} else {
|
||||
normals.push(req);
|
||||
}
|
||||
|
||||
// 根据配置数量,直接在波次开始时把该坑位要刷的所有怪排入队列
|
||||
for (let m = 0; m < monCount; m++) {
|
||||
const uuid = this.getRandomUuidByType(slot.type);
|
||||
const upType = this.getRandomUpType();
|
||||
const request = { uuid, isBoss, upType, monLv: wave, slotsPerMon };
|
||||
this.slotSpawnQueues[currentSlotIndex].push(request);
|
||||
}
|
||||
}
|
||||
|
||||
this.waveTargetCount = bosses.length + normals.length;
|
||||
this.waveSpawnedCount = 0;
|
||||
|
||||
// Boss 只能放在 0, 2, 4 (即 1, 3, 5 号位)
|
||||
let bossAllowedIndices = [0, 2, 4];
|
||||
let assignedSlots = new Array(MissionMonCompComp.MAX_SLOTS).fill(null);
|
||||
|
||||
for (const boss of bosses) {
|
||||
let placed = false;
|
||||
for (const idx of bossAllowedIndices) {
|
||||
if (!assignedSlots[idx] && !assignedSlots[idx + 1]) {
|
||||
assignedSlots[idx] = boss;
|
||||
assignedSlots[idx + 1] = "occupied"; // 占位
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!placed) {
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] No slot for boss!");
|
||||
}
|
||||
}
|
||||
|
||||
for (const normal of normals) {
|
||||
let placed = false;
|
||||
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
|
||||
if (!assignedSlots[i]) {
|
||||
assignedSlots[i] = normal;
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!placed) {
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] No slot for normal monster!");
|
||||
}
|
||||
}
|
||||
|
||||
// 立即生成本波所有怪物
|
||||
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
|
||||
const req = assignedSlots[i];
|
||||
if (req && req !== "occupied") {
|
||||
this.addMonsterBySlot(i, req.uuid, req.isBoss, req.upType, req.monLv, req.slotsPerMon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private hasPendingSlotQueue() {
|
||||
for (let i = 0; i < this.slotSpawnQueues.length; i++) {
|
||||
if (this.slotSpawnQueues[i].length > 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private hasActiveSlotMonster() {
|
||||
for (let i = 0; i < this.slotOccupiedEids.length; i++) {
|
||||
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
|
||||
if (this.slotOccupiedEids[i]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private refreshSlotOccupancy() {
|
||||
for (let i = 0; i < this.slotOccupiedEids.length; i++) {
|
||||
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
|
||||
const eid = this.slotOccupiedEids[i];
|
||||
if (!eid) continue;
|
||||
const entity = ecs.getEntityByEid(eid);
|
||||
@@ -246,118 +266,12 @@ export class MissionMonCompComp extends CCComp {
|
||||
continue;
|
||||
}
|
||||
const attrs = entity.get(HeroAttrsComp);
|
||||
if (!attrs) {
|
||||
if (!attrs || attrs.hp <= 0) {
|
||||
this.slotOccupiedEids[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getSlotQueueLoad(slotIndex: number): number {
|
||||
const occupied = this.slotOccupiedEids[slotIndex] ? 1 : 0;
|
||||
return occupied + this.slotSpawnQueues[slotIndex].length;
|
||||
}
|
||||
|
||||
private resolveMonType(uuid: number): number {
|
||||
for (const key in MonList) {
|
||||
const list = MonList[key as unknown as number] as number[];
|
||||
if (list && list.includes(uuid)) {
|
||||
return Number(key);
|
||||
}
|
||||
}
|
||||
return MonType.Melee;
|
||||
}
|
||||
|
||||
private pickAssignSlotIndex(uuid: number, slotsPerMon: number): number {
|
||||
const expectedType = this.resolveMonType(uuid);
|
||||
let bestLoad = Number.MAX_SAFE_INTEGER;
|
||||
let bestIndex = -1;
|
||||
|
||||
// 尝试找到一个连续的、且类型匹配并且 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;
|
||||
|
||||
// 降级寻找任意能容纳大小的槽位组合
|
||||
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;
|
||||
}
|
||||
}
|
||||
return Math.max(0, bestIndex);
|
||||
}
|
||||
|
||||
private enqueueMonsterRequest(
|
||||
uuid: number,
|
||||
isBoss: boolean,
|
||||
upType: UpType,
|
||||
monLv: number = 1,
|
||||
slotsPerMon: number = 1,
|
||||
priority: boolean = false,
|
||||
) {
|
||||
const slotIndex = this.pickAssignSlotIndex(uuid, slotsPerMon);
|
||||
const request = { uuid, isBoss, upType, monLv, slotsPerMon };
|
||||
|
||||
// 如果怪占用多个槽位,它应该存在于它占据的所有槽位的队列中(这样别的怪才会认为这里很挤)
|
||||
// 但为了避免在 trySpawnFromSlotQueues 中被多次生成,我们只把实际的 request 放进它的起始槽位
|
||||
// 其他被占用的槽位可以放一个占位符,或者通过其它方式处理
|
||||
// 为了简便,我们只将它推入首个槽位,但排队检查的时候只要其中一个满了就算占用
|
||||
if (priority) {
|
||||
this.slotSpawnQueues[slotIndex].unshift(request);
|
||||
return;
|
||||
}
|
||||
this.slotSpawnQueues[slotIndex].push(request);
|
||||
}
|
||||
|
||||
private trySpawnFromSlotQueues() {
|
||||
if (smc.mission.stop_spawn_mon) return;
|
||||
for (let i = 0; i < this.slotSpawnQueues.length; i++) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private addMonsterBySlot(
|
||||
slotIndex: number,
|
||||
uuid: number = 1001,
|
||||
@@ -379,7 +293,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
// 将它占用的所有格子都标记为这个 eid
|
||||
for (let j = 0; j < slotsPerMon; j++) {
|
||||
if (slotIndex + j < this.slotOccupiedEids.length) {
|
||||
if (slotIndex + j < MissionMonCompComp.MAX_SLOTS) {
|
||||
this.slotOccupiedEids[slotIndex + j] = mon.eid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,49 +40,45 @@ export interface IWaveSlot {
|
||||
type: number; // 对应 MonType
|
||||
count: number; // 占位数量
|
||||
slotsPerMon?: number; // 每个怪占用几个位置,默认 1
|
||||
monCount: number; // 这个占位排队的怪物总数(每个坑位要刷多少个怪)
|
||||
}
|
||||
|
||||
// =========================================================================================
|
||||
// 【每波怪物占位与刷怪配置说明】
|
||||
// 1. 数组顺序:数组中的元素顺序即为战场上怪物从左到右占位的物理顺序。
|
||||
// 2. 字段说明:
|
||||
// 1. 字段说明:
|
||||
// - type: 怪物类型 (参考 MonType,如近战 0,远程 1,Boss 3 等)。
|
||||
// - count: 该类型在场上同时存在几个并排的占位坑。
|
||||
// - monCount: 每个占位坑需要刷出的怪物总数(即每个坑排队的怪物数量)。当场上该坑位的怪死亡后,排队的下一只才会生成。
|
||||
// - slotsPerMon: (可选) 单个怪物体积占用几个占位坑,默认为 1。如果是大型 Boss 可设为 2 或更多,它会跨占位降落。
|
||||
// - count: 该类型的怪在场上同时存在几个。
|
||||
// - slotsPerMon: (可选) 单个怪物体积占用几个占位坑,默认为 1。如果是大型 Boss 可设为 2,它会跨占位降落。
|
||||
//
|
||||
// 举例:
|
||||
// { type: MonType.Melee, count: 2, monCount: 3 }
|
||||
// 表示:在对应的位置开启 2 个近战占位坑,每个坑要排队刷出 3 只怪,总计该行配置会刷出 6 只近战怪。
|
||||
//
|
||||
// 【注意】:波次怪物的总数将由所有坑位的 count * monCount 自动累加计算得出。
|
||||
// 【注意】:
|
||||
// 全场固定 6 个槽位(索引 0-5)。
|
||||
// Boss 固定占用 2 个位置,且只能出现在 1、3、5 号位(对应索引 0, 2, 4)。
|
||||
// 每波怪物总槽位占用不能超过 6。不再支持排队刷怪。
|
||||
// =========================================================================================
|
||||
export const WaveSlotConfig: { [wave: number]: IWaveSlot[] } = {
|
||||
1: [
|
||||
{ type: MonType.Melee, count: 2, monCount: 3 },
|
||||
{ type: MonType.Long, count: 2, monCount: 2 }
|
||||
{ type: MonType.Melee, count: 3 },
|
||||
{ type: MonType.Long, count: 3 }
|
||||
],
|
||||
2: [
|
||||
{ type: MonType.Melee, count: 2, monCount: 4 },
|
||||
{ type: MonType.Long, count: 2, monCount: 3 },
|
||||
{ type: MonType.Support, count: 1, monCount: 2 }
|
||||
{ type: MonType.Melee, count: 2 },
|
||||
{ type: MonType.Long, count: 2 },
|
||||
{ type: MonType.Support, count: 2 }
|
||||
],
|
||||
3: [
|
||||
{ type: MonType.Melee, count: 1, monCount: 5 },
|
||||
{ type: MonType.MeleeBoss, count: 1, slotsPerMon: 2, monCount: 1 },
|
||||
{ type: MonType.Long, count: 2, monCount: 4 }
|
||||
{ type: MonType.Melee, count: 2 },
|
||||
{ type: MonType.MeleeBoss, count: 1, slotsPerMon: 2 },
|
||||
{ type: MonType.Long, count: 2 }
|
||||
],
|
||||
4: [
|
||||
{ type: MonType.Melee, count: 1, monCount: 5 },
|
||||
{ type: MonType.Long, count: 1, monCount: 5 },
|
||||
{ type: MonType.LongBoss, count: 1, slotsPerMon: 2, monCount: 1 }
|
||||
{ type: MonType.Melee, count: 2 },
|
||||
{ type: MonType.Long, count: 2 },
|
||||
{ type: MonType.LongBoss, count: 1, slotsPerMon: 2 }
|
||||
],
|
||||
}
|
||||
|
||||
// 默认占位配置 (如果在 WaveSlotConfig 中找不到波次,则使用此配置)
|
||||
export const DefaultWaveSlot: IWaveSlot[] = [
|
||||
{ type: MonType.Melee, count: 2, monCount: 3 },
|
||||
{ type: MonType.Long, count: 3, monCount: 3 }
|
||||
{ type: MonType.Melee, count: 3 },
|
||||
{ type: MonType.Long, count: 3 }
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user