import { BoxCollider2D, instantiate, Node, Prefab, v3, Vec3, NodePool } from "cc"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { EType, SkillSet, SkillUpList } 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 */ static pools: Map = new Map(); 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)) { 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 || this.totalPoolSize() >= this.MAX_POOL_TOTAL) { node.destroy(); return; } pool.put(node); } 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(); } static getPoolStats() { let total = 0; this.pools.forEach((pool) => { total += pool.size(); }); return { paths: this.pools.size, total, maxPerPath: this.MAX_POOL_SIZE, maxTotal: this.MAX_POOL_TOTAL }; } /** ---------- 数据层 ---------- */ SDataCom!: SDataCom; SMoveCom!: SMoveDataComp /** 当前技能的预制体路径(用于对象池回收) */ private prefabPath: string = ""; private skillNode: Node | null = null; // 持有节点引用以便销毁时回收 /** ---------- 业务层 ---------- */ // SkillBll!: SkillBllComp; /** ---------- 视图层 ---------- */ SView!: SkillView; /** 实始添加的数据层组件 */ protected init() { this.addComponents(SDataCom); this.addComponents(SMoveDataComp); } load(startPos: Vec3, parent: Node, s_uuid: number, targetPos: Vec3, caster:HeroViewComp,cAttrsComp:HeroAttrsComp, skill_lv:number=0, 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 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 SUp=SkillUpList[s_uuid] ? SkillUpList[s_uuid]:SkillUpList[1001]; const sCrt = (config.crt ?? 0)+(SUp.crt*skill_lv); const sFrz = (config.frz ?? 0)+(SUp.frz*skill_lv); const sBck = (config.bck ?? 0)+(SUp.bck*skill_lv); const sAp =config.ap+(SUp.ap*skill_lv); const sHit=config.hit_count+(SUp.hit_count*skill_lv) + cAttrsComp.puncture sDataCom.Attrs[Attrs.ap] = Math.floor(cAttrsComp.ap*sAp/100); //技能的ap是百分值 需要/100 而且需要再最终计算总ap时再/100,不然会出现ap为90%变0 sDataCom.Attrs[Attrs.critical] = cAttrsComp.critical + sCrt; sDataCom.Attrs[Attrs.freeze_chance] = cAttrsComp.freeze_chance + sFrz; sDataCom.Attrs[Attrs.back_chance] = cAttrsComp.back_chance + sBck; sDataCom.s_uuid=s_uuid sDataCom.skill_lv = Math.max(0, skill_lv); sDataCom.fac=cAttrsComp.fac sDataCom.ext_dmg=ext_dmg sDataCom.hit_count = 0 sDataCom.max_hit_count = Math.max(1,sHit) 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; } this.prefabPath = ""; super.destroy(); } }