diff --git a/assets/script/game/hero/HeroViewComp.ts b/assets/script/game/hero/HeroViewComp.ts index 0730afe9..4eff2b3c 100644 --- a/assets/script/game/hero/HeroViewComp.ts +++ b/assets/script/game/hero/HeroViewComp.ts @@ -59,6 +59,7 @@ export class HeroViewComp extends CCComp { }> = []; private isProcessingDamage: boolean = false; private damageInterval: number = 0.01; // 伤害数字显示间隔 + private effectLifeTime: number = 0.8; onLoad() { this.as = this.getComponent(HeroSpine); const collider = this.node.getComponent(BoxCollider2D); @@ -206,28 +207,19 @@ export class HeroViewComp extends CCComp { /** 升级特效 */ private lv_up() { - var path = "game/skill/buff/buff_lvup"; - var prefab: Prefab = oops.res.get(path, Prefab)!; - var node = instantiate(prefab); - node.parent = this.node; + this.spawnEffect("game/skill/buff/buff_lvup", this.node, 1.0); } /** 攻击力提升特效 */ private ap_up() { - var path = "game/skill/buff/buff_apup"; - var prefab: Prefab = oops.res.get(path, Prefab)!; - var node = instantiate(prefab); - node.parent = this.node; + this.spawnEffect("game/skill/buff/buff_apup", this.node, 1.0); } /** 显示 Buff 特效 */ private show_do_buff(name: string) { var path = "game/skill/buff/" + name; - var prefab: Prefab = oops.res.get(path, Prefab)!; - var node = instantiate(prefab); let pos = v3(this.node.position.x, this.node.position.y + 20, this.node.position.z); - node.parent = this.node.parent; - node.setPosition(pos); + this.spawnEffect(path, this.node.parent, 1.0, pos); } @@ -244,24 +236,22 @@ export class HeroViewComp extends CCComp { /** 冰冻特效 */ private in_iced(t: number = 1, ap: number = 0) { - var path = "game/skill/buff/buff_iced"; - var prefab: Prefab = oops.res.get(path, Prefab)!; - var node = instantiate(prefab); - node.getComponent(timedCom).time = t; - node.getComponent(timedCom).ap = ap; - node.parent = this.node; + const node = this.spawnEffect("game/skill/buff/buff_iced", this.node, t); + if (!node) return; + const timer = node.getComponent(timedCom) || node.addComponent(timedCom); + timer.time = t; + timer.ap = ap; } /** 眩晕特效 */ private in_yun(t: number = 1, ap: number = 0) { - var path = "game/skill/buff/buff_yun"; - var prefab: Prefab = oops.res.get(path, Prefab)!; - var node = instantiate(prefab); + const node = this.spawnEffect("game/skill/buff/buff_yun", this.node, t); + if (!node) return; let height = this.node.getComponent(UITransform).height; node.setPosition(v3(0, height)); - node.getComponent(timedCom).time = t; - node.getComponent(timedCom).ap = ap; - node.parent = this.node; + const timer = node.getComponent(timedCom) || node.addComponent(timedCom); + timer.time = t; + timer.ap = ap; } /** 技能提示 */ @@ -294,39 +284,42 @@ export class HeroViewComp extends CCComp { public palayBuff(anm: string = ""){ if(anm==="") return; var path = "game/skill/buff/" + anm; - var prefab: Prefab = oops.res.get(path, Prefab)!; - var node = instantiate(prefab); - node.parent = this.node; + this.spawnEffect(path, this.node, this.effectLifeTime); } public playReady(anm: string = ""){ if(anm==="") return; var path = "game/skill/ready/" + anm; - var prefab: Prefab = oops.res.get(path, Prefab)!; - var node = instantiate(prefab); - node.parent = this.node; + this.spawnEffect(path, this.node, this.effectLifeTime); } public playEnd(anm: string = ""){ if(anm==="") return; var path = "game/skill/end/" + anm; - var prefab: Prefab = oops.res.get(path, Prefab)!; - var node = instantiate(prefab); - node.parent = this.node; + this.spawnEffect(path, this.node, this.effectLifeTime); } /** 治疗特效 */ private heathed() { - var path = "game/skill/buff/heathed"; - var prefab: Prefab = oops.res.get(path, Prefab)!; - var node = instantiate(prefab); - node.parent = this.node; + this.spawnEffect("game/skill/buff/heathed", this.node, 1.0); } private deaded(){ - var path = "game/skill/end/atked"; - var prefab: Prefab = oops.res.get(path, Prefab)!; - var node = instantiate(prefab); - node.parent = this.node; + this.spawnEffect("game/skill/end/atked", this.node, this.effectLifeTime); + } + private spawnEffect(path: string, parent: Node | null, life: number = 0.8, worldPos?: Vec3): Node | null { + if (!parent || !parent.isValid) return null; + const prefab: Prefab = oops.res.get(path, Prefab)!; + if (!prefab) return null; + const node = instantiate(prefab); + if (!node || !node.isValid) return null; + node.parent = parent; + if (worldPos) { + node.setWorldPosition(worldPos); + } + const timer = node.getComponent(timedCom) || node.addComponent(timedCom); + timer.time = Math.max(0.2, life); + timer.ap = 0; + return node; } // 注意:BaseUp 逻辑已移到 HeroAttrSystem.update() // 注意:updateTemporaryBuffsDebuffs 逻辑已移到 HeroAttrSystem.update() diff --git a/assets/script/game/hero/Mon.ts b/assets/script/game/hero/Mon.ts index e1843d9e..27018e06 100644 --- a/assets/script/game/hero/Mon.ts +++ b/assets/script/game/hero/Mon.ts @@ -19,7 +19,15 @@ export class Monster extends ecs.Entity { // 多键对象池:Map static pools: Map = new Map(); - static readonly MAX_POOL_SIZE: number = 24; + static readonly MAX_POOL_SIZE: number = 12; + static readonly MAX_POOL_TOTAL: number = 60; + private static totalPoolSize(): number { + let total = 0; + this.pools.forEach((pool) => { + total += pool.size(); + }); + return total; + } static getFromPool(path: string): Node | null { if (this.pools.has(path)) { @@ -40,7 +48,7 @@ export class Monster extends ecs.Entity { this.pools.set(path, new NodePool()); } const pool = this.pools.get(path)!; - if (pool.size() >= this.MAX_POOL_SIZE) { + if (pool.size() >= this.MAX_POOL_SIZE || this.totalPoolSize() >= this.MAX_POOL_TOTAL) { node.destroy(); return; } @@ -49,6 +57,12 @@ export class Monster extends ecs.Entity { static clearPools() { this.pools.forEach((pool) => { + while (pool.size() > 0) { + const node = pool.get(); + if (node && node.isValid) { + node.destroy(); + } + } pool.clear(); }); this.pools.clear(); @@ -62,7 +76,8 @@ export class Monster extends ecs.Entity { return { paths: this.pools.size, total, - maxPerPath: this.MAX_POOL_SIZE + maxPerPath: this.MAX_POOL_SIZE, + maxTotal: this.MAX_POOL_TOTAL }; } diff --git a/assets/script/game/hero/MoveComp.ts b/assets/script/game/hero/MoveComp.ts index 69953770..27f4fd4e 100644 --- a/assets/script/game/hero/MoveComp.ts +++ b/assets/script/game/hero/MoveComp.ts @@ -44,7 +44,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate private readonly attackPassThresholdX = 60; private readonly minSpacingY = 30; private readonly renderSortInterval = 0.05; - private renderSortElapsed = 0; + private lastRenderSortAt = 0; private heroMoveMatcher: ecs.IMatcher | null = null; private heroViewMatcher: ecs.IMatcher | null = null; private readonly renderEntries: { node: Node; bossPriority: number; frontScore: number; spawnOrder: number; eid: number }[] = []; @@ -405,7 +405,6 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate } private updateRenderOrder(view: HeroViewComp) { - this.renderSortElapsed += this.dt; const scene = smc.map?.MapView?.scene; const actorRoot = scene?.entityLayer?.node?.getChildByName("HERO"); if (!actorRoot) return; @@ -414,8 +413,9 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate view.node.parent = actorRoot; } - if (this.renderSortElapsed < this.renderSortInterval) return; - this.renderSortElapsed = 0; + const now = Date.now() / 1000; + if (now - this.lastRenderSortAt < this.renderSortInterval) return; + this.lastRenderSortAt = now; this.renderEntryCount = 0; ecs.query(this.getHeroMoveMatcher()).forEach(e => { diff --git a/assets/script/game/hero/SCastSystem.ts b/assets/script/game/hero/SCastSystem.ts index 4894f054..d1901834 100644 --- a/assets/script/game/hero/SCastSystem.ts +++ b/assets/script/game/hero/SCastSystem.ts @@ -25,9 +25,16 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate debugMode: boolean = false; // 是否启用调试模式 private readonly emptyCastPlan = { skillId: 0, isFriendly: false, targetPos: null as Vec3 | null, targetEids: [] as number[] }; private readonly meleeCastRange = 64; + private heroMatcher: ecs.IMatcher | null = null; + private getHeroMatcher(): ecs.IMatcher { + if (!this.heroMatcher) { + this.heroMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp); + } + return this.heroMatcher; + } filter(): ecs.IMatcher { - return ecs.allOf(HeroAttrsComp, HeroViewComp); + return this.getHeroMatcher(); } update(e: ecs.Entity): void { @@ -181,7 +188,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate const currentX = heroView.node.position.x; let nearest: HeroViewComp | null = null; let minDist = Infinity; - ecs.query(this.filter()).forEach(entity => { + ecs.query(this.getHeroMatcher()).forEach(entity => { const attrs = entity.get(HeroAttrsComp); const view = entity.get(HeroViewComp); if (!attrs || !view?.node) return; diff --git a/assets/script/game/map/MissionComp.ts b/assets/script/game/map/MissionComp.ts index 32d5b5a2..b678775b 100644 --- a/assets/script/game/map/MissionComp.ts +++ b/assets/script/game/map/MissionComp.ts @@ -12,6 +12,7 @@ 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; @@ -184,7 +185,10 @@ export class MissionComp extends CCComp { 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 @@ -255,13 +259,18 @@ export class MissionComp extends CCComp { } private cleanComponents() { - // 优化销毁顺序:直接销毁实体,让ECS系统自动处理组件清理 - // 这样可以避免在组件reset方法中访问已经被销毁的实体引用 + 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(); }); } @@ -269,6 +278,38 @@ export class MissionComp extends CCComp { 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 + }; } /** 性能监控相关代码 */ @@ -308,6 +349,8 @@ export class MissionComp extends CCComp { }); 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; @@ -338,8 +381,8 @@ export class MissionComp extends CCComp { `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})`; + `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; diff --git a/assets/script/game/skill/Skill.ts b/assets/script/game/skill/Skill.ts index e0e60543..b7548ca1 100644 --- a/assets/script/game/skill/Skill.ts +++ b/assets/script/game/skill/Skill.ts @@ -19,7 +19,15 @@ export class Skill extends ecs.Entity { private debugMode: boolean = false; /** 多键对象池:Map */ static pools: Map = new Map(); - static readonly MAX_POOL_SIZE: number = 128; + static readonly MAX_POOL_SIZE: number = 48; + static readonly MAX_POOL_TOTAL: number = 192; + private static totalPoolSize(): number { + let total = 0; + this.pools.forEach((pool) => { + total += pool.size(); + }); + return total; + } static getFromPool(path: string): Node | null { if (this.pools.has(path)) { @@ -40,7 +48,7 @@ export class Skill extends ecs.Entity { this.pools.set(path, new NodePool()); } const pool = this.pools.get(path)!; - if (pool.size() >= this.MAX_POOL_SIZE) { + if (pool.size() >= this.MAX_POOL_SIZE || this.totalPoolSize() >= this.MAX_POOL_TOTAL) { node.destroy(); return; } @@ -49,6 +57,12 @@ export class Skill extends ecs.Entity { static clearPools() { this.pools.forEach((pool) => { + while (pool.size() > 0) { + const node = pool.get(); + if (node && node.isValid) { + node.destroy(); + } + } pool.clear(); }); this.pools.clear(); @@ -62,7 +76,8 @@ export class Skill extends ecs.Entity { return { paths: this.pools.size, total, - maxPerPath: this.MAX_POOL_SIZE + maxPerPath: this.MAX_POOL_SIZE, + maxTotal: this.MAX_POOL_TOTAL }; } @@ -229,6 +244,7 @@ export class Skill extends ecs.Entity { Skill.putToPool(this.prefabPath, this.skillNode); this.skillNode = null; } + this.prefabPath = ""; super.destroy(); diff --git a/assets/script/game/skill/Tooltip.ts b/assets/script/game/skill/Tooltip.ts index a3828d15..cac8743c 100644 --- a/assets/script/game/skill/Tooltip.ts +++ b/assets/script/game/skill/Tooltip.ts @@ -11,11 +11,34 @@ import { instantiate, Node, Prefab, Vec3 ,tween, v3,animation,Label,resources,Sp export class Tooltip extends ecs.Entity { /** 对象池 */ static pool: NodePool = new NodePool(); + static readonly MAX_POOL_SIZE: number = 80; /** 回收节点 */ static put(node: Node) { + if (!node || !node.isValid) return; + if (this.pool.size() >= this.MAX_POOL_SIZE) { + node.destroy(); + return; + } + node.active = false; + node.parent = null; this.pool.put(node); } + static clearPool() { + while (this.pool.size() > 0) { + const node = this.pool.get(); + if (node && node.isValid) { + node.destroy(); + } + } + this.pool.clear(); + } + static getPoolStats() { + return { + total: this.pool.size(), + max: this.MAX_POOL_SIZE + }; + } /** ---------- 数据层 ---------- */ // SkillModel!: SkillModelComp;