feat(怪物系统): 实现基于威胁预算的动态刷怪机制
新增威胁预算系统,根据游戏时间和英雄血量动态生成怪物 - 添加 HeroAttrsComp 查询获取英雄血量比例 - 实现 calculateBudget 计算当前威胁点数 - 实现 generateMonstersFromBudget 根据预算生成怪物 - 添加每秒刷怪逻辑到 MissionMonComp - 定义不同时间段的怪物生成权重配置
This commit is contained in:
@@ -5,6 +5,7 @@ import { GameMap } from "../map/GameMap";
|
||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||
import { WxCloudApi } from "../wx_clound_client_api/WxCloudApi";
|
||||
import { GameEvent } from "./config/GameEvent";
|
||||
import * as exp from "constants";
|
||||
/**
|
||||
* 用远程数据覆盖本地数据(统一方法)
|
||||
* @param remoteData 远程数据(云端或本地调试)
|
||||
@@ -59,6 +60,27 @@ export class SingletonModuleComp extends ecs.Comp {
|
||||
max_mission:4,//最大关卡
|
||||
coin:0,
|
||||
},
|
||||
hero_data:{
|
||||
name:'',
|
||||
path:'',
|
||||
as:0,
|
||||
type:0,
|
||||
lv:0,
|
||||
exp:0,
|
||||
exp_max:0,
|
||||
hp:0,
|
||||
ho_max:0,
|
||||
mp:0,
|
||||
mp_max:0,
|
||||
def:0,
|
||||
ap:0,
|
||||
dis:0,
|
||||
speed:0,
|
||||
skills:[],
|
||||
buff:[],
|
||||
tal:[],
|
||||
info:'',
|
||||
},
|
||||
gold: 100, // 金币数据(MVVM绑定字段)
|
||||
};
|
||||
vmAdd() {
|
||||
|
||||
@@ -6,9 +6,11 @@ import { MonStart } from "../common/config/heroSet";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
// 导入肉鸽配置
|
||||
import { getStageMonConfigs, MonType } from "./RogueConfig";
|
||||
import { getStageMonConfigs, MonType, generateMonstersFromBudget } from "./RogueConfig";
|
||||
import { BuffConf } from "../common/config/SkillSet";
|
||||
import { IndexSet } from "../common/config/GameSet";
|
||||
import { IndexSet, FacSet } from "../common/config/GameSet";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { Attrs } from "../common/config/HeroAttrs";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/** 视图层对象 */
|
||||
@@ -34,6 +36,8 @@ export class MissionMonCompComp extends CCComp {
|
||||
private globalSpawnOrder: number = 0;
|
||||
/** 游戏进行时间(秒) */
|
||||
private gameTime: number = 0;
|
||||
/** 刷怪逻辑计时器(每秒执行一次) */
|
||||
private spawnLogicTimer: number = 0;
|
||||
|
||||
|
||||
onLoad(){
|
||||
@@ -61,6 +65,31 @@ export class MissionMonCompComp extends CCComp {
|
||||
// 累加游戏时间
|
||||
this.gameTime += dt;
|
||||
|
||||
// ==========================================
|
||||
// 新增:每秒执行一次刷怪逻辑 (Threat Budget)
|
||||
// ==========================================
|
||||
this.spawnLogicTimer += dt;
|
||||
if (this.spawnLogicTimer >= 1.0) {
|
||||
this.spawnLogicTimer = 0;
|
||||
|
||||
// 获取英雄血量比例
|
||||
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 || []
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 处理随机事件
|
||||
|
||||
|
||||
@@ -102,15 +131,33 @@ export class MissionMonCompComp extends CCComp {
|
||||
this.spawnTimer = 0;
|
||||
this.eventProcessed = false;
|
||||
|
||||
const cStage = smc.data.mission;
|
||||
// 使用新的肉鸽关卡配置
|
||||
let level=smc.vmdata.mission_data.level
|
||||
// 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);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
return 1.0; // 默认满血
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -186,3 +186,152 @@ export function getStageMonConfigs(stage: number): IMonsConfig[] {
|
||||
|
||||
return monsterConfigs;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 新增:基于威胁点数(Threat Budget)的刷怪系统
|
||||
// ==========================================
|
||||
|
||||
// 怪物消耗点数配置
|
||||
export const MonsterCost: Record<number, number> = {
|
||||
5201: 1, // 兽人战士 (Warrior)
|
||||
5301: 3, // 兽人斥候 (Assassin)
|
||||
5401: 5, // 兽人卫士 (Tank)
|
||||
5501: 4, // 兽人射手 (Remote)
|
||||
5601: 10, // 兽人自爆兵 (Mechanic)
|
||||
5602: 8, // 兽人召唤师
|
||||
5603: 6, // 兽人祭司 (Healer)
|
||||
5604: 6, // 兽人图腾师
|
||||
5701: 50, // 兽人首领 (Elite/Boss)
|
||||
};
|
||||
|
||||
// 刷怪权重接口
|
||||
interface SpawnWeight {
|
||||
uuid: number;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据游戏时间获取刷怪权重
|
||||
* @param timeInSeconds 游戏时间(秒)
|
||||
*/
|
||||
function getSpawnWeights(timeInSeconds: number): SpawnWeight[] {
|
||||
const minutes = timeInSeconds / 60;
|
||||
|
||||
if (minutes < 2) {
|
||||
// 0-2min: 匀速群落 - 100% 战士
|
||||
return [{ uuid: 5201, weight: 100 }];
|
||||
} else if (minutes < 5) {
|
||||
// 2-5min: 快速干扰 - 70% 战士, 30% 刺客
|
||||
return [
|
||||
{ uuid: 5201, weight: 70 },
|
||||
{ uuid: 5301, weight: 30 }
|
||||
];
|
||||
} else if (minutes < 10) {
|
||||
// 5-10min: 阵地博弈 - 50% 战士, 40% 刺客, 10% 攻城/治疗
|
||||
return [
|
||||
{ uuid: 5201, weight: 50 },
|
||||
{ uuid: 5301, weight: 40 },
|
||||
{ uuid: 5401, weight: 5 }, // 攻城
|
||||
{ uuid: 5603, weight: 5 } // 治疗
|
||||
];
|
||||
} else if (minutes < 14) {
|
||||
// 10-14min: 极限生存 - 30% 战士, 50% 刺客, 20% 机制/精英
|
||||
return [
|
||||
{ uuid: 5201, weight: 30 },
|
||||
{ uuid: 5301, weight: 50 },
|
||||
{ uuid: 5601, weight: 10 }, // 机制怪
|
||||
{ uuid: 5701, weight: 10 } // 精英
|
||||
];
|
||||
} else {
|
||||
// 15min: 剧情杀/决战 - 100% Boss
|
||||
return [{ uuid: 5701, weight: 100 }];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前威胁点数预算
|
||||
* @param timeInSeconds 游戏时间(秒)
|
||||
* @param heroHpRatio 英雄血量比例 (0-1)
|
||||
*/
|
||||
export function calculateBudget(timeInSeconds: number, heroHpRatio: number = 1.0): number {
|
||||
// 基础预算:每秒产生的点数
|
||||
// 假设1分钟时为20点,公式: Budget = Base * (1 + 60/60 * 0.2) = Base * 1.2 = 20 => Base ≈ 16
|
||||
const Base_Budget = 16;
|
||||
|
||||
// 时间因子:随时间增加难度
|
||||
const timeFactor = 1 + (timeInSeconds / 60) * 0.2;
|
||||
|
||||
// 强度乘数 (DDA):根据英雄状态调整
|
||||
let intensityMultiplier = 1.0;
|
||||
if (heroHpRatio < 0.4) {
|
||||
// 绝地求生:血量低于40%时,预算缩减30%
|
||||
intensityMultiplier = 0.7;
|
||||
}
|
||||
|
||||
// 公式: Budget(t) = Base_Budget * (1 + t/60 * 0.2) * Intensity_Multiplier
|
||||
return Math.floor(Base_Budget * timeFactor * intensityMultiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据预算生成怪物列表
|
||||
* @param timeInSeconds 游戏时间(秒)
|
||||
* @param heroHpRatio 英雄血量比例
|
||||
*/
|
||||
export function generateMonstersFromBudget(timeInSeconds: number, heroHpRatio: number = 1.0): IMonsConfig[] {
|
||||
// 15分钟后只生成Boss,且如果已经有Boss可能需要控制(这里简化为每次调用都尝试生成,由外部控制频率或唯一性)
|
||||
// 但根据"清除所有杂兵,锁定刷出最终BOSS",这里只返回Boss配置
|
||||
if (timeInSeconds >= 15 * 60) {
|
||||
// 可以在这里做特殊处理,比如只返回一个Boss,或者返回空如果Boss已存在
|
||||
// 假设外部每秒调用,这里返回Boss,外部队列会堆积。
|
||||
// 建议:Boss波次由特殊逻辑控制,或者这里返回Boss但消耗巨大预算
|
||||
return [{
|
||||
uuid: 5701,
|
||||
type: MonType.BOSS,
|
||||
level: Math.floor(timeInSeconds / 60), // 等级随时间
|
||||
position: 2 // 中间位置
|
||||
}];
|
||||
}
|
||||
|
||||
const budget = calculateBudget(timeInSeconds, heroHpRatio);
|
||||
const weights = getSpawnWeights(timeInSeconds);
|
||||
const monsters: IMonsConfig[] = [];
|
||||
let currentBudget = budget;
|
||||
|
||||
// 构建权重池
|
||||
const pool: number[] = [];
|
||||
weights.forEach(w => {
|
||||
for(let i=0; i<w.weight; i++) pool.push(w.uuid);
|
||||
});
|
||||
|
||||
if (pool.length === 0) return [];
|
||||
|
||||
let attempts = 0;
|
||||
// 尝试消耗预算直到耗尽或无法购买最便宜的怪物
|
||||
const minCost = Math.min(...weights.map(w => MonsterCost[w.uuid] || 1));
|
||||
|
||||
while (currentBudget >= minCost && attempts < 50) {
|
||||
attempts++;
|
||||
|
||||
// 随机选择怪物
|
||||
const uuid = pool[Math.floor(Math.random() * pool.length)];
|
||||
const cost = MonsterCost[uuid] || 1;
|
||||
|
||||
if (currentBudget >= cost) {
|
||||
currentBudget -= cost;
|
||||
|
||||
// 确定怪物类型
|
||||
let type = MonType.NORMAL;
|
||||
if (uuid === 5701) type = MonType.BOSS; // 或者是精英,视情况而定
|
||||
else if (MonsterCost[uuid] >= 10) type = MonType.ELITE;
|
||||
|
||||
monsters.push({
|
||||
uuid: uuid,
|
||||
type: type,
|
||||
level: Math.floor(timeInSeconds / 60) + 1, // 简单等级计算,实际属性由getMonAttr处理
|
||||
position: Math.floor(Math.random() * 5) // 随机位置 0-4
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return monsters;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user