From ad4fd303147e646613620b5123a065147c6e91db Mon Sep 17 00:00:00 2001 From: walkpan Date: Thu, 1 Jan 2026 23:49:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=80=AA=E7=89=A9=E7=B3=BB=E7=BB=9F):=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=9F=BA=E4=BA=8E=E5=A8=81=E8=83=81=E9=A2=84?= =?UTF-8?q?=E7=AE=97=E7=9A=84=E5=8A=A8=E6=80=81=E5=88=B7=E6=80=AA=E6=9C=BA?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增威胁预算系统,根据游戏时间和英雄血量动态生成怪物 - 添加 HeroAttrsComp 查询获取英雄血量比例 - 实现 calculateBudget 计算当前威胁点数 - 实现 generateMonstersFromBudget 根据预算生成怪物 - 添加每秒刷怪逻辑到 MissionMonComp - 定义不同时间段的怪物生成权重配置 --- .../script/game/common/SingletonModuleComp.ts | 22 +++ assets/script/game/map/MissionMonComp.ts | 65 ++++++-- assets/script/game/map/RogueConfig.ts | 149 ++++++++++++++++++ 3 files changed, 227 insertions(+), 9 deletions(-) diff --git a/assets/script/game/common/SingletonModuleComp.ts b/assets/script/game/common/SingletonModuleComp.ts index a0b9d056..4ae5e139 100644 --- a/assets/script/game/common/SingletonModuleComp.ts +++ b/assets/script/game/common/SingletonModuleComp.ts @@ -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() { diff --git a/assets/script/game/map/MissionMonComp.ts b/assets/script/game/map/MissionMonComp.ts index 1dabf913..146b165c 100644 --- a/assets/script/game/map/MissionMonComp.ts +++ b/assets/script/game/map/MissionMonComp.ts @@ -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; // 默认满血 } diff --git a/assets/script/game/map/RogueConfig.ts b/assets/script/game/map/RogueConfig.ts index cf15efa5..cb2fb679 100644 --- a/assets/script/game/map/RogueConfig.ts +++ b/assets/script/game/map/RogueConfig.ts @@ -186,3 +186,152 @@ export function getStageMonConfigs(stage: number): IMonsConfig[] { return monsterConfigs; } + +// ========================================== +// 新增:基于威胁点数(Threat Budget)的刷怪系统 +// ========================================== + +// 怪物消耗点数配置 +export const MonsterCost: Record = { + 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 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; +}