fix: 收敛战斗内存增长并强化战斗结束清理
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -19,7 +19,15 @@ export class Monster extends ecs.Entity {
|
||||
|
||||
// 多键对象池:Map<prefabPath, NodePool>
|
||||
static pools: Map<string, NodePool> = 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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -19,7 +19,15 @@ export class Skill extends ecs.Entity {
|
||||
private debugMode: boolean = false;
|
||||
/** 多键对象池:Map<prefabPath, NodePool> */
|
||||
static pools: Map<string, NodePool> = 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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user