feat(肉鸽): 重构为基于波次的刷怪系统
- 废弃动态威胁预算算法,改用确定性的15波配置(每分钟1波) - 引入三阶段节奏设计:构筑期、磨合期、极限期,每波有独立怪物权重池 - 简化刷怪逻辑,移除复杂的预算计算和英雄血量响应机制 - 特殊事件怪物改为队列处理,与波次系统并行运行 - 优化代码结构,移除冗余状态变量和未使用的方法
This commit is contained in:
@@ -5,8 +5,8 @@ import { Monster } from "../hero/Mon";
|
||||
import { MonStart } from "../common/config/heroSet";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
// 导入肉鸽配置
|
||||
import { getStageMonConfigs, MonType, generateMonstersFromBudget, getRogueConfig } from "./RogueConfig";
|
||||
// 导入新的肉鸽配置
|
||||
import { getCurrentWave, MonType, WaveConfig } from "./RogueConfig";
|
||||
import { BuffConf } from "../common/config/SkillSet";
|
||||
import { IndexSet, FacSet, BoxSet } from "../common/config/GameSet";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
@@ -17,7 +17,7 @@ const { ccclass, property } = _decorator;
|
||||
@ccclass('MissionMonCompComp')
|
||||
@ecs.register('MissionMonComp', false)
|
||||
export class MissionMonCompComp extends CCComp {
|
||||
// 添加刷怪队列 - 使用新的RogueConfig格式
|
||||
// 刷怪队列 (主要用于特殊事件插队)
|
||||
private MonQueue: Array<{
|
||||
uuid: number,
|
||||
position: number,
|
||||
@@ -25,20 +25,20 @@ export class MissionMonCompComp extends CCComp {
|
||||
level: number,
|
||||
buffs: BuffConf[]
|
||||
}> = [];
|
||||
private isSpawning: boolean = false;// 是否正在生成怪物
|
||||
private spawnInterval: number = 0.6; // 每个怪物生成间隔时间(减半速度)
|
||||
private spawnTimer: number = 0; // 生成计时器
|
||||
|
||||
private spawnCount: number = 0; // 召唤计数器
|
||||
// private pauseInterval: number = 5.0; // 暂停间隔时间(5秒)
|
||||
// private isPausing: boolean = false; // 是否正在暂停
|
||||
private eventProcessed: boolean = false; // 事件是否已处理
|
||||
|
||||
/** 全局生成顺序计数器,用于层级管理 */
|
||||
private globalSpawnOrder: number = 0;
|
||||
|
||||
/** 游戏进行时间(秒) */
|
||||
private gameTime: number = 0;
|
||||
/** 刷怪逻辑计时器(每秒执行一次) */
|
||||
private spawnLogicTimer: number = 0;
|
||||
|
||||
/** 波次刷怪计时器 */
|
||||
private waveTimer: number = 0;
|
||||
|
||||
/** 队列处理计时器 */
|
||||
private queueTimer: number = 0;
|
||||
|
||||
onLoad(){
|
||||
this.on(GameEvent.FightReady,this.fight_ready,this)
|
||||
@@ -56,8 +56,8 @@ export class MissionMonCompComp extends CCComp {
|
||||
if (!args) return;
|
||||
console.log(`[MissionMonComp] 收到特殊刷怪指令:`, args);
|
||||
|
||||
// 插入队列头部,优先生成
|
||||
this.MonQueue.unshift({
|
||||
// 插入队列
|
||||
this.MonQueue.push({
|
||||
uuid: args.uuid,
|
||||
position: args.position !== undefined ? args.position : 2, // 默认中间
|
||||
type: args.type,
|
||||
@@ -65,27 +65,24 @@ export class MissionMonCompComp extends CCComp {
|
||||
buffs: args.buffs || []
|
||||
});
|
||||
|
||||
// 让刷怪计时器立即满足条件,以便尽快生成
|
||||
// 注意:不直接调用 spawnNextMonster 是为了保持 update 循环的一致性
|
||||
const config = getRogueConfig();
|
||||
this.spawnTimer = config.spawnInterval + 0.1;
|
||||
// 立即触发一次队列检查 (可选,让 update 尽快处理)
|
||||
this.queueTimer = 1.0;
|
||||
}
|
||||
|
||||
/** 视图层逻辑代码分离演示 */
|
||||
start() {
|
||||
// var entity = this.ent as ecs.Entity; // ecs.Entity 可转为当前模块的具体实体对象
|
||||
// this.on(ModuleEvent.Cmd, this.onHandler, this);
|
||||
}
|
||||
|
||||
fight_ready(){
|
||||
// console.log("[MissionMonComp]:fight_ready")
|
||||
smc.vmdata.mission_data.mon_num=0
|
||||
// 重置生成顺序计数器
|
||||
this.globalSpawnOrder = 0
|
||||
this.gameTime = 0
|
||||
this.spawnLogicTimer = 0
|
||||
this.waveTimer = 0
|
||||
this.queueTimer = 0
|
||||
this.MonQueue = []
|
||||
this.do_mon_wave()
|
||||
this.spawnCount = 0
|
||||
|
||||
console.log("[MissionMonComp] Starting Wave System (15-min Cycle)");
|
||||
}
|
||||
|
||||
protected update(dt: number): void {
|
||||
@@ -96,137 +93,75 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
// 累加游戏时间
|
||||
this.gameTime += dt;
|
||||
const config = getRogueConfig();
|
||||
|
||||
// ==========================================
|
||||
// 新增:每秒执行一次刷怪逻辑 (Threat Budget)
|
||||
// ==========================================
|
||||
this.spawnLogicTimer += dt;
|
||||
if (this.spawnLogicTimer >= config.spawnLogicInterval) {
|
||||
this.spawnLogicTimer = 0;
|
||||
|
||||
// 检查最大怪物数量限制
|
||||
if (smc.vmdata.mission_data.mon_num < config.maxMonsterCount) {
|
||||
// 获取英雄血量比例
|
||||
const hpRatio = this.getHeroHpRatio();
|
||||
|
||||
// 生成怪物
|
||||
const newMonsters = generateMonstersFromBudget(this.gameTime, hpRatio);
|
||||
|
||||
// 添加到队列
|
||||
newMonsters.forEach(mon => {
|
||||
this.addToStageSpawnQueue(
|
||||
mon.uuid,
|
||||
mon.position !== undefined ? mon.position : 0,
|
||||
mon.type,
|
||||
mon.level,
|
||||
mon.buffs || []
|
||||
);
|
||||
});
|
||||
|
||||
// 获取当前波次配置
|
||||
const currentWave = getCurrentWave(this.gameTime);
|
||||
|
||||
// 1. 优先处理特殊怪队列
|
||||
if (this.MonQueue.length > 0) {
|
||||
this.queueTimer += dt;
|
||||
// 队列出怪速度快于普通波次 (0.5秒一只)
|
||||
if (this.queueTimer >= 0.5) {
|
||||
this.spawnNextFromQueue();
|
||||
this.queueTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理随机事件
|
||||
|
||||
|
||||
// 处理刷怪队列
|
||||
if (this.MonQueue.length > 0 && !this.isSpawning) {
|
||||
this.spawnTimer += dt;
|
||||
// 2. 处理波次自然刷怪
|
||||
this.waveTimer += dt;
|
||||
if (this.waveTimer >= currentWave.spawnInterval) {
|
||||
this.waveTimer = 0;
|
||||
|
||||
// 正常召唤间隔
|
||||
if (this.spawnTimer >= config.spawnInterval) {
|
||||
this.spawnNextMonster();
|
||||
this.spawnTimer = 0;
|
||||
// 检查同屏数量限制
|
||||
if (smc.vmdata.mission_data.mon_num < currentWave.maxActive) {
|
||||
this.spawnWaveMonster(currentWave);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do_mon_wave(){
|
||||
// 重置召唤相关状态
|
||||
this.spawnCount = 0;
|
||||
// this.isPausing = false;
|
||||
this.spawnTimer = 0;
|
||||
this.eventProcessed = false;
|
||||
|
||||
// const cStage = smc.data.mission;
|
||||
// // 使用新的肉鸽关卡配置
|
||||
// let level=smc.vmdata.mission_data.level
|
||||
|
||||
|
||||
// const monsConf = getStageMonConfigs(cStage);
|
||||
// // console.log(`[MissionMonComp]:第${cStage}关 - ${stageType}类型,怪物数量: ${monsConf.length}`);
|
||||
// const monsConfFiltered = monsConf.filter((mon: any, index) => index === 0);
|
||||
// this.generateMonsters(monsConfFiltered);
|
||||
console.log("[MissionMonComp] Starting Threat Budget Wave System");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取英雄血量比例
|
||||
* 从当前波次配置生成并生成怪物
|
||||
*/
|
||||
private getHeroHpRatio(): number {
|
||||
// 查询带有 HeroAttrsComp 的实体
|
||||
// 注意:这里假设只有一个英雄,且性能允许每秒查询一次
|
||||
const entities = ecs.query(ecs.allOf(HeroAttrsComp));
|
||||
for (const e of entities) {
|
||||
const attrs = e.get(HeroAttrsComp);
|
||||
if (attrs && attrs.fac === FacSet.HERO) {
|
||||
const maxHp = attrs.Attrs[Attrs.HP_MAX] || 1;
|
||||
return attrs.hp / maxHp;
|
||||
private spawnWaveMonster(wave: WaveConfig) {
|
||||
if (!wave.weights || wave.weights.length === 0) return;
|
||||
|
||||
// 权重随机算法
|
||||
const totalWeight = wave.weights.reduce((sum, item) => sum + item.weight, 0);
|
||||
let random = Math.random() * totalWeight;
|
||||
let selectedUuid = wave.weights[0].uuid;
|
||||
let selectedType = wave.weights[0].type || MonType.NORMAL;
|
||||
|
||||
for (const item of wave.weights) {
|
||||
random -= item.weight;
|
||||
if (random <= 0) {
|
||||
selectedUuid = item.uuid;
|
||||
selectedType = item.type || MonType.NORMAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 1.0; // 默认满血
|
||||
|
||||
// 随机位置 (0-4)
|
||||
const position = Math.floor(Math.random() * 5);
|
||||
|
||||
// 等级随时间增长 (每分钟+1级)
|
||||
const level = Math.floor(this.gameTime / 60) + 1;
|
||||
|
||||
this.addMonster(
|
||||
selectedUuid,
|
||||
position,
|
||||
selectedType,
|
||||
level,
|
||||
[],
|
||||
this.gameTime
|
||||
);
|
||||
|
||||
this.spawnCount++;
|
||||
}
|
||||
|
||||
|
||||
// 根据新的关卡配置生成怪物
|
||||
private generateMonsters(monsConf: any[]) {
|
||||
const cStage = smc.data.mission;
|
||||
|
||||
// 设置怪物总数
|
||||
// console.log("[MissionMonComp] generateMonsters",monsConf)
|
||||
if (!monsConf || monsConf.length === 0) {
|
||||
console.warn(`[MissionMonComp]:关卡${cStage}配置中没有怪物信息`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 为每个怪物配置生成怪物
|
||||
monsConf.forEach((mon: any, index: number) => {
|
||||
const { uuid, type, level, buffs, position } = mon;
|
||||
|
||||
// 使用配置中的位置,如果没有则使用索引
|
||||
const spawnPosition = position !== undefined ? position : (index % 5);
|
||||
|
||||
this.addToStageSpawnQueue(
|
||||
uuid,
|
||||
spawnPosition,
|
||||
type,
|
||||
level,
|
||||
buffs
|
||||
);
|
||||
});
|
||||
|
||||
// console.log(`[MissionMonComp]:关卡${cStage}将生成 ${monsConf.length} 只怪物`);
|
||||
}
|
||||
|
||||
// 添加到关卡刷怪队列 - 使用新的配置格式
|
||||
private addToStageSpawnQueue(
|
||||
uuid: number,
|
||||
position: number,
|
||||
type: MonType = MonType.NORMAL,
|
||||
level: number = 1,
|
||||
buffs: BuffConf[] = []
|
||||
) {
|
||||
this.MonQueue.push({
|
||||
uuid: uuid,
|
||||
position: position,
|
||||
type: type,
|
||||
level: level,
|
||||
buffs: buffs
|
||||
});
|
||||
}
|
||||
|
||||
// 从队列中生成下一个怪物 - 使用新的配置格式
|
||||
private spawnNextMonster() {
|
||||
/**
|
||||
* 从队列中生成下一个怪物
|
||||
*/
|
||||
private spawnNextFromQueue() {
|
||||
if (this.MonQueue.length === 0) return;
|
||||
|
||||
const monsterData = this.MonQueue.shift();
|
||||
@@ -239,9 +174,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
monsterData.buffs,
|
||||
this.gameTime
|
||||
);
|
||||
// 增加召唤计数
|
||||
this.spawnCount++;
|
||||
// console.log(`[MissionMonComp]: 召唤第${this.spawnCount}只${monsterData.type}怪物,剩余队列: ${this.MonQueue.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,46 +188,22 @@ export class MissionMonCompComp extends CCComp {
|
||||
) {
|
||||
let mon = ecs.getEntity<Monster>(Monster);
|
||||
let scale = -1;
|
||||
// 使用 MonStart 计算怪物出生位置:
|
||||
// x 从 START_X 开始,按 START_I 的间隔递增;
|
||||
// y 在线路之间交替:0->SLINE_1, 1->SLINE_2, 2->SLINE_3, 3->SLINE_4
|
||||
const x = MonStart.START_X + Math.floor(i / 4) * MonStart.START_I;
|
||||
|
||||
const x = MonStart.START_X + Math.floor(i / 4) * MonStart.START_I;
|
||||
let y = BoxSet.GAME_LINE;
|
||||
let lane = 0;
|
||||
|
||||
// switch (i % 4) {
|
||||
// case 0:
|
||||
// y = MonStart.SLINE_1;
|
||||
// lane = 0;
|
||||
// break;
|
||||
// case 1:
|
||||
// y = MonStart.SLINE_2;
|
||||
// lane = 1;
|
||||
// break;
|
||||
// case 2:
|
||||
// y = MonStart.SLINE_3;
|
||||
// lane = 2;
|
||||
// break;
|
||||
// case 3:
|
||||
// y = MonStart.SLINE_4;
|
||||
// lane = 3;
|
||||
// break;
|
||||
// }
|
||||
|
||||
let pos: Vec3 = v3(x, y, 0);
|
||||
|
||||
// 递增全局生成顺序 - 🔥 添加溢出保护
|
||||
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999; // 防止无限增长,在999处循环重置
|
||||
// 递增全局生成顺序 - 溢出保护
|
||||
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
|
||||
|
||||
// 生成怪物,传递线路和生成顺序
|
||||
// 生成怪物
|
||||
mon.load(pos, scale, uuid, lv, monType, buffs, false, lane, this.globalSpawnOrder, gameTime);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
||||
reset() {
|
||||
// this.node.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user