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 { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||||
import { WxCloudApi } from "../wx_clound_client_api/WxCloudApi";
|
import { WxCloudApi } from "../wx_clound_client_api/WxCloudApi";
|
||||||
import { GameEvent } from "./config/GameEvent";
|
import { GameEvent } from "./config/GameEvent";
|
||||||
|
import * as exp from "constants";
|
||||||
/**
|
/**
|
||||||
* 用远程数据覆盖本地数据(统一方法)
|
* 用远程数据覆盖本地数据(统一方法)
|
||||||
* @param remoteData 远程数据(云端或本地调试)
|
* @param remoteData 远程数据(云端或本地调试)
|
||||||
@@ -59,6 +60,27 @@ export class SingletonModuleComp extends ecs.Comp {
|
|||||||
max_mission:4,//最大关卡
|
max_mission:4,//最大关卡
|
||||||
coin:0,
|
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绑定字段)
|
gold: 100, // 金币数据(MVVM绑定字段)
|
||||||
};
|
};
|
||||||
vmAdd() {
|
vmAdd() {
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import { MonStart } 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 { getStageMonConfigs, MonType } from "./RogueConfig";
|
import { getStageMonConfigs, MonType, generateMonstersFromBudget } from "./RogueConfig";
|
||||||
import { BuffConf } from "../common/config/SkillSet";
|
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;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
/** 视图层对象 */
|
/** 视图层对象 */
|
||||||
@@ -34,6 +36,8 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
private globalSpawnOrder: number = 0;
|
private globalSpawnOrder: number = 0;
|
||||||
/** 游戏进行时间(秒) */
|
/** 游戏进行时间(秒) */
|
||||||
private gameTime: number = 0;
|
private gameTime: number = 0;
|
||||||
|
/** 刷怪逻辑计时器(每秒执行一次) */
|
||||||
|
private spawnLogicTimer: number = 0;
|
||||||
|
|
||||||
|
|
||||||
onLoad(){
|
onLoad(){
|
||||||
@@ -61,6 +65,31 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
// 累加游戏时间
|
// 累加游戏时间
|
||||||
this.gameTime += dt;
|
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.spawnTimer = 0;
|
||||||
this.eventProcessed = false;
|
this.eventProcessed = false;
|
||||||
|
|
||||||
const cStage = smc.data.mission;
|
// const cStage = smc.data.mission;
|
||||||
// 使用新的肉鸽关卡配置
|
// // 使用新的肉鸽关卡配置
|
||||||
let level=smc.vmdata.mission_data.level
|
// let level=smc.vmdata.mission_data.level
|
||||||
|
|
||||||
|
|
||||||
const monsConf = getStageMonConfigs(cStage);
|
// const monsConf = getStageMonConfigs(cStage);
|
||||||
// console.log(`[MissionMonComp]:第${cStage}关 - ${stageType}类型,怪物数量: ${monsConf.length}`);
|
// // console.log(`[MissionMonComp]:第${cStage}关 - ${stageType}类型,怪物数量: ${monsConf.length}`);
|
||||||
const monsConfFiltered = monsConf.filter((mon: any, index) => index === 0);
|
// const monsConfFiltered = monsConf.filter((mon: any, index) => index === 0);
|
||||||
this.generateMonsters(monsConfFiltered);
|
// 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;
|
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