From 9a1d517aa9bff69c838cc2f0304d676083ff5b91 Mon Sep 17 00:00:00 2001 From: panw Date: Tue, 7 Apr 2026 16:36:49 +0800 Subject: [PATCH] =?UTF-8?q?refactor(game):=20=E9=87=8D=E6=9E=84=E6=80=AA?= =?UTF-8?q?=E7=89=A9=E6=B3=A2=E6=AC=A1=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4=E6=8E=92=E9=98=9F=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 IWaveSlot 中的 monCount 字段,改为固定 6 个槽位 - Boss 现在固定占用 2 个槽位且只能放置在 1、3、5 号位 - 每波开始时立即生成所有怪物,不再支持死亡后排队刷怪 - 简化 MissionMonComp 中的槽位管理逻辑,移除 slotSpawnQueues 等复杂结构 --- assets/script/game/map/MissionMonComp.ts | 266 ++++++++--------------- assets/script/game/map/RogueConfig.ts | 44 ++-- 2 files changed, 110 insertions(+), 200 deletions(-) diff --git a/assets/script/game/map/MissionMonComp.ts b/assets/script/game/map/MissionMonComp.ts index f7867b8d..f6bcb86b 100644 --- a/assets/script/game/map/MissionMonComp.ts +++ b/assets/script/game/map/MissionMonComp.ts @@ -31,16 +31,9 @@ export class MissionMonCompComp extends CCComp { level: number, }> = []; - private slotSpawnQueues: Array> = []; - private slotOccupiedEids: Array = []; - private slotRangeTypes: Array = []; - private slotSizes: Array = []; // 记录每个槽位原本被配置为多大尺寸的怪,用于后续校验 + private static readonly MAX_SLOTS = 6; + private slotOccupiedEids: Array = 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; } } diff --git a/assets/script/game/map/RogueConfig.ts b/assets/script/game/map/RogueConfig.ts index 561126ba..46c5f39a 100644 --- a/assets/script/game/map/RogueConfig.ts +++ b/assets/script/game/map/RogueConfig.ts @@ -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 } ]