@@ -17,7 +17,7 @@
* - 第 5 节胜利 → open_Victory(final) 弹结算,确认退出并存档进下一关。
* - cleanComponents() / clearBattlePools() 仅在整关开始/结束时执行,节间不调用。
*/
import { _decorator , Vec3 , Animation , instantiate , Prefab , Node , NodeEventType , ProgressBar , Label , CCInteger , tween , v3 , Tween , Widget , UIOpacity } from "cc" ;
import { _decorator , Node , NodeEventType , Label , tween , v3 , Tween , Widget , Color , Sprite , ProgressBar } 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" ;
@@ -31,7 +31,6 @@ import { SkillTriggerHelper } from "../hero/SkillTriggerHelper";
import { UIID } from "../common/config/GameUIConfig" ;
import { SkillView } from "../skill/SkillView" ;
import { FacSet , FightSet } from "../common/config/GameSet" ;
import { HeroInfo } from "../common/config/heroSet" ;
import { mLogger } from "../common/Logger" ;
import { Monster } from "../hero/Mon" ;
import { Skill } from "../skill/Skill" ;
@@ -80,8 +79,6 @@ type SettleType = "defeat" | "final";
export class MissionComp extends CCComp {
@property ( { tooltip : "是否启用调试日志" } )
private debugMode : boolean = true ;
@property ( { tooltip : "是否显示战斗内存观测面板" } )
private showMemoryPanel : boolean = false ;
// ======================== 配置参数 ========================
@@ -97,6 +94,21 @@ export class MissionComp extends CCComp {
@property ( Node )
tooltip : Node = null !
/** 回合指示节点( 5 个,索引 0~4 对应回合 1~5) , 按回合进度染色 */
@property ( { type : [ Node ] , tooltip : "回合指示节点(按回合 1~5 顺序绑定)" } )
round_nodes : Node [ ] = [ ] ;
/** 回合进度条:第 1 回合 0%,每推进一回合 +25%(第 5 回合满格) */
@property ( { type : ProgressBar , tooltip : "回合进度条(第 1 回合 0%,每回合 +25%) " } )
round_progress : ProgressBar = null ! ;
/** 回合指示:未激活(白色) */
private static readonly ROUND_INACTIVE = new Color ( 255 , 255 , 255 , 255 ) ;
/** 回合指示:已激活(橙黄 #FFA300) */
private static readonly ROUND_ACTIVE = new Color ( 255 , 163 , 0 , 255 ) ;
/** 回合指示: Boss 当前回合(红色) */
private static readonly ROUND_BOSS = new Color ( 230 , 60 , 60 , 255 ) ;
/** 阶段名称映射表(用于 UI 显示) */
private static readonly PhaseNameMap : Record < MissionPhase , string > = {
[ MissionPhase . None ] : "未开始" ,
@@ -133,26 +145,6 @@ export class MissionComp extends CCComp {
private lastTimeStr : string = "" ;
/** 上一次显示的秒数(避免重复计算) */
private lastTimeSecond : number = - 1 ;
/** 性能监控面板 Label 引用 */
private memoryLabel : Label | null = null ;
/** 性能监控刷新计时器 */
private memoryRefreshTimer : number = 0 ;
/** 上一次性能文本(避免重复渲染) */
private lastMemoryText : string = "" ;
/** 帧间隔累加(用于计算平均 FPS) */
private perfDtAcc : number = 0 ;
/** 帧数计数 */
private perfFrameCount : number = 0 ;
/** 初始堆内存基准值( MB) */
private heapBaseMB : number = - 1 ;
/** 堆内存峰值( MB) */
private heapPeakMB : number = 0 ;
/** 堆内存增长趋势( MB/分钟) */
private heapTrendPerMinMB : number = 0 ;
/** 趋势计算计时器 */
private heapTrendTimer : number = 0 ;
/** 趋势计算基准( MB) */
private heapTrendBaseMB : number = - 1 ;
/** 怪物数量同步计时器(降低同步频率) */
private monsterCountSyncTimer : number = 0 ;
/** 当前回合数(= 当前小节号,驱动 HUD 回合/进度显示) */
@@ -161,8 +153,6 @@ export class MissionComp extends CCComp {
private isBossWave : boolean = false ;
/** 超时回合已在 onBattleTimeout 预扣留存分, BattleEnd 需跳过重复累加 */
private skipRemainScoreOnBattleEnd : boolean = false ;
/** 上节清场时间快照(含清场加速还原,供评分统计) */
private lastWaveClearTime : number = 0 ;
/** 当前任务阶段 */
public currentPhase : MissionPhase = MissionPhase . None ;
/** 是否处于回合间倒计时状态 */
@@ -200,7 +190,6 @@ export class MissionComp extends CCComp {
this . heroViewMatcher = ecs . allOf ( HeroViewComp ) ;
this . skillViewMatcher = ecs . allOf ( SkillView ) ;
this . heroAttrsMatcher = ecs . allOf ( HeroAttrsComp ) ;
this . showMemoryPanel = false
// 注册生命周期事件
this . on ( GameEvent . MissionEnd , this . mission_end , this )
this . on ( GameEvent . NewWave , this . onNewWave , this )
@@ -210,10 +199,9 @@ export class MissionComp extends CCComp {
this . start_btn ? . on ( NodeEventType . TOUCH_END , this . onStartFightBtnClick , this )
// 开局免费三选一选完后自动进入战斗
oops . message . on ( "OpeningPicksDone" , this . onOpeningPicksDone , this ) ;
this . removeMemoryPanel ( )
}
onAdded( args : any ) {
onAdded() {
// 使用 scheduleOnce 将事件推迟到下一帧执行,
// 确保所有关联组件(如 MissionCardComp 等)都已经完成其 onLoad 生命周期。
this . scheduleOnce ( ( ) = > {
@@ -452,7 +440,6 @@ export class MissionComp extends CCComp {
*/
private changePhase ( targetPhase : MissionPhase ) {
if ( this . currentPhase === targetPhase ) return ;
const oldPhase = this . currentPhase ;
this . currentPhase = targetPhase ;
const phaseName = MissionComp . PhaseNameMap [ targetPhase ] || "未知" ;
@@ -553,8 +540,6 @@ export class MissionComp extends CCComp {
// 英雄为真实死亡(实体已销毁),本节死亡数取死亡记录长度
const heroDeathCount = smc . mission . dead_heroes . length ;
// 快照清场时间供评分统计( onNewWave 会清零 clearTime, 必须提前快照)
this . lastWaveClearTime = this . clearTime ;
// 【评分系统 - 战绩分】记录全员存活的胜利节数(额外加分)
if ( hasHero && heroDeathCount === 0 ) {
smc . vmdata . scores . wave_all_alive_count ++ ;
@@ -748,11 +733,11 @@ export class MissionComp extends CCComp {
* - 胜利即 final( 通关) , 存档推进到下一关; 失败即 defeat( 进度不变, 可复活/再来一次)
* - 切入 BattleEnd, 2s 后由 autoNextPhase 自动弹出 VictoryComp
*
* @param e 事件对象(未使用)
* @param is_hero_dead 是否因英雄全灭触发
* @param _ e 事件对象(未使用)
* @param _ is_hero_dead 是否因英雄全灭触发(当前未使用,保留签名以统一事件调用口径)
* @param victory 是否胜利(默认 false)
*/
open_Victory ( e : any , is_hero_dead : boolean = false , victory : boolean = false ) {
open_Victory ( _ e : any , _ is_hero_dead : boolean = false , victory : boolean = false ) {
// 战斗结束(通关/失败),标记暂停状态以切断节间流转逻辑
smc . mission . pause = true ;
this . _victory = victory ;
@@ -828,20 +813,11 @@ export class MissionComp extends CCComp {
this . currentWave = 1 ;
this . isBossWave = false ;
this . skipRemainScoreOnBattleEnd = false ;
this . lastWaveClearTime = 0 ;
this . updateRoundNodes ( ) ;
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 ;
spawningEngine . reset ( ) ;
@@ -941,7 +917,7 @@ export class MissionComp extends CCComp {
* @param event 事件名
* @param data { wave: number, bossWave?: boolean }
*/
private onNewWave ( event : string , data : any ) {
private onNewWave ( _ event : string , data : any ) {
const wave = Number ( data ? . wave ? ? 0 ) ;
if ( wave <= 0 ) return ;
@@ -950,12 +926,46 @@ export class MissionComp extends CCComp {
this . currentWave = wave ;
smc . vmdata . mission_data . level = wave ;
this . updateRoundNodes ( ) ;
// 金币改为怪物死亡掉落(见 HeroAtkSystem) , 不再每回合固定发放
this . lastTimeSecond = - 1 ;
this . clearTime = 0 ;
this . update_time ( ) ;
}
/**
* 刷新回合指示节点颜色:
* - 已激活(已完成 + 当前回合)→ 黄色;当前回合且为 Boss 节 → 红色;未激活 → 白色。
* Why: 回合推进时( onNewWave) 与开局重置( data_init) 调用, 驱动 HUD 回合指示实时反馈。
*/
private updateRoundNodes() {
const len = Math . min ( this . round_nodes . length , SECTIONS_PER_STAGE ) ;
for ( let i = 0 ; i < len ; i ++ ) {
const node = this . round_nodes [ i ] ;
if ( ! node || ! node . isValid ) continue ;
const round = i + 1 ;
const color = round > this . currentWave
? MissionComp . ROUND_INACTIVE
: ( round === this . currentWave && this . isBossWave )
? MissionComp.ROUND_BOSS
: MissionComp.ROUND_ACTIVE ;
// 优先染色 Sprite( 图标配色) , 无 Sprite 时退化到 Label, 均无则跳过
const sprite = node . getComponent ( Sprite ) ;
if ( sprite ) {
sprite . color = color ;
} else {
const label = node . getComponent ( Label ) ;
if ( label ) label . color = color ;
}
}
// 进度条:第 1 回合 0%,每推进一回合 +25%(第 5 回合满格)
if ( this . round_progress && this . round_progress . isValid ) {
this . round_progress . progress =
Math . max ( 0 , Math . min ( SECTIONS_PER_STAGE , this . currentWave ) - 1 ) / ( SECTIONS_PER_STAGE - 1 ) ;
}
}
// ======================== 怪物数量管理 ========================
/**
@@ -1164,115 +1174,6 @@ export class MissionComp extends CCComp {
}
}
/** 获取战斗层的英雄和技能节点数量(用于性能监控) */
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
} ;
}
// ======================== 性能监控面板 ========================
/** 性能监控相关代码 */
/** 初始化性能监控面板:在本组件节点下创建 Label */
private initMemoryPanel() {
if ( ! this . showMemoryPanel ) return ;
let panel = this . node . getChildByName ( "mem_panel" ) ;
if ( ! panel ) {
panel = new Node ( "mem_panel" ) ;
panel . parent = this . 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 . node ? . getChildByName ( "mem_panel" ) ;
if ( panel ) {
panel . destroy ( ) ;
}
this . memoryLabel = null ;
this . lastMemoryText = "" ;
}
/**
* 更新性能监控面板内容(每 0.5 秒一次):
* 显示 堆内存 / 增长趋势 / 帧率 / 实体数量 / 对象池状态 等信息。
*/
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 组件移除时销毁节点 */
reset() {
this . PhaseTime = null as any ;