refactor(怪物生成): 重构波次配置与生成逻辑
- 将波次配置从属性迁移至配置文件,增强可维护性 - 重构怪物生成逻辑,使用基于槽位的排队机制 - 移除旧的计时生成方式,改为配置驱动
This commit is contained in:
@@ -23,14 +23,6 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
private static readonly MON_DROP_HEIGHT = 280;
|
private static readonly MON_DROP_HEIGHT = 280;
|
||||||
@property({ tooltip: "是否启用调试日志" })
|
@property({ tooltip: "是否启用调试日志" })
|
||||||
private debugMode: boolean = false;
|
private debugMode: boolean = false;
|
||||||
@property({ tooltip: "每波基础普通怪数量" })
|
|
||||||
private baseMonstersPerWave: number = 5;
|
|
||||||
@property({ tooltip: "每波额外增加普通怪数量" })
|
|
||||||
private waveMonsterGrowth: number = 0;
|
|
||||||
@property({ tooltip: "多少波刷新一次 Boss" })
|
|
||||||
private bossWaveInterval: number = 5;
|
|
||||||
@property({ tooltip: "同一波内刷怪间隔(秒)" })
|
|
||||||
private waveSpawnCd: number = 0.35;
|
|
||||||
|
|
||||||
// 刷怪队列(用于插队生成:比如运营活动怪、技能召唤怪、剧情强制怪)
|
// 刷怪队列(用于插队生成:比如运营活动怪、技能召唤怪、剧情强制怪)
|
||||||
// 约定:队列里的怪会优先于常规刷新处理
|
// 约定:队列里的怪会优先于常规刷新处理
|
||||||
@@ -53,11 +45,9 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
private globalSpawnOrder: number = 0;
|
private globalSpawnOrder: number = 0;
|
||||||
/** 插队刷怪处理计时器 */
|
/** 插队刷怪处理计时器 */
|
||||||
private queueTimer: number = 0;
|
private queueTimer: number = 0;
|
||||||
private waveSpawnTimer: number = 0;
|
|
||||||
private currentWave: number = 0;
|
private currentWave: number = 0;
|
||||||
private waveTargetCount: number = 0;
|
private waveTargetCount: number = 0;
|
||||||
private waveSpawnedCount: number = 0;
|
private waveSpawnedCount: number = 0;
|
||||||
private bossSpawnedInWave: boolean = false;
|
|
||||||
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);
|
||||||
@@ -87,11 +77,9 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
smc.mission.stop_spawn_mon = false
|
smc.mission.stop_spawn_mon = false
|
||||||
this.globalSpawnOrder = 0
|
this.globalSpawnOrder = 0
|
||||||
this.queueTimer = 0
|
this.queueTimer = 0
|
||||||
this.waveSpawnTimer = 0
|
|
||||||
this.currentWave = 0
|
this.currentWave = 0
|
||||||
this.waveTargetCount = 0
|
this.waveTargetCount = 0
|
||||||
this.waveSpawnedCount = 0
|
this.waveSpawnedCount = 0
|
||||||
this.bossSpawnedInWave = false
|
|
||||||
this.MonQueue = []
|
this.MonQueue = []
|
||||||
this.startNextWave()
|
this.startNextWave()
|
||||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System");
|
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System");
|
||||||
@@ -108,7 +96,6 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
if(!smc.mission.in_fight) return;
|
if(!smc.mission.in_fight) return;
|
||||||
if(smc.mission.stop_spawn_mon) return;
|
if(smc.mission.stop_spawn_mon) return;
|
||||||
this.updateSpecialQueue(dt);
|
this.updateSpecialQueue(dt);
|
||||||
this.updateWaveSpawn(dt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateSpecialQueue(dt: number) {
|
private updateSpecialQueue(dt: number) {
|
||||||
@@ -125,82 +112,50 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
this.enqueueMonsterRequest(item.uuid, isBoss, upType, Math.max(1, Number(item.level ?? 1)), slotsPerMon, true);
|
this.enqueueMonsterRequest(item.uuid, isBoss, upType, Math.max(1, Number(item.level ?? 1)), slotsPerMon, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateWaveSpawn(dt: number) {
|
|
||||||
this.waveSpawnTimer += dt;
|
|
||||||
if (this.waveSpawnTimer < this.waveSpawnCd) return;
|
|
||||||
this.waveSpawnTimer = 0;
|
|
||||||
if (this.waveSpawnedCount >= this.waveTargetCount) {
|
|
||||||
if (this.isBossWave() && !this.bossSpawnedInWave) {
|
|
||||||
const bossUuid = this.getRandomBossUuid();
|
|
||||||
const bossUpType = this.getRandomUpType();
|
|
||||||
this.enqueueMonsterRequest(bossUuid, true, bossUpType, 1, 2);
|
|
||||||
this.bossSpawnedInWave = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const uuid = this.getRandomNormalMonsterUuid();
|
|
||||||
const upType = this.getRandomUpType();
|
|
||||||
this.enqueueMonsterRequest(uuid, false, upType, 1, 1);
|
|
||||||
this.waveSpawnedCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private startNextWave() {
|
private startNextWave() {
|
||||||
this.currentWave += 1;
|
this.currentWave += 1;
|
||||||
smc.vmdata.mission_data.level = this.currentWave;
|
smc.vmdata.mission_data.level = this.currentWave;
|
||||||
this.waveTargetCount = Math.max(1, this.baseMonstersPerWave + (this.currentWave - 1) * this.waveMonsterGrowth);
|
|
||||||
this.waveSpawnedCount = 0;
|
|
||||||
this.bossSpawnedInWave = false;
|
|
||||||
this.waveSpawnTimer = this.waveSpawnCd;
|
|
||||||
this.resetSlotSpawnData(this.currentWave);
|
this.resetSlotSpawnData(this.currentWave);
|
||||||
this.primeWaveInitialBurst();
|
|
||||||
|
let hasBoss = false;
|
||||||
|
const config = WaveSlotConfig[this.currentWave] || DefaultWaveSlot;
|
||||||
|
for (const slot of config) {
|
||||||
|
if (slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss) {
|
||||||
|
hasBoss = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
oops.message.dispatchEvent(GameEvent.NewWave, {
|
oops.message.dispatchEvent(GameEvent.NewWave, {
|
||||||
wave: this.currentWave,
|
wave: this.currentWave,
|
||||||
total: this.waveTargetCount,
|
total: this.waveTargetCount,
|
||||||
bossWave: this.isBossWave(),
|
bossWave: hasBoss,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private tryAdvanceWave() {
|
private tryAdvanceWave() {
|
||||||
if (this.waveSpawnedCount < this.waveTargetCount) return;
|
|
||||||
if (this.isBossWave() && !this.bossSpawnedInWave) return;
|
|
||||||
if (this.hasPendingSlotQueue()) return;
|
if (this.hasPendingSlotQueue()) return;
|
||||||
if (this.hasActiveSlotMonster()) return;
|
if (this.hasActiveSlotMonster()) return;
|
||||||
if (smc.vmdata.mission_data.mon_num > 0) return;
|
if (smc.vmdata.mission_data.mon_num > 0) return;
|
||||||
this.startNextWave();
|
this.startNextWave();
|
||||||
}
|
}
|
||||||
|
|
||||||
private isBossWave() {
|
|
||||||
const interval = Math.max(1, Math.floor(this.bossWaveInterval));
|
|
||||||
return this.currentWave > 0 && this.currentWave % interval === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCurrentStage(): number {
|
private getCurrentStage(): number {
|
||||||
return Math.max(0, this.currentWave - 1);
|
return Math.max(0, this.currentWave - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRandomUpType(): UpType {
|
private getRandomUpType(): UpType {
|
||||||
// 从 StageGrow 的 key 中采样,保证新增配置无需改逻辑
|
|
||||||
const keys = Object.keys(StageGrow).map(v => Number(v) as UpType);
|
const keys = Object.keys(StageGrow).map(v => Number(v) as UpType);
|
||||||
const index = Math.floor(Math.random() * keys.length);
|
const index = Math.floor(Math.random() * keys.length);
|
||||||
return keys[index] ?? UpType.AP1_HP1;
|
return keys[index] ?? UpType.AP1_HP1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRandomNormalMonsterUuid(): number {
|
private getRandomUuidByType(monType: number): number {
|
||||||
// MonType 是常量对象,这里通过值采样拿到怪物类型 id
|
const pool = (MonList as any)[monType] || MonList[MonType.Melee];
|
||||||
const typeKeys = Object.keys(MonType).map(k => (MonType as any)[k]).filter(v => typeof v === "number");
|
if (!pool || pool.length === 0) return 6001;
|
||||||
const randomType = typeKeys[Math.floor(Math.random() * typeKeys.length)] as number;
|
|
||||||
// 如果某类型配置被清空,回退到 AP 类型,避免空池异常
|
|
||||||
const pool = MonList[randomType] || MonList[MonType.Melee];
|
|
||||||
const index = Math.floor(Math.random() * pool.length);
|
const index = Math.floor(Math.random() * pool.length);
|
||||||
return pool[index];
|
return pool[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRandomBossUuid(): number {
|
|
||||||
// 目前 Boss 池可扩展为多个,先走随机抽取
|
|
||||||
const index = Math.floor(Math.random() * BossList.length);
|
|
||||||
return BossList[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveGrowPair(upType: UpType, isBoss: boolean): [number, number] {
|
private resolveGrowPair(upType: UpType, isBoss: boolean): [number, number] {
|
||||||
// 普通怪基础成长:StageGrow
|
// 普通怪基础成长:StageGrow
|
||||||
const grow = StageGrow[upType] || StageGrow[UpType.AP1_HP1];
|
const grow = StageGrow[upType] || StageGrow[UpType.AP1_HP1];
|
||||||
@@ -214,27 +169,21 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
return SpawnPowerBias;
|
return SpawnPowerBias;
|
||||||
}
|
}
|
||||||
|
|
||||||
private primeWaveInitialBurst() {
|
|
||||||
const remain = this.waveTargetCount - this.waveSpawnedCount;
|
|
||||||
if (remain <= 0) return;
|
|
||||||
const burstCount = Math.min(this.slotSpawnQueues.length, remain);
|
|
||||||
for (let i = 0; i < burstCount; i++) {
|
|
||||||
const uuid = this.getRandomNormalMonsterUuid();
|
|
||||||
const upType = this.getRandomUpType();
|
|
||||||
this.enqueueMonsterRequest(uuid, false, upType, 1, 1);
|
|
||||||
}
|
|
||||||
this.waveSpawnedCount += burstCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resetSlotSpawnData(wave: number = 1) {
|
private resetSlotSpawnData(wave: number = 1) {
|
||||||
const config: IWaveSlot[] = WaveSlotConfig[wave] || DefaultWaveSlot;
|
const config: IWaveSlot[] = WaveSlotConfig[wave] || DefaultWaveSlot;
|
||||||
|
|
||||||
let totalSlots = 0;
|
let totalSlots = 0;
|
||||||
|
let totalMonsters = 0;
|
||||||
for (const slot of config) {
|
for (const slot of config) {
|
||||||
const slotsPerMon = slot.slotsPerMon || 1;
|
const slotsPerMon = slot.slotsPerMon || 1;
|
||||||
|
const monCount = slot.monCount || 1;
|
||||||
totalSlots += slot.count * slotsPerMon;
|
totalSlots += slot.count * slotsPerMon;
|
||||||
|
totalMonsters += slot.count * monCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.waveTargetCount = totalMonsters;
|
||||||
|
this.waveSpawnedCount = 0;
|
||||||
|
|
||||||
this.slotSpawnQueues = Array.from(
|
this.slotSpawnQueues = Array.from(
|
||||||
{ length: totalSlots },
|
{ length: totalSlots },
|
||||||
() => []
|
() => []
|
||||||
@@ -243,18 +192,31 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
{ length: totalSlots },
|
{ length: totalSlots },
|
||||||
() => null
|
() => null
|
||||||
);
|
);
|
||||||
this.confirmWaveSlotTypes(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
private confirmWaveSlotTypes(config: IWaveSlot[]) {
|
|
||||||
this.slotRangeTypes = [];
|
this.slotRangeTypes = [];
|
||||||
this.slotSizes = [];
|
this.slotSizes = [];
|
||||||
|
|
||||||
|
let slotIndex = 0;
|
||||||
for (const slot of config) {
|
for (const slot of config) {
|
||||||
const slotsPerMon = slot.slotsPerMon || 1;
|
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++) {
|
for (let i = 0; i < slot.count; i++) {
|
||||||
|
const currentSlotIndex = slotIndex;
|
||||||
|
|
||||||
|
// 设置槽位类型和大小
|
||||||
for (let s = 0; s < slotsPerMon; s++) {
|
for (let s = 0; s < slotsPerMon; s++) {
|
||||||
this.slotRangeTypes.push(slot.type);
|
this.slotRangeTypes.push(slot.type);
|
||||||
this.slotSizes.push(slotsPerMon);
|
this.slotSizes.push(slotsPerMon);
|
||||||
|
slotIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据配置数量,直接在波次开始时把该坑位要刷的所有怪排入队列
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,36 +37,51 @@ export const SpawnPowerBias = 1
|
|||||||
|
|
||||||
export interface IWaveSlot {
|
export interface IWaveSlot {
|
||||||
type: number; // 对应 MonType
|
type: number; // 对应 MonType
|
||||||
count: number; // 怪物数量
|
count: number; // 占位数量
|
||||||
slotsPerMon?: number; // 每个怪占用几个位置,默认 1
|
slotsPerMon?: number; // 每个怪占用几个位置,默认 1
|
||||||
|
monCount: number; // 这个占位排队的怪物总数(每个坑位要刷多少个怪)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 每波怪物占位数量配置:数组顺序即为占位从左到右的排列顺序
|
// =========================================================================================
|
||||||
|
// 【每波怪物占位与刷怪配置说明】
|
||||||
|
// 1. 数组顺序:数组中的元素顺序即为战场上怪物从左到右占位的物理顺序。
|
||||||
|
// 2. 字段说明:
|
||||||
|
// - type: 怪物类型 (参考 MonType,如近战 0,远程 1,Boss 3 等)。
|
||||||
|
// - count: 该类型在场上同时存在几个并排的占位坑。
|
||||||
|
// - monCount: 每个占位坑需要刷出的怪物总数(即每个坑排队的怪物数量)。当场上该坑位的怪死亡后,排队的下一只才会生成。
|
||||||
|
// - slotsPerMon: (可选) 单个怪物体积占用几个占位坑,默认为 1。如果是大型 Boss 可设为 2 或更多,它会跨占位降落。
|
||||||
|
//
|
||||||
|
// 举例:
|
||||||
|
// { type: MonType.Melee, count: 2, monCount: 3 }
|
||||||
|
// 表示:在对应的位置开启 2 个近战占位坑,每个坑要排队刷出 3 只怪,总计该行配置会刷出 6 只近战怪。
|
||||||
|
//
|
||||||
|
// 【注意】:波次怪物的总数将由所有坑位的 count * monCount 自动累加计算得出。
|
||||||
|
// =========================================================================================
|
||||||
export const WaveSlotConfig: { [wave: number]: IWaveSlot[] } = {
|
export const WaveSlotConfig: { [wave: number]: IWaveSlot[] } = {
|
||||||
1: [
|
1: [
|
||||||
{ type: MonType.Melee, count: 2 },
|
{ type: MonType.Melee, count: 2, monCount: 3 },
|
||||||
{ type: MonType.Long, count: 2 }
|
{ type: MonType.Long, count: 2, monCount: 2 }
|
||||||
],
|
],
|
||||||
2: [
|
2: [
|
||||||
{ type: MonType.Melee, count: 2 },
|
{ type: MonType.Melee, count: 2, monCount: 4 },
|
||||||
{ type: MonType.Long, count: 2 },
|
{ type: MonType.Long, count: 2, monCount: 3 },
|
||||||
{ type: MonType.Support, count: 1 }
|
{ type: MonType.Support, count: 1, monCount: 2 }
|
||||||
],
|
],
|
||||||
3: [
|
3: [
|
||||||
{ type: MonType.Melee, count: 1 },
|
{ type: MonType.Melee, count: 1, monCount: 5 },
|
||||||
{ type: MonType.MeleeBoss, count: 1, slotsPerMon: 2 },
|
{ type: MonType.MeleeBoss, count: 1, slotsPerMon: 2, monCount: 1 },
|
||||||
{ type: MonType.Long, count: 2 }
|
{ type: MonType.Long, count: 2, monCount: 4 }
|
||||||
],
|
],
|
||||||
4: [
|
4: [
|
||||||
{ type: MonType.Melee, count: 1 },
|
{ type: MonType.Melee, count: 1, monCount: 5 },
|
||||||
{ type: MonType.Long, count: 1 },
|
{ type: MonType.Long, count: 1, monCount: 5 },
|
||||||
{ type: MonType.LongBoss, count: 1, slotsPerMon: 2 }
|
{ type: MonType.LongBoss, count: 1, slotsPerMon: 2, monCount: 1 }
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认占位配置 (如果在 WaveSlotConfig 中找不到波次,则使用此配置)
|
// 默认占位配置 (如果在 WaveSlotConfig 中找不到波次,则使用此配置)
|
||||||
export const DefaultWaveSlot: IWaveSlot[] = [
|
export const DefaultWaveSlot: IWaveSlot[] = [
|
||||||
{ type: MonType.Melee, count: 2 },
|
{ type: MonType.Melee, count: 2, monCount: 3 },
|
||||||
{ type: MonType.Long, count: 3 }
|
{ type: MonType.Long, count: 3, monCount: 3 }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user