Files
pixelheros/assets/script/game/map/MissionMonComp.ts
panw f713a82a2d feat(肉鸽): 实现动态难度和配置化刷怪系统
- 新增 RogueConfig 配置文件,定义怪物类型、成长曲线和刷新参数
- 重构 MissionMonComp 刷怪逻辑,支持普通怪、Boss 和特殊队列三种生成方式
- 引入阶段成长机制,怪物属性随游戏时间动态增强
- 添加随机成长类型,使同阶段怪物属性具有差异性
- 支持外部事件插队刷怪,用于运营活动和技能召唤等场景
2026-03-17 16:04:09 +08:00

223 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { _decorator, v3, Vec3 } from "cc";
import { mLogger } from "../common/Logger";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { Monster } from "../hero/Mon";
import { HeroInfo, MonStart } from "../common/config/heroSet";
import { smc } from "../common/SingletonModuleComp";
import { GameEvent } from "../common/config/GameEvent";
import {BoxSet } from "../common/config/GameSet";
import { BossList, BossSpawnCd, MonList, MonType, SpawnBaseCd, SpawnMinCd, SpawnPowerBias, SpawnStageReduce, StageBossGrow, StageDuration, StageGrow, UpType } from "./RogueConfig";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
const { ccclass, property } = _decorator;
/** 视图层对象 */
@ccclass('MissionMonCompComp')
@ecs.register('MissionMonComp', false)
export class MissionMonCompComp extends CCComp {
@property({ tooltip: "是否启用调试日志" })
private debugMode: boolean = false;
// 刷怪队列(用于插队生成:比如运营活动怪、技能召唤怪、剧情强制怪)
// 约定:队列里的怪会优先于常规刷新处理
private MonQueue: Array<{
uuid: number,
level: number,
}> = [];
private spawnCount: number = 0; // 总生成计数,用于控制横向分布位置
/** 全局生成顺序计数器,用于层级管理(预留) */
private globalSpawnOrder: number = 0;
/** 局内战斗运行时间(秒) */
private gameTime: number = 0;
/** 普通怪刷新计时器 */
private waveTimer: number = 0;
/** 插队刷怪处理计时器 */
private queueTimer: number = 0;
/** Boss 刷新计时器 */
private bossTimer: number = 0;
onLoad(){
// 关卡准备、切换波次时重置刷怪状态
this.on(GameEvent.FightReady,this.fight_ready,this)
this.on(GameEvent.NewWave,this.fight_ready,this)
// 支持外部模块主动推入特殊怪(优先级高于常规刷新)
this.on("SpawnSpecialMonster", this.onSpawnSpecialMonster, this);
}
/**
* 接收特殊刷怪事件并入队
* 事件数据最小结构:{ uuid, level }
*/
private onSpawnSpecialMonster(event: string, args: any) {
if (!args) return;
mLogger.log(this.debugMode, 'MissionMonComp', `[MissionMonComp] 收到特殊刷怪指令:`, args);
this.MonQueue.push({
uuid: args.uuid,
level: args.level,
});
// 让队列在下一帧附近尽快消费,提升事件响应感
this.queueTimer = 1.0;
}
start() {
}
fight_ready(){
// 仅重置“本组件”刷怪状态,不处理其他系统状态
smc.vmdata.mission_data.mon_num=0
this.globalSpawnOrder = 0
this.gameTime = 0
this.waveTimer = 0
this.queueTimer = 0
this.bossTimer = 0
this.MonQueue = []
this.spawnCount = 0
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System (15-min Cycle)");
}
protected update(dt: number): void {
// 统一战斗状态门禁:未开战/暂停/角色死亡阶段均不刷怪
if(!smc.mission.play) return
if(smc.mission.pause) return
if(smc.mission.stop_mon_action) return;
if(!smc.mission.in_fight) return;
// 计时推进:所有“按时间驱动”的曲线都依赖 gameTime
this.gameTime += dt;
// 刷怪优先级:特殊队列 > 普通怪 > Boss都满足条件时同帧可连续执行
this.updateSpecialQueue(dt);
this.updateNormalSpawn(dt);
this.updateBossSpawn(dt);
}
private updateSpecialQueue(dt: number) {
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();
this.addMonster(item.uuid, this.spawnCount, BossList.includes(item.uuid), upType);
this.spawnCount += 1;
}
private updateNormalSpawn(dt: number) {
this.waveTimer += dt;
// 普通怪刷新间隔会随 stage 增长而缩短
const spawnCd = this.getCurrentSpawnCd();
if (this.waveTimer < spawnCd) return;
this.waveTimer = 0;
// 先随机怪物类型,再在该类型池内随机具体 uuid
const uuid = this.getRandomNormalMonsterUuid();
// 每只怪独立抽取成长方案,使同时间段怪群有轻微离散性
const upType = this.getRandomUpType();
this.addMonster(uuid, this.spawnCount, false, upType);
this.spawnCount += 1;
}
private updateBossSpawn(dt: number) {
this.bossTimer += dt;
// Boss 按固定周期出现,不受普通怪 CD 影响
if (this.bossTimer < BossSpawnCd) return;
this.bossTimer = 0;
const uuid = this.getRandomBossUuid();
const upType = this.getRandomUpType();
this.addMonster(uuid, this.spawnCount, true, upType);
this.spawnCount += 1;
}
private getCurrentStage(): number {
// 每 30 秒提升 1 阶(由配置 StageDuration 控制)
return Math.floor(this.gameTime / StageDuration);
}
private getCurrentSpawnCd(): number {
const stage = this.getCurrentStage();
const cd = SpawnBaseCd - stage * SpawnStageReduce;
// 保护下限,避免后期刷新间隔过小打爆性能
return Math.max(SpawnMinCd, cd);
}
private getRandomUpType(): UpType {
// 从 StageGrow 的 key 中采样,保证新增配置无需改逻辑
const keys = Object.keys(StageGrow).map(v => Number(v) as UpType);
const index = Math.floor(Math.random() * keys.length);
return keys[index] ?? UpType.AP1_HP1;
}
private getRandomNormalMonsterUuid(): number {
// MonType 是常量对象,这里通过值采样拿到怪物类型 id
const typeKeys = Object.keys(MonType).map(k => (MonType as any)[k]).filter(v => typeof v === "number");
const randomType = typeKeys[Math.floor(Math.random() * typeKeys.length)] as number;
// 如果某类型配置被清空,回退到 AP 类型,避免空池异常
const pool = MonList[randomType] || MonList[MonType.AP];
const index = Math.floor(Math.random() * pool.length);
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] {
// 普通怪基础成长StageGrow
const grow = StageGrow[upType] || StageGrow[UpType.AP1_HP1];
if (!isBoss) return [grow[0], grow[1]];
// Boss 额外成长StageBossGrow在普通成长上叠加
const bossGrow = StageBossGrow[upType] || StageBossGrow[UpType.AP1_HP1];
return [grow[0] + bossGrow[0], grow[1] + bossGrow[1]];
}
private getSpawnPowerBias(): number {
// 动态难度偏差入口:当前固定读取配置,后续可切到玩家表现驱动
return SpawnPowerBias;
}
private addMonster(
uuid: number = 1001,
i: number = 0,
isBoss: boolean = false,
upType: UpType = UpType.AP1_HP1,
) {
// 创建 ECS 怪物实体
let mon = ecs.getEntity<Monster>(Monster);
let scale = -1;
// 按生成序号做横向错列,减轻重叠感
const x = MonStart.START_X + Math.floor(i / 4) * MonStart.START_I;
let y = BoxSet.GAME_LINE;
let pos: Vec3 = v3(x, y, 0);
// 递增全局生成顺序,做溢出保护
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
// 先用原始配置创建怪物,再覆盖成长后属性
mon.load(pos, scale, uuid, isBoss);
const model = mon.get(HeroAttrsComp);
const base = HeroInfo[uuid];
if (!model || !base) return;
const stage = this.getCurrentStage();
const grow = this.resolveGrowPair(upType, isBoss);
// 偏差值用于整体系数缩放1=不变,>1增强<1减弱
const bias = Math.max(0.1, this.getSpawnPowerBias());
// 最终公式:基础值 + 阶段成长,再乘偏差
model.ap = Math.max(1, Math.floor((base.ap + stage * grow[0]) * bias));
model.hp_max = Math.max(1, Math.floor((base.hp + stage * grow[1]) * bias));
// 满血登场,保证 hp/hp_max 一致
model.hp = model.hp_max;
}
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
// this.node.destroy();
}
}