Files
pixelheros/assets/script/game/map/MissionComp.ts
walkpan d6a4cfe07e fix: 修复实体视图状态初始化问题并调整关卡参数
- 为 Monster 和 Hero 实体添加 idle 状态初始化,确保视图组件正确显示
- 调用 Hero 视图组件的 init 方法以完成初始化流程
- 调整关卡配置参数:关闭内存面板显示,减少怪物上限和恢复阈值以优化性能
2026-03-24 19:41:39 +08:00

399 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
}