Files
pixelheros/assets/script/game/skill/Skill.ts
panw eff4154ba3 perf: 增加技能对象池最大容量并优化伤害日志
将技能对象池最大容量从64提升至128,以支持更多并发技能实例。
将技能6008的结束类型从动画结束改为碰撞检测,提高准确性。
移除伤害计算中不必要的施法者属性获取和击杀计数更新,简化日志输出。
2026-03-18 10:37:25 +08:00

242 lines
8.4 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 { BoxCollider2D, instantiate, Node, Prefab, v3, Vec3, NodePool } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { EType, SkillSet } from "../common/config/SkillSet";
import { oops } from "db://oops-framework/core/Oops";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { Attrs } from "../common/config/HeroAttrs";
import { SkillView } from "./SkillView";
import { SDataCom } from "./SDataCom";
import { SMoveDataComp } from "../skill/SMoveComp";
import { StimeDataComp } from "./STimeComp";
import { HeroViewComp } from "../hero/HeroViewComp";
import { smc } from "../common/SingletonModuleComp";
import { mLogger } from "../common/Logger";
/** Skill 模块 */
@ecs.register(`Skill`)
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 getFromPool(path: string): Node | null {
if (this.pools.has(path)) {
const pool = this.pools.get(path)!;
while (pool.size() > 0) {
const node = pool.get();
if (node && node.isValid) {
return node;
}
}
}
return null;
}
static putToPool(path: string, node: Node) {
if (!node || !node.isValid) return;
if (!this.pools.has(path)) {
this.pools.set(path, new NodePool());
}
const pool = this.pools.get(path)!;
if (pool.size() >= this.MAX_POOL_SIZE) {
node.destroy();
return;
}
pool.put(node);
}
static clearPools() {
this.pools.forEach((pool) => {
pool.clear();
});
this.pools.clear();
}
static getPoolStats() {
let total = 0;
this.pools.forEach((pool) => {
total += pool.size();
});
return {
paths: this.pools.size,
total,
maxPerPath: this.MAX_POOL_SIZE
};
}
/** ---------- 数据层 ---------- */
SDataCom!: SDataCom;
SMoveCom!: SMoveDataComp
/** 当前技能的预制体路径(用于对象池回收) */
private prefabPath: string = "";
private skillNode: Node | null = null; // 持有节点引用以便销毁时回收
/** ---------- 业务层 ---------- */
// SkillBll!: SkillBllComp;
/** ---------- 视图层 ---------- */
SView!: SkillView;
/** 实始添加的数据层组件 */
protected init() {
this.addComponents<SDataCom>(SDataCom);
this.addComponents<SMoveDataComp>(SMoveDataComp);
}
load(startPos: Vec3, parent: Node, s_uuid: number, targetPos: Vec3,
caster:HeroViewComp,ext_dmg:number=0) {
const config = SkillSet[s_uuid];
if (!config) {
mLogger.error(this.debugMode, 'Skill', "[Skill] 技能配置不存在:", s_uuid);
return;
}
// 加载预制体
const path = `game/skill/atk/${config.sp_name}`;
const prefab:Prefab = oops.res.get(path, Prefab);
if (!prefab) {
mLogger.error(this.debugMode, 'Skill', "[Skill] 预制体加载失败:", path);
return;
}
const node: Node = Skill.getFromPool(path) || instantiate(prefab);
if (!node || !node.isValid) {
mLogger.error(this.debugMode, 'Skill', "[Skill] 节点无效:", path);
return;
}
this.prefabPath = path;
this.skillNode = node;
let skillParent: Node | null = null;
if (smc.map && smc.map.MapView && smc.map.MapView.scene && smc.map.MapView.scene.entityLayer && smc.map.MapView.scene.entityLayer.node) {
skillParent = smc.map.MapView.scene.entityLayer.node.getChildByName("SKILL");
}
if (!skillParent || !skillParent.isValid) {
skillParent = parent;
}
if (!skillParent || !skillParent.isValid) {
mLogger.error(this.debugMode, 'Skill', "[Skill] 父节点无效");
if(node.isValid) node.destroy();
return;
}
node.parent = skillParent;
node.active = true;
// 设置节点属性
let face=caster.node.scale.x < 0 ? -1 : 1
node.setScale(v3(node.scale.x*face,node.scale.y,1))
// 初始视图
const SView = node.getComponent(SkillView);
if (!SView) {
mLogger.error(this.debugMode, 'Skill', "[Skill] SkillView 组件缺失:", path);
if (node.isValid) node.destroy();
return;
}
if(config.EType!=EType.collision){
const collider=node.getComponent(BoxCollider2D);
if(collider){
collider.enabled=false
}
}
// 只设置必要的运行时属性,配置信息通过 SkillSet[uuid] 访问
// 核心标识
SView.s_uuid= s_uuid
SView.group= caster.box_group
this.add(SView);
startPos.x=startPos.x+SView.atk_x*face
startPos.y=startPos.y+SView.atk_y
node.setPosition(startPos);
// 初始化移动组件 - 从SkillView获取移动参数
let sMoveCom = this.get(SMoveDataComp);
if (!sMoveCom) {
sMoveCom = this.add(SMoveDataComp);
}
sMoveCom.reset(); // 复用组件时重置状态
sMoveCom.startPos.set(startPos);
sMoveCom.targetPos.set(targetPos);
sMoveCom.s_uuid = s_uuid;
sMoveCom.scale = caster.node.scale.x < 0 ? -1 : 1;
sMoveCom.runType = config.RType;
sMoveCom.endType = config.EType;
// 从SkillView获取移动参数位置初始化由SMoveSystem统一处理
sMoveCom.atk_x = SView.atk_x;
sMoveCom.atk_y = SView.atk_y;
if (config.EType === EType.timeEnd) {
let sTimeCom = this.get(StimeDataComp);
if (!sTimeCom) sTimeCom = this.add(StimeDataComp);
sTimeCom.reset();
sTimeCom.s_uuid = s_uuid;
sTimeCom.totalTime = Math.max(1, config.time ?? 0);
sTimeCom.hitInterval = Math.max(0.5, config.hitcd || 0);
} else {
const sTimeCom = this.get(StimeDataComp);
if (sTimeCom) this.remove(StimeDataComp);
}
let cAttrsComp=caster.ent.get(HeroAttrsComp)
// 初始化数据组件
let sDataCom = this.get(SDataCom);
if (!sDataCom) {
sDataCom = this.add(SDataCom);
}
sDataCom.reset();
sDataCom.group=caster.box_group
sDataCom.casterEid=caster.ent.eid
sDataCom.Attrs = {};
const addCrt = config.crt ?? 0;
const addFrz = config.frz ?? 0;
const addStn = config.stn ?? 0;
const addBck = config.bck ?? 0;
const addSlw = config.slw ?? 0;
const addPct = config.pct ?? 0;
const totalPuncture = cAttrsComp.puncture + addPct;
sDataCom.Attrs[Attrs.ap] = cAttrsComp.ap;
sDataCom.Attrs[Attrs.critical] = cAttrsComp.critical + addCrt;
sDataCom.Attrs[Attrs.critical_dmg] = cAttrsComp.critical_dmg;
sDataCom.Attrs[Attrs.freeze_chance] = cAttrsComp.freeze_chance + addFrz;
sDataCom.Attrs[Attrs.stun_chance] = cAttrsComp.stun_chance + addStn;
sDataCom.Attrs[Attrs.back_chance] = cAttrsComp.back_chance + addBck;
sDataCom.Attrs[Attrs.slow_chance] = cAttrsComp.slow_chance + addSlw;
sDataCom.Attrs[Attrs.puncture] = totalPuncture;
sDataCom.Attrs[Attrs.puncture_dmg] = cAttrsComp.puncture_dmg;
sDataCom.s_uuid=s_uuid
sDataCom.fac=cAttrsComp.fac
sDataCom.ext_dmg=ext_dmg
sDataCom.hit_count = 0
sDataCom.max_hit_count = Math.max(1, config.hit_count + totalPuncture)
SView.init();
}
/** 模块资源释放 */
destroy() {
// 注: 自定义释放逻辑,视图层实现 ecs.IComp 接口的 ecs 组件需要手动释放
this.remove(SDataCom);
this.remove(SMoveDataComp)
this.remove(StimeDataComp)
// 移除组件会触发 reset但我们在 reset 中移除了 destroy
this.remove(SkillView)
// 回收节点到对象池
if (this.skillNode && this.skillNode.isValid && this.prefabPath) {
Skill.putToPool(this.prefabPath, this.skillNode);
this.skillNode = null;
}
super.destroy();
}
}