- 为 Monster 和 Hero 实体添加 idle 状态初始化,确保视图组件正确显示 - 调用 Hero 视图组件的 init 方法以完成初始化流程 - 调整关卡配置参数:关闭内存面板显示,减少怪物上限和恢复阈值以优化性能
399 lines
14 KiB
TypeScript
399 lines
14 KiB
TypeScript
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)
|
||
coins_node:Node = null!
|
||
@property(Node)
|
||
lv_node:Node = null!
|
||
@property(Node)
|
||
chou_node:Node = null!
|
||
@property(Node)
|
||
time_node:Node = null!
|
||
@property(Node)
|
||
hero_info_node:Node = null!
|
||
@property(Node)
|
||
update_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.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.initMemoryPanel()
|
||
}
|
||
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();
|
||
this.updateMemoryPanel(dt);
|
||
}
|
||
}
|
||
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 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();
|
||
}
|
||
}
|