import { _decorator, Animation, CCInteger, Collider2D, Contact2DType, UITransform, v3, Vec3 } 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 { HeroViewComp } from "../hero/HeroViewComp"; import { BuffsList, DTType, EType, RType, SkillConfig, SkillSet } from "../common/config/SkillSet"; import { SDataCom } from "./SDataCom"; import { HeroAttrsComp } from "../hero/HeroAttrsComp"; import { DamageQueueHelper } from "../hero/DamageQueueComp"; import { mLogger } from "../common/Logger"; const { ccclass, property } = _decorator; /** 技能视图组件:负责碰撞窗口、碰撞回调与伤害派发 */ @ccclass('SkillView') @ecs.register('SkillView', false) export class SkillView extends CCComp { /** 视图层逻辑代码分离演示 */ @property({ type: CCInteger }) atk_x: number = 0 @property({ type: CCInteger }) atk_y: number = 0 @property({ tooltip: "是否启用调试日志" }) private debugMode: boolean = false; anim:Animation=null; group:number=0; SConf:SkillConfig=null; sData:SDataCom=null; s_uuid:number=1001 private collider: Collider2D = null; // 缓存碰撞体引用 private pendingDisableCollider: boolean = false; private isDisposing: boolean = false; // 生命周期保护标记,避免在销毁阶段重复处理碰撞 init() { this.SConf = SkillSet[this.s_uuid] this.sData = this.ent.get(SDataCom) this.anim = this.node.getComponent(Animation) this.node.active = true; this.pendingDisableCollider = false; this.isDisposing = false; this.collider = this.getComponent(Collider2D); if(this.collider) { this.collider.group = this.group; this.collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); this.collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); this.collider.enabled = this.SConf?.EType === EType.collision; } if(this.node.getComponent(Animation)){ let anim = this.node.getComponent(Animation); mLogger.log(this.debugMode, 'SkillView', "[SkillCom]:has anim",anim) anim.off(Animation.EventType.FINISHED, this.onAnimationFinished, this); anim.on(Animation.EventType.FINISHED, this.onAnimationFinished, this); // 对象池复用时,需要手动播放默认动画(因为 Play On Load 只在首次生效) if (anim.defaultClip) { anim.play(anim.defaultClip.name); } } } onBeginContact (seCol: Collider2D, oCol: Collider2D) { if (!this.sData || !this.SConf) { mLogger.warn(this.debugMode, 'SkillView', '[SkillView] onBeginContact 缺少 sData 或 SConf,忽略此次碰撞'); return; } if (this.isDisposing) return; if (!this.node || !this.node.activeInHierarchy) return; const targetView = oCol.getComponent(HeroViewComp); if (this.debugMode) { const casterEid = this.sData.casterEid; const targetName = targetView?.ent?.get(HeroAttrsComp)?.hero_name ?? '非英雄对象'; const targetEid = targetView?.ent?.eid ?? '未知EID'; mLogger.log(this.debugMode, 'SkillView', `[skillView] 碰撞1 [${this.sData.group}][eid:${casterEid}]的[${seCol.group}]:[${this.SConf.name}][${this.ent.eid}]碰撞了 [${oCol.group}]:[${targetName}][${targetEid}]`); } if (oCol.group === seCol.group) return; if (this.pendingDisableCollider) return; if (this.sData.hit_count >= this.sData.max_hit_count) { this.handle_collision_limit(); return; } this.sData.hit_count++; if (this.sData.hit_count >= this.sData.max_hit_count) { this.handle_collision_limit(); } // 命中次数按碰撞事件统计:不依赖是否最终造成伤害 // 不是 HeroViewComp,直接忽略 if (!targetView) return; // 🔥 方案A:防御性检查 - 在获取model前强制检查ent是否存在 if (!targetView.ent) { mLogger.warn(this.debugMode, 'SkillView', '[SkillView] onBeginContact targetView.ent为空,实体已销毁,忽略此次碰撞'); return; } let model = targetView.ent.get(HeroAttrsComp); if (this.debugMode) { mLogger.log(this.debugMode, 'SkillView', `[skillView] 碰撞3`, oCol.group, seCol.group, model); } if (!model) return; if (model.is_dead) return; if (this.sData.fac == model.fac) return; // 检查是否已经命中过这个目标(日志安全输出) this.apply_damage(targetView) } onAnimationFinished(){ // animationEnd 类型:动画结束后再关闭碰撞并销毁 if(this.SConf.EType==EType.animationEnd){ this.disable_collider_now(); this.ent.destroy() } } // 动画帧事件 atk:仅负责开启一帧碰撞窗口,不负责伤害与计数 public atk(args:any){ if(!this.SConf) return; if(this.SConf.EType==EType.collision) return if (this.enable_collider_safely()) { mLogger.log(this.debugMode, 'SkillView', `[SkillView] [${this.SConf?.name}] 开启碰撞检测`); this.scheduleOnce(() => { if (!this.node || !this.node.isValid || this.isDisposing) return; this.close_collider(); mLogger.log(this.debugMode, 'SkillView', `[SkillView] [${this.SConf?.name}] 关闭碰撞检测`); }, 0); } } // 仅负责伤害派发,不处理命中次数与实体销毁 apply_damage(target:HeroViewComp,is_range:boolean=false){ if(target == null) return; // 安全检查:如果目标实体已不存在,直接返回 if (!target.ent) return; if (!this.sData) return; if (!this.SConf) return; if (this.debugMode) { const targetName = target.ent.get(HeroAttrsComp)?.hero_name ?? "未知目标"; mLogger.log(this.debugMode, 'SkillView', `[skillView] 伤害 [${this.group}][eid:${this.sData.casterEid}]的 [${this.SConf.name}]对 [${target.box_group}][${targetName}][${target.ent.eid}]`); } // 使用伤害队列系统处理伤害 DamageQueueHelper.addDamageToEntity( target.ent, this.sData.Attrs, this.sData.casterEid, this.sData.s_uuid, this.sData.ext_dmg, this.sData.dmg_ratio, ); } close_collider(){ if (!this.collider) return; if (this.pendingDisableCollider && !this.collider.enabled) return; this.pendingDisableCollider = true; if (this.collider.isValid) { this.collider.enabled = false; } this.pendingDisableCollider = false; } private disable_collider_now() { this.isDisposing = true; this.close_collider(); } // 碰撞上限收口:先关碰撞;collision 类型再延迟销毁实体 private handle_collision_limit() { this.close_collider(); if (this.SConf?.EType !== EType.collision) return; if (this.isDisposing) return; this.isDisposing = true; this.scheduleOnce(() => { if (this.ent) { this.ent.destroy(); } }, 0); } private enable_collider_safely(): boolean { if (!this.collider || !this.collider.isValid) return false; if (this.isDisposing) return false; if (!this.node || !this.node.isValid || !this.node.activeInHierarchy) return false; this.pendingDisableCollider = false; this.collider.group = this.group; this.collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); this.collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); this.collider.enabled = true; return true; } /** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */ reset() { // 清理碰撞体事件监听 if (this.collider) { this.collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); this.collider.enabled = false; } this.pendingDisableCollider = false; this.isDisposing = false; if (this.anim) { this.anim.off(Animation.EventType.FINISHED, this.onAnimationFinished, this); } // 取消所有定时器 this.unscheduleAllCallbacks(); if (this.node && this.node.isValid) { this.node.active = false; } } }