From 56452795bb6d505affcc105264650ab1fe8e2e40 Mon Sep 17 00:00:00 2001 From: walkpan Date: Sat, 3 Jan 2026 10:09:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=8B=B1=E9=9B=84=E7=B3=BB=E7=BB=9F):=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=8B=B1=E9=9B=84=E5=8D=87=E7=BA=A7=E5=92=8C?= =?UTF-8?q?=E7=BB=8F=E9=AA=8C=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 HeroViewComp 中扩展怪物死亡事件数据,包含等级和类型信息 - 在 SingletonModuleComp 中实现完整的经验计算和升级逻辑 - 在 MissionComp 中添加经验获取和升级事件处理 - 在 RogueConfig 中添加经验计算公式和怪物经验配置 - 添加等级同步机制防止ECS数据覆盖 --- .../script/game/common/SingletonModuleComp.ts | 50 ++- assets/script/game/hero/HeroViewComp.ts | 5 +- assets/script/game/map/MissionComp.ts | 54 ++- assets/script/game/map/RogueConfig.ts | 393 ++++++++++-------- 4 files changed, 317 insertions(+), 185 deletions(-) diff --git a/assets/script/game/common/SingletonModuleComp.ts b/assets/script/game/common/SingletonModuleComp.ts index 9a4c13fa..8ba1968e 100644 --- a/assets/script/game/common/SingletonModuleComp.ts +++ b/assets/script/game/common/SingletonModuleComp.ts @@ -9,6 +9,7 @@ import * as exp from "constants"; import { HeroAttrsComp } from "../hero/HeroAttrsComp"; import { Attrs } from "./config/HeroAttrs"; import { time } from "console"; +import { getLevelExp } from "../map/RogueConfig"; /** * 用远程数据覆盖本地数据(统一方法) * @param remoteData 远程数据(云端或本地调试) @@ -89,6 +90,40 @@ export class SingletonModuleComp extends ecs.Comp { }, gold: 200, // 金币数据(MVVM绑定字段) }; + + /** + * 更新英雄经验 + * @param exp 获得的经验值 + */ + updateHeroExp(exp: number) { + if (!this.vmdata.hero) return; + + this.vmdata.hero.exp += exp; + // console.log('[smc] 英雄升级 经验:' + this.vmdata.hero.exp + ' 等级:' + this.vmdata.hero.lv + ' 上限:' + this.vmdata.hero.exp_max); + // 确保 exp_max 初始化 + if (this.vmdata.hero.exp_max <= 0) { + this.vmdata.hero.exp_max = getLevelExp(this.vmdata.hero.lv || 1); + } + + while (this.vmdata.hero.exp >= this.vmdata.hero.exp_max) { + this.vmdata.hero.exp -= this.vmdata.hero.exp_max; + this.vmdata.hero.lv++; + + // 更新下一级所需经验 + this.vmdata.hero.exp_max = getLevelExp(this.vmdata.hero.lv); + + // 触发升级事件 + oops.message.dispatchEvent(GameEvent.CanUpdateLv, { lv: this.vmdata.hero.lv }); + } + + // 更新进度条显示 (0-1) + if (this.vmdata.hero.exp_max > 0) { + this.vmdata.hero.exp_pre = Math.floor(this.vmdata.hero.exp / this.vmdata.hero.exp_max * 100); + } else { + this.vmdata.hero.exp_pre = 0; + } + } + vmAdd() { VM.add(this.vmdata, "data"); } @@ -225,7 +260,16 @@ export class SingletonModuleComp extends ecs.Comp { // 基础信息 h.name = heroAttrs.hero_name; h.type = heroAttrs.type; - h.lv = heroAttrs.lv; + + // 防止 ECS 旧数据覆盖 VM 新数据 (如果 VM 里的等级更高,说明刚升级还没同步到 ECS) + if (heroAttrs.lv > h.lv) { + h.lv = heroAttrs.lv; + } else if (h.lv > heroAttrs.lv) { + // 此时应该反向同步?或者等待 CanUpdateLv 事件处理 + // 这里暂时保持 VM 的高等级,不被 ECS 覆盖 + } else { + h.lv = heroAttrs.lv; + } // 动态属性 h.hp = Math.floor(heroAttrs.hp); @@ -241,10 +285,6 @@ export class SingletonModuleComp extends ecs.Comp { h.crt = Math.floor(heroAttrs.Attrs[Attrs.CRITICAL] || 0); h.as = Math.floor(heroAttrs.Attrs[Attrs.AS] || 0); } - updateHeroExp(exp:number){ - this.vmdata.hero.exp += exp; - this.vmdata.hero.exp_pre =Math.floor(this.vmdata.hero.exp/this.vmdata.hero.exp_max) - } error(){ oops.gui.toast("数据处理异常,请重试或重新登录") } diff --git a/assets/script/game/hero/HeroViewComp.ts b/assets/script/game/hero/HeroViewComp.ts index adf97882..75e652ad 100644 --- a/assets/script/game/hero/HeroViewComp.ts +++ b/assets/script/game/hero/HeroViewComp.ts @@ -404,7 +404,10 @@ export class HeroViewComp extends CCComp { // 根据阵营触发不同事件 if(this.model.fac === FacSet.MON){ oops.message.dispatchEvent(GameEvent.MonDead, { - hero_uuid: this.model.hero_uuid, + uuid: this.model.hero_uuid, + lv: this.model.lv, + is_boss: this.model.is_boss, + is_elite: this.model.is_big_boss, // 暂时映射 is_big_boss 为 elite,或者由 MissionComp 二次判断 position: this.node.position }); } diff --git a/assets/script/game/map/MissionComp.ts b/assets/script/game/map/MissionComp.ts index 88dde066..f14bb619 100644 --- a/assets/script/game/map/MissionComp.ts +++ b/assets/script/game/map/MissionComp.ts @@ -3,11 +3,13 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { smc } from "../common/SingletonModuleComp"; import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops"; -import { FightSet} from "../common/config/GameSet"; +import { HeroAttrsComp } from "../hero/HeroAttrsComp"; +import { getLevelExp, getMonsterExp, MonsterCost, MonType } from "./RogueConfig"; import { GameEvent } from "../common/config/GameEvent"; import { HeroViewComp } from "../hero/HeroViewComp"; import { UIID } from "../common/config/GameUIConfig"; import { SkillView } from "../skill/SkillView"; +import { FightSet } from "../common/config/GameSet"; const { ccclass, property } = _decorator; @@ -33,7 +35,7 @@ export class MissionComp extends CCComp { this.on(GameEvent.FightEnd,this.fight_end,this) this.on(GameEvent.MissionEnd,this.mission_end,this) this.on(GameEvent.DO_AD_BACK,this.do_ad,this) - // this.on(GameEvent.CanUpdateLv,this.show_uplv_button,this) + this.on(GameEvent.CanUpdateLv,this.onLevelUp,this) } protected update(dt: number): void { if(!smc.mission.play||smc.mission.pause){ @@ -45,6 +47,28 @@ export class MissionComp extends CCComp { } } + // 升级奖励触发 + onLevelUp(event: string, args: any) { + console.log(`[MissionComp] 英雄升级到 ${args.lv} 级!`); + + // 同步等级到 ECS 组件,防止被 updateHeroInfo 覆盖回旧值 + ecs.query(ecs.allOf(HeroAttrsComp)).forEach(e => { + const attrs = e.get(HeroAttrsComp); + if (attrs && attrs.is_master) { + attrs.lv = args.lv; + // 这里可以扩展:更新英雄属性,如 HP 上限等 + } + }); + + // 触发奖励选择界面 (暂时留空) + this.showLevelUpReward(); + } + + showLevelUpReward() { + // TODO: 显示三选一技能/属性奖励界面 + console.log("[MissionComp] 显示升级奖励界面 (TODO)"); + } + //奖励发放 do_reward(){ // 奖励发放 @@ -56,7 +80,31 @@ export class MissionComp extends CCComp { do_mon_dead(event:any,data:any){ // console.log("[MissionComp] do_mon_dead",event,data) smc.vmdata.mission_data.mon_num-- - + + // 计算并增加经验 + // data 应该是怪物组件或包含怪物信息的对象 + if (data && data.uuid) { + // 默认值处理 + const level = data.lv || 1; + + // 类型推断 + let type = MonType.NORMAL; + if (data.is_boss) { + type = MonType.BOSS; + } else if (data.is_elite) { + type = MonType.ELITE; + } else { + // 兜底策略:根据Cost判断是否为精英怪 + const cost = MonsterCost[data.uuid] || 1; + if (cost >= 10) { + type = MonType.ELITE; + } + } + + // 获取怪物经验 + const exp = getMonsterExp(data.uuid, level, type); + smc.updateHeroExp(exp); + } } do_hero_dead(event:any,data:any){ diff --git a/assets/script/game/map/RogueConfig.ts b/assets/script/game/map/RogueConfig.ts index 3f5176bc..5099de9b 100644 --- a/assets/script/game/map/RogueConfig.ts +++ b/assets/script/game/map/RogueConfig.ts @@ -65,182 +65,6 @@ enum GrowthType { LOGARITHMIC = 0.5 // 对数级 - Speed } -/** - * 计算波次因子 - * @param stage 当前波次 - * @param timeInSeconds 游戏进行时间(秒) - * @returns 波次因子 (0-1之间,15分钟时达到最大) - */ -function calculateWaveFactor(stage: number, timeInSeconds: number = 0): number { - const MAX_GAME_TIME = 15 * 60; // 15分钟 = 900秒 - const effectiveTime = timeInSeconds || (stage * 30); // 如果没有时间数据,用波次估算(每波30秒) - const factor = Math.min(effectiveTime / MAX_GAME_TIME, 1.0); - return factor; -} - -/** - * 应用成长公式到基础属性 - * @param baseStat 基础属性值 - * @param waveFactor 波次因子 (0-1) - * @param growthType 成长类型 - * @returns 成长后的属性值 - */ -function applyGrowthFormula(baseStat: number, waveFactor: number, growthType: GrowthType): number { - // 公式: Current_Stat = Base_Stat * (1 + waveFactor * 0.15) ^ Growth_Type - const growthMultiplier = Math.pow(1 + waveFactor * 0.15, growthType); - return Math.floor(baseStat * growthMultiplier); -} - -/** - * 获取怪物动态成长属性 - * @param stage 当前波次 - * @param uuid 怪物ID - * @param monType 怪物类型 - * @param timeInSeconds 游戏进行时间(秒) - * @returns 怪物属性 - */ -export function getMonAttr(stage: number, uuid: number, monType: MonType = MonType.NORMAL, timeInSeconds: number = 0): MonAttrs { - const baseMonster = HeroInfo[uuid]; - if (!baseMonster) { - console.warn(`[RogueConfig] 未找到怪物ID: ${uuid}`); - return { hp: 100, mp: 100, ap: 10, def: 0, speed: 100 }; - } - - // 计算波次因子 - const waveFactor = calculateWaveFactor(stage, timeInSeconds); - - // 根据怪物类型应用额外的倍率 - let typeMultiplier = 1.0; - if (monType === MonType.ELITE) { - typeMultiplier = 2.0; // 精英怪2倍属性 - } else if (monType === MonType.BOSS) { - typeMultiplier = 5.0; // Boss 5倍属性 - } - - // 应用不同的成长类型 - const hp = applyGrowthFormula(baseMonster.hp, waveFactor, GrowthType.EXPONENTIAL) * typeMultiplier; - const ap = applyGrowthFormula(baseMonster.ap, waveFactor, GrowthType.LINEAR) * typeMultiplier; - const speed = applyGrowthFormula(baseMonster.speed, waveFactor, GrowthType.LOGARITHMIC); - - // MP和DEF使用线性成长 - const mp = applyGrowthFormula(baseMonster.mp, waveFactor, GrowthType.LINEAR); - const def = applyGrowthFormula(baseMonster.def, waveFactor, GrowthType.LINEAR) * typeMultiplier; - - return { - hp: Math.floor(hp), - mp: Math.floor(mp), - ap: Math.floor(ap), - def: Math.floor(def), - speed: Math.floor(speed) - }; -} - -/** - * 根据波次生成怪物配置 - * @param stage 当前波次 - * @returns IMonsConfig数组 - */ -export function getStageMonConfigs(stage: number): IMonsConfig[] { - const monsterConfigs: IMonsConfig[] = []; - - // 基础怪物列表(从heroset.ts中获取) - const normalMons = [5201, 5301, 5401, 5501, 5601, 5602, 5603, 5604]; - const eliteMons = [5701]; - - // 根据波次生成怪物配置 - // 波次越高,怪物数量越多,精英怪物出现概率越高 - const baseCount = 5 + Math.floor(stage / 2); // 基础数量每2波增加1 - const eliteChance = Math.min(stage * 0.05, 0.3); // 精英怪概率最高30% - const bossWave = stage % 10 === 0; // 每10波出Boss - - if (bossWave && stage > 0) { - // Boss波 - monsterConfigs.push({ - uuid: 5701, - type: MonType.BOSS, - level: stage - }); - } else { - // 普通波 - for (let i = 0; i < baseCount; i++) { - // 随机决定是否生成精英怪 - const isElite = Math.random() < eliteChance; - const monList = isElite ? eliteMons : normalMons; - const randomUuid = monList[Math.floor(Math.random() * monList.length)]; - - monsterConfigs.push({ - uuid: randomUuid, - type: isElite ? MonType.ELITE : MonType.NORMAL, - level: stage, - position: i % 5 - }); - } - } - - 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 }]; - } -} - /** * 全局刷怪配置接口 @@ -384,3 +208,220 @@ export function generateMonstersFromBudget(timeInSeconds: number, heroHpRatio: n return monsters; } + + + + +/** + * 计算波次因子 + * @param stage 当前波次 + * @param timeInSeconds 游戏进行时间(秒) + * @returns 波次因子 (0-1之间,15分钟时达到最大) + */ +function calculateWaveFactor(stage: number, timeInSeconds: number = 0): number { + const MAX_GAME_TIME = 15 * 60; // 15分钟 = 900秒 + const effectiveTime = timeInSeconds || (stage * 30); // 如果没有时间数据,用波次估算(每波30秒) + const factor = Math.min(effectiveTime / MAX_GAME_TIME, 1.0); + return factor; +} + +/** + * 应用成长公式到基础属性 + * @param baseStat 基础属性值 + * @param waveFactor 波次因子 (0-1) + * @param growthType 成长类型 + * @returns 成长后的属性值 + */ +function applyGrowthFormula(baseStat: number, waveFactor: number, growthType: GrowthType): number { + // 公式: Current_Stat = Base_Stat * (1 + waveFactor * 0.15) ^ Growth_Type + const growthMultiplier = Math.pow(1 + waveFactor * 0.15, growthType); + return Math.floor(baseStat * growthMultiplier); +} + +/** + * 获取怪物动态成长属性 + * @param stage 当前波次 + * @param uuid 怪物ID + * @param monType 怪物类型 + * @param timeInSeconds 游戏进行时间(秒) + * @returns 怪物属性 + */ +export function getMonAttr(stage: number, uuid: number, monType: MonType = MonType.NORMAL, timeInSeconds: number = 0): MonAttrs { + const baseMonster = HeroInfo[uuid]; + if (!baseMonster) { + console.warn(`[RogueConfig] 未找到怪物ID: ${uuid}`); + return { hp: 100, mp: 100, ap: 10, def: 0, speed: 100 }; + } + + // 计算波次因子 + const waveFactor = calculateWaveFactor(stage, timeInSeconds); + + // 根据怪物类型应用额外的倍率 + let typeMultiplier = 1.0; + if (monType === MonType.ELITE) { + typeMultiplier = 2.0; // 精英怪2倍属性 + } else if (monType === MonType.BOSS) { + typeMultiplier = 5.0; // Boss 5倍属性 + } + + // 应用不同的成长类型 + const hp = applyGrowthFormula(baseMonster.hp, waveFactor, GrowthType.EXPONENTIAL) * typeMultiplier; + const ap = applyGrowthFormula(baseMonster.ap, waveFactor, GrowthType.LINEAR) * typeMultiplier; + const speed = applyGrowthFormula(baseMonster.speed, waveFactor, GrowthType.LOGARITHMIC); + + // MP和DEF使用线性成长 + const mp = applyGrowthFormula(baseMonster.mp, waveFactor, GrowthType.LINEAR); + const def = applyGrowthFormula(baseMonster.def, waveFactor, GrowthType.LINEAR) * typeMultiplier; + + return { + hp: Math.floor(hp), + mp: Math.floor(mp), + ap: Math.floor(ap), + def: Math.floor(def), + speed: Math.floor(speed) + }; +} + +/** + * 根据波次生成怪物配置 + * @param stage 当前波次 + * @returns IMonsConfig数组 + */ +export function getStageMonConfigs(stage: number): IMonsConfig[] { + const monsterConfigs: IMonsConfig[] = []; + + // 基础怪物列表(从heroset.ts中获取) + const normalMons = [5201, 5301, 5401, 5501, 5601, 5602, 5603, 5604]; + const eliteMons = [5701]; + + // 根据波次生成怪物配置 + // 波次越高,怪物数量越多,精英怪物出现概率越高 + const baseCount = 5 + Math.floor(stage / 2); // 基础数量每2波增加1 + const eliteChance = Math.min(stage * 0.05, 0.3); // 精英怪概率最高30% + const bossWave = stage % 10 === 0; // 每10波出Boss + + if (bossWave && stage > 0) { + // Boss波 + monsterConfigs.push({ + uuid: 5701, + type: MonType.BOSS, + level: stage + }); + } else { + // 普通波 + for (let i = 0; i < baseCount; i++) { + // 随机决定是否生成精英怪 + const isElite = Math.random() < eliteChance; + const monList = isElite ? eliteMons : normalMons; + const randomUuid = monList[Math.floor(Math.random() * monList.length)]; + + monsterConfigs.push({ + uuid: randomUuid, + type: isElite ? MonType.ELITE : MonType.NORMAL, + level: stage, + position: i % 5 + }); + } + } + + return monsterConfigs; +} + +/** + * 无限等级经验配置 + * @param level 当前等级 + * @returns 升级所需经验值 + */ +export function getLevelExp(level: number): number { + // 基础经验 + const baseExp = 100; + // 增长因子 (每级增加20%) + const growthFactor = 1.2; + + // 公式: Exp = Base * (Factor ^ (Level - 1)) + // 1级: 100 + // 2级: 120 + // 3级: 144 + // 10级: ~515 + // 20级: ~3194 + return Math.floor(baseExp * Math.pow(growthFactor, level - 1)); +} + +/** + * 获取怪物掉落经验 + * @param monsterUuid 怪物ID + * @param monsterLevel 怪物等级 + * @param monsterType 怪物类型 + */ +export function getMonsterExp(monsterUuid: number, monsterLevel: number, monsterType: MonType): number { + // 基础掉落经验 (可以从 MonsterCost 推导,或者单独配置) + const baseDrop = MonsterCost[monsterUuid] ? MonsterCost[monsterUuid] * 5 : 5; + + // 类型加成 + let typeMultiplier = 1.0; + if (monsterType === MonType.ELITE) typeMultiplier = 3.0; + if (monsterType === MonType.BOSS) typeMultiplier = 10.0; + + // 等级加成 (每级增加10%) + const levelMultiplier = 1 + (monsterLevel - 1) * 0.1; + + return Math.floor(baseDrop * typeMultiplier * levelMultiplier); +} + +// 怪物消耗点数配置 +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 }]; + } +} +