feat(地图): 新增波次怪物占位配置系统

- 引入 WaveSlotConfig 和 DefaultWaveSlot 配置,支持每波怪物类型和数量自定义
- 替换硬编码的槽位数量,根据配置动态计算槽位总数和类型分布
- 重构怪物分配逻辑,依据怪物类型匹配配置槽位类型
This commit is contained in:
panw
2026-04-03 16:34:29 +08:00
parent 9fd893c692
commit eb106c1b60
2 changed files with 77 additions and 40 deletions

View File

@@ -8,7 +8,7 @@ import { HeroInfo, HType } from "../common/config/heroSet";
import { smc } from "../common/SingletonModuleComp"; import { smc } from "../common/SingletonModuleComp";
import { GameEvent } from "../common/config/GameEvent"; import { GameEvent } from "../common/config/GameEvent";
import {BoxSet } from "../common/config/GameSet"; import {BoxSet } from "../common/config/GameSet";
import { BossList, MonList, MonType, SpawnPowerBias, StageBossGrow, StageGrow, UpType } from "./RogueConfig"; import { BossList, MonList, MonType, SpawnPowerBias, StageBossGrow, StageGrow, UpType, WaveSlotConfig, DefaultWaveSlot, IWaveSlot } from "./RogueConfig";
import { HeroAttrsComp } from "../hero/HeroAttrsComp"; import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { MoveComp } from "../hero/MoveComp"; import { MoveComp } from "../hero/MoveComp";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@@ -18,8 +18,6 @@ const { ccclass, property } = _decorator;
@ecs.register('MissionMonComp', false) @ecs.register('MissionMonComp', false)
export class MissionMonCompComp extends CCComp { export class MissionMonCompComp extends CCComp {
private static readonly BOSS_RENDER_PRIORITY = 1000000; private static readonly BOSS_RENDER_PRIORITY = 1000000;
private static readonly MON_SLOT_COUNT = 6;
private static readonly MON_FRONT_SLOT_COUNT = 3;
private static readonly MON_SLOT_START_X = 30; private static readonly MON_SLOT_START_X = 30;
private static readonly MON_SLOT_X_INTERVAL = 60; private static readonly MON_SLOT_X_INTERVAL = 60;
private static readonly MON_DROP_HEIGHT = 280; private static readonly MON_DROP_HEIGHT = 280;
@@ -48,7 +46,7 @@ export class MissionMonCompComp extends CCComp {
monLv: number, monLv: number,
}>> = []; }>> = [];
private slotOccupiedEids: Array<number | null> = []; private slotOccupiedEids: Array<number | null> = [];
private slotRangeTypes: Array<HType.Melee | HType.Long> = []; private slotRangeTypes: Array<number> = [];
/** 全局生成顺序计数器,用于层级管理(预留) */ /** 全局生成顺序计数器,用于层级管理(预留) */
private globalSpawnOrder: number = 0; private globalSpawnOrder: number = 0;
/** 插队刷怪处理计时器 */ /** 插队刷怪处理计时器 */
@@ -61,7 +59,7 @@ export class MissionMonCompComp extends CCComp {
onLoad(){ onLoad(){
this.on(GameEvent.FightReady,this.fight_ready,this) this.on(GameEvent.FightReady,this.fight_ready,this)
this.on("SpawnSpecialMonster", this.onSpawnSpecialMonster, this); this.on("SpawnSpecialMonster", this.onSpawnSpecialMonster, this);
this.resetSlotSpawnData() this.resetSlotSpawnData(1)
} }
/** /**
@@ -93,7 +91,6 @@ export class MissionMonCompComp extends CCComp {
this.waveSpawnedCount = 0 this.waveSpawnedCount = 0
this.bossSpawnedInWave = false this.bossSpawnedInWave = false
this.MonQueue = [] this.MonQueue = []
this.resetSlotSpawnData()
this.startNextWave() this.startNextWave()
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System"); mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System");
} }
@@ -150,7 +147,7 @@ export class MissionMonCompComp extends CCComp {
this.waveSpawnedCount = 0; this.waveSpawnedCount = 0;
this.bossSpawnedInWave = false; this.bossSpawnedInWave = false;
this.waveSpawnTimer = this.waveSpawnCd; this.waveSpawnTimer = this.waveSpawnCd;
this.confirmWaveSlotTypes(); this.resetSlotSpawnData(this.currentWave);
this.primeWaveInitialBurst(); this.primeWaveInitialBurst();
oops.message.dispatchEvent(GameEvent.NewWave, { oops.message.dispatchEvent(GameEvent.NewWave, {
wave: this.currentWave, wave: this.currentWave,
@@ -216,7 +213,7 @@ export class MissionMonCompComp extends CCComp {
private primeWaveInitialBurst() { private primeWaveInitialBurst() {
const remain = this.waveTargetCount - this.waveSpawnedCount; const remain = this.waveTargetCount - this.waveSpawnedCount;
if (remain <= 0) return; if (remain <= 0) return;
const burstCount = Math.min(MissionMonCompComp.MON_SLOT_COUNT, remain); const burstCount = Math.min(this.slotSpawnQueues.length, remain);
for (let i = 0; i < burstCount; i++) { for (let i = 0; i < burstCount; i++) {
const uuid = this.getRandomNormalMonsterUuid(); const uuid = this.getRandomNormalMonsterUuid();
const upType = this.getRandomUpType(); const upType = this.getRandomUpType();
@@ -225,23 +222,32 @@ export class MissionMonCompComp extends CCComp {
this.waveSpawnedCount += burstCount; this.waveSpawnedCount += burstCount;
} }
private resetSlotSpawnData() { private resetSlotSpawnData(wave: number = 1) {
const config: IWaveSlot[] = WaveSlotConfig[wave] || DefaultWaveSlot;
let totalSlots = 0;
for (const slot of config) {
totalSlots += slot.count;
}
this.slotSpawnQueues = Array.from( this.slotSpawnQueues = Array.from(
{ length: MissionMonCompComp.MON_SLOT_COUNT }, { length: totalSlots },
() => [] () => []
); );
this.slotOccupiedEids = Array.from( this.slotOccupiedEids = Array.from(
{ length: MissionMonCompComp.MON_SLOT_COUNT }, { length: totalSlots },
() => null () => null
); );
this.confirmWaveSlotTypes(); this.confirmWaveSlotTypes(config);
} }
private confirmWaveSlotTypes() { private confirmWaveSlotTypes(config: IWaveSlot[]) {
this.slotRangeTypes = Array.from( this.slotRangeTypes = [];
{ length: MissionMonCompComp.MON_SLOT_COUNT }, for (const slot of config) {
(_, index) => index < MissionMonCompComp.MON_FRONT_SLOT_COUNT ? HType.Melee : HType.Long for (let i = 0; i < slot.count; i++) {
); this.slotRangeTypes.push(slot.type);
}
}
} }
private hasPendingSlotQueue() { private hasPendingSlotQueue() {
@@ -279,44 +285,40 @@ export class MissionMonCompComp extends CCComp {
return occupied + this.slotSpawnQueues[slotIndex].length; return occupied + this.slotSpawnQueues[slotIndex].length;
} }
private resolveMonsterSlotRange(uuid: number): HType.Melee | HType.Long { private resolveMonType(uuid: number): number {
const type = HeroInfo[uuid]?.type; for (const key in MonList) {
if (type === HType.Melee) return HType.Melee; const list = MonList[key as unknown as number] as number[];
return HType.Long; if (list && list.includes(uuid)) {
} return Number(key);
}
private resolveSlotPriorityIndexes(uuid: number): number[] {
if (this.resolveMonsterSlotRange(uuid) === HType.Melee) {
return [0, 1, 2, 3, 4, 5];
} }
return [5, 4, 3, 2, 1, 0]; return MonType.Melee;
} }
private pickAssignSlotIndex(uuid: number): number { private pickAssignSlotIndex(uuid: number): number {
const expectedRange = this.resolveMonsterSlotRange(uuid); const expectedType = this.resolveMonType(uuid);
const slotPriority = this.resolveSlotPriorityIndexes(uuid);
let bestLoad = Number.MAX_SAFE_INTEGER; let bestLoad = Number.MAX_SAFE_INTEGER;
let bestIndex = -1; let bestIndex = -1;
for (let i = 0; i < slotPriority.length; i++) {
const index = slotPriority[i]; for (let i = 0; i < this.slotRangeTypes.length; i++) {
if (this.slotRangeTypes[index] !== expectedRange) continue; if (this.slotRangeTypes[i] !== expectedType) continue;
const load = this.getSlotQueueLoad(index); const load = this.getSlotQueueLoad(i);
if (load < bestLoad) { if (load < bestLoad) {
bestLoad = load; bestLoad = load;
bestIndex = index; bestIndex = i;
} }
} }
if (bestIndex >= 0) return bestIndex; if (bestIndex >= 0) return bestIndex;
for (let i = 0; i < slotPriority.length; i++) {
const index = slotPriority[i]; for (let i = 0; i < this.slotRangeTypes.length; i++) {
const load = this.getSlotQueueLoad(index); const load = this.getSlotQueueLoad(i);
if (load < bestLoad) { if (load < bestLoad) {
bestLoad = load; bestLoad = load;
bestIndex = index; bestIndex = i;
} }
} }
if (bestIndex >= 0) return bestIndex; return Math.max(0, bestIndex);
return bestIndex;
} }
private enqueueMonsterRequest( private enqueueMonsterRequest(

View File

@@ -35,3 +35,38 @@ export const MonList = {
export const BossList = [6006,6104,6015] export const BossList = [6006,6104,6015]
export const SpawnPowerBias = 1 export const SpawnPowerBias = 1
export interface IWaveSlot {
type: number; // 对应 MonType
count: number;
}
// 每波怪物占位数量配置:数组顺序即为占位从左到右的排列顺序
export const WaveSlotConfig: { [wave: number]: IWaveSlot[] } = {
1: [
{ type: MonType.Melee, count: 2 },
{ type: MonType.Long, count: 2 }
],
2: [
{ type: MonType.Melee, count: 2 },
{ type: MonType.Long, count: 3 },
{ type: MonType.Support, count: 1 }
],
3: [
{ type: MonType.Melee, count: 3 },
{ type: MonType.MeleeBoss, count: 1 },
{ 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 }
],
}
// 默认占位配置 (如果在 WaveSlotConfig 中找不到波次,则使用此配置)
export const DefaultWaveSlot: IWaveSlot[] = [
{ type: MonType.Melee, count: 3 },
{ type: MonType.Long, count: 3 }
]