- 新增 `maxMonsterCount` 和 `resumeMonsterCount` 属性配置怪物数量阈值 - 添加 `stop_spawn_mon` 状态控制刷怪暂停与恢复 - 实现 `syncMonsterSpawnState` 方法周期性同步怪物数量并自动管理刷怪状态 - 在战斗准备时重置刷怪状态,确保每局开始时刷怪正常
356 lines
13 KiB
TypeScript
356 lines
13 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";
|
||
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 = true;
|
||
@property({ tooltip: "场上怪物上限" })
|
||
private maxMonsterCount: number = 15;
|
||
@property({ tooltip: "恢复刷怪阈值" })
|
||
private resumeMonsterCount: number = 10;
|
||
|
||
// 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 逻辑:清理组件、停止游戏循环
|
||
smc.mission.play=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() {
|
||
// 优化销毁顺序:直接销毁实体,让ECS系统自动处理组件清理
|
||
// 这样可以避免在组件reset方法中访问已经被销毁的实体引用
|
||
ecs.query(this.heroViewMatcher).forEach(entity => {
|
||
entity.destroy();
|
||
});
|
||
|
||
ecs.query(this.skillViewMatcher).forEach(entity => {
|
||
entity.destroy();
|
||
});
|
||
}
|
||
|
||
private clearBattlePools() {
|
||
Monster.clearPools();
|
||
Skill.clearPools();
|
||
}
|
||
|
||
/** 性能监控相关代码 */
|
||
|
||
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 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` +
|
||
`Pool M:${monPool.total}(${monPool.paths}) K:${skillPool.total}(${skillPool.paths})`;
|
||
if (text === this.lastMemoryText) return;
|
||
this.lastMemoryText = text;
|
||
this.memoryLabel.string = text;
|
||
}
|
||
|
||
|
||
|
||
|
||
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
||
reset() {
|
||
this.node.destroy();
|
||
}
|
||
}
|