import { _decorator, Vec3,Animation, instantiate, Prefab, Node, ProgressBar, Label } from "cc"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; 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 { HeroAttrsComp } from "../hero/HeroAttrsComp"; import { GameEvent } from "../common/config/GameEvent"; import { HeroViewComp } from "../hero/HeroViewComp"; import { UIID } from "../common/config/GameUIConfig"; import { SkillView } from "../skill/SkillView"; import { FacSet, FightSet } from "../common/config/GameSet"; import { mLogger } from "../common/Logger"; import { Monster } from "../hero/Mon"; import { Skill } from "../skill/Skill"; import { Tooltip } from "../skill/Tooltip"; const { ccclass, property } = _decorator; //@todo 需要关注 当boss死亡的时候的动画播放完成后,需要触发事件,通知 MissionComp 进行奖励处理 /** 视图层对象 */ @ccclass('MissionComp') @ecs.register('MissionComp', false) export class MissionComp extends CCComp { @property({ tooltip: "是否启用调试日志" }) private debugMode: boolean = false; @property({ tooltip: "是否显示战斗内存观测面板" }) private showMemoryPanel: boolean = false; @property({ tooltip: "场上怪物上限" }) private maxMonsterCount: number = 5; @property({ tooltip: "恢复刷怪阈值" }) private resumeMonsterCount: number = 3; // VictoryComp:any = null; // reward:number = 0; // reward_num:number = 0; @property(Node) time_node:Node = null! FightTime:number = FightSet.FiIGHT_TIME /** 剩余复活次数 */ revive_times: number = 1; rewards:any[]=[] game_data:any={ exp:0, gold:0, diamond:0 } private lastTimeStr: string = ""; private lastTimeSecond: number = -1; private memoryLabel: Label | null = null; private memoryRefreshTimer: number = 0; private lastMemoryText: string = ""; private perfDtAcc: number = 0; private perfFrameCount: number = 0; private heapBaseMB: number = -1; private heapPeakMB: number = 0; private heapTrendPerMinMB: number = 0; private heapTrendTimer: number = 0; private heapTrendBaseMB: number = -1; private monsterCountSyncTimer: number = 0; private readonly heroViewMatcher = ecs.allOf(HeroViewComp); private readonly skillViewMatcher = ecs.allOf(SkillView); private readonly heroAttrsMatcher = ecs.allOf(HeroAttrsComp); // 记录已触发的特殊刷怪索引 onLoad(){ this.showMemoryPanel = false this.on(GameEvent.MissionStart,this.mission_start,this) // this.on(GameEvent.HeroDead,this.do_hero_dead,this) // 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.removeMemoryPanel() } protected update(dt: number): void { if(!smc.mission.play) return if(smc.mission.pause) return if(smc.mission.in_fight){ this.syncMonsterSpawnState(dt) if(smc.mission.stop_mon_action) return smc.vmdata.mission_data.fight_time+=dt this.FightTime-=dt // 检查特殊刷怪时间 this.update_time(); } } update_time(){ const time = Math.max(0, this.FightTime); const remainSecond = Math.floor(time); if (remainSecond === this.lastTimeSecond) return; this.lastTimeSecond = remainSecond; let m = Math.floor(remainSecond / 60); let s = remainSecond % 60; let str = `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; if(str != this.lastTimeStr){ this.time_node.getChildByName("time").getComponent(Label).string = str; this.lastTimeStr = str; } } //奖励发放 do_reward(){ // 奖励发放 } do_ad(){ if(this.ad_back()){ oops.message.dispatchEvent(GameEvent.AD_BACK_TRUE) smc.vmdata.mission_data.refresh_count+=FightSet.MORE_RC }else{ oops.message.dispatchEvent(GameEvent.AD_BACK_FALSE) } } ad_back(){ return true } async mission_start(){ // 防止上一局的 fight_end 延迟回调干扰新局 this.unscheduleAllCallbacks(); // 确保清理上一局的残留实体 this.cleanComponents(); oops.message.dispatchEvent(GameEvent.FightReady) this.node.active=true this.data_init() let loading=this.node.parent.getChildByName("loading") loading.active=true this.scheduleOnce(()=>{ loading.active=false },0.5) this.scheduleOnce(()=>{ this.to_fight() },0.1) } to_fight(){ smc.mission.in_fight=true oops.message.dispatchEvent(GameEvent.FightStart) //GameSetMonComp 监听刷怪 } open_Victory(e:any,is_hero_dead: boolean = false){ // 暂停游戏循环和怪物行为 // smc.mission.play = false; smc.mission.pause = true; // oops.message.dispatchEvent(GameEvent.FightEnd,{victory:false}) mLogger.log(this.debugMode, 'MissionComp', " open_Victory",is_hero_dead,this.revive_times) oops.gui.open(UIID.Victory,{ victory:false, rewards:this.rewards, game_data:this.game_data, can_revive: is_hero_dead && this.revive_times > 0 }) } fight_end(){ // mLogger.log(this.debugMode, 'MissionComp', "任务结束") // 延迟0.5秒后执行任务结束逻辑 this.scheduleOnce(() => { smc.mission.play=false this.cleanComponents() this.clearBattlePools() }, 0.5) } mission_end(){ // mLogger.log(this.debugMode, 'MissionComp', " mission_end") // 合并 FightEnd 逻辑:清理组件、停止游戏循环 this.unscheduleAllCallbacks(); smc.mission.play=false smc.mission.pause = false; smc.mission.in_fight = false; this.cleanComponents() this.clearBattlePools() this.node.active=false } data_init(){ // 重置金币为初始值 (如果需要保留金币,请注释掉此行) smc.vmdata.gold = 0; //局内数据初始化 smc 数据初始化 smc.mission.play = true; smc.mission.pause = false; smc.mission.stop_mon_action = false; smc.mission.stop_spawn_mon = false; smc.vmdata.mission_data.in_fight=false smc.vmdata.mission_data.fight_time=0 smc.vmdata.mission_data.mon_num=0 smc.vmdata.mission_data.level=0 smc.vmdata.mission_data.mon_max = Math.max(1, Math.floor(this.maxMonsterCount)) this.FightTime=FightSet.FiIGHT_TIME this.rewards=[] // 改为数组,用于存储掉落物品列表 this.revive_times = 1; // 每次任务开始重置复活次数 this.lastTimeStr = ""; this.lastTimeSecond = -1; this.memoryRefreshTimer = 0; this.lastMemoryText = ""; this.perfDtAcc = 0; this.perfFrameCount = 0; this.heapBaseMB = -1; this.heapPeakMB = 0; this.heapTrendPerMinMB = 0; this.heapTrendTimer = 0; this.heapTrendBaseMB = -1; this.monsterCountSyncTimer = 0; // 重置全局属性加成和主角引用 (确保新一局数据干净) // smc.role = null; // 重置英雄数据,确保新一局是初始状态 // mLogger.log(this.debugMode, 'MissionComp', "局内数据初始化",smc.vmdata.mission_data) } private getMonsterThresholds(): { max: number; resume: number } { const max = Math.max(1, Math.floor(this.maxMonsterCount)); const resume = Math.min(max - 1, Math.max(0, Math.floor(this.resumeMonsterCount))); return { max, resume }; } private syncMonsterSpawnState(dt: number) { this.monsterCountSyncTimer += dt; if (dt > 0 && this.monsterCountSyncTimer < 0.2) return; this.monsterCountSyncTimer = 0; let monsterCount = 0; ecs.query(this.heroAttrsMatcher).forEach(entity => { const attrs = entity.get(HeroAttrsComp); if (!attrs || attrs.fac !== FacSet.MON || attrs.is_dead) return; monsterCount += 1; }); smc.vmdata.mission_data.mon_num = monsterCount; const { max, resume } = this.getMonsterThresholds(); smc.vmdata.mission_data.mon_max = max; const stopSpawn = !!smc.mission.stop_spawn_mon; if (stopSpawn) { if (monsterCount <= resume) smc.mission.stop_spawn_mon = false; return; } if (monsterCount >= max) smc.mission.stop_spawn_mon = true; } private cleanComponents() { const heroEntities: ecs.Entity[] = []; ecs.query(this.heroViewMatcher).forEach(entity => { heroEntities.push(entity); }); heroEntities.forEach(entity => { entity.destroy(); }); const skillEntities: ecs.Entity[] = []; ecs.query(this.skillViewMatcher).forEach(entity => { skillEntities.push(entity); }); skillEntities.forEach(entity => { entity.destroy(); }); } private clearBattlePools() { Monster.clearPools(); Skill.clearPools(); Tooltip.clearPool(); this.clearBattleSceneNodes(); } private clearBattleSceneNodes() { const scene = smc.map?.MapView?.scene; const layer = scene?.entityLayer?.node; if (!layer) return; const heroRoot = layer.getChildByName("HERO"); const skillRoot = layer.getChildByName("SKILL"); if (heroRoot) { for (let i = heroRoot.children.length - 1; i >= 0; i--) { heroRoot.children[i].destroy(); } } if (skillRoot) { for (let i = skillRoot.children.length - 1; i >= 0; i--) { skillRoot.children[i].destroy(); } } } private getBattleLayerNodeCount() { const scene = smc.map?.MapView?.scene; const layer = scene?.entityLayer?.node; if (!layer) return { heroNodes: 0, skillNodes: 0 }; const heroRoot = layer.getChildByName("HERO"); const skillRoot = layer.getChildByName("SKILL"); return { heroNodes: heroRoot?.children.length || 0, skillNodes: skillRoot?.children.length || 0 }; } /** 性能监控相关代码 */ private initMemoryPanel() { if (!this.showMemoryPanel || !this.time_node) return; let panel = this.time_node.getChildByName("mem_panel"); if (!panel) { panel = new Node("mem_panel"); panel.parent = this.time_node; panel.setPosition(0, -32, 0); } let label = panel.getComponent(Label); if (!label) { label = panel.addComponent(Label); } label.fontSize = 16; label.lineHeight = 20; this.memoryLabel = label; } private removeMemoryPanel() { const panel = this.time_node?.getChildByName("mem_panel"); if (panel) { panel.destroy(); } this.memoryLabel = null; this.lastMemoryText = ""; } private updateMemoryPanel(dt: number) { if (!this.showMemoryPanel || !this.memoryLabel) return; this.perfDtAcc += dt; this.perfFrameCount += 1; this.memoryRefreshTimer += dt; if (this.memoryRefreshTimer < 0.5) return; this.memoryRefreshTimer = 0; let heroCount = 0; ecs.query(this.heroViewMatcher).forEach(() => { heroCount++; }); let skillCount = 0; ecs.query(this.skillViewMatcher).forEach(() => { skillCount++; }); const monPool = Monster.getPoolStats(); const skillPool = Skill.getPoolStats(); const tooltipPool = Tooltip.getPoolStats(); const layerNodes = this.getBattleLayerNodeCount(); const perf = (globalThis as any).performance; const heapBytes = perf && perf.memory ? perf.memory.usedJSHeapSize : 0; let heapMB = heapBytes > 0 ? heapBytes / 1024 / 1024 : -1; if (heapMB > 0 && this.heapBaseMB < 0) { this.heapBaseMB = heapMB; this.heapPeakMB = heapMB; this.heapTrendBaseMB = heapMB; this.heapTrendTimer = 0; } if (heapMB > this.heapPeakMB) { this.heapPeakMB = heapMB; } this.heapTrendTimer += 0.5; if (heapMB > 0 && this.heapTrendBaseMB > 0 && this.heapTrendTimer >= 10) { const deltaMB = heapMB - this.heapTrendBaseMB; this.heapTrendPerMinMB = (deltaMB / this.heapTrendTimer) * 60; this.heapTrendBaseMB = heapMB; this.heapTrendTimer = 0; } const heapText = heapMB > 0 ? heapMB.toFixed(1) : "N/A"; const heapDeltaText = this.heapBaseMB > 0 && heapMB > 0 ? (heapMB - this.heapBaseMB).toFixed(1) : "N/A"; const heapPeakText = this.heapPeakMB > 0 ? this.heapPeakMB.toFixed(1) : "N/A"; const avgDt = this.perfFrameCount > 0 ? this.perfDtAcc / this.perfFrameCount : 0; const fps = avgDt > 0 ? 1 / avgDt : 0; this.perfDtAcc = 0; this.perfFrameCount = 0; const text = `Heap:${heapText}MB Δ:${heapDeltaText} Peak:${heapPeakText}\n` + `Trend:${this.heapTrendPerMinMB.toFixed(2)}MB/min\n` + `Perf dt:${(avgDt * 1000).toFixed(1)}ms fps:${fps.toFixed(1)}\n` + `Ent H:${heroCount} S:${skillCount} N:${layerNodes.heroNodes}/${layerNodes.skillNodes}\n` + `Pool M:${monPool.total}(${monPool.paths}) K:${skillPool.total}(${skillPool.paths}) T:${tooltipPool.total}`; if (text === this.lastMemoryText) return; this.lastMemoryText = text; this.memoryLabel.string = text; } /** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */ reset() { this.node.destroy(); } }