Files
heros/assets/script/game/skill/SkillView.ts
panw 1a45e91f1a feat(skill): 优化技能碰撞检测逻辑并添加攻击帧计数
- 在SkillView中缓存碰撞体引用并添加攻击帧计数器
- 实现攻击帧事件中动态开启碰撞检测
- 非持续碰撞类型技能在造成伤害后立即关闭碰撞检测
- 清理资源时取消所有定时器
- 调整技能预制体碰撞体位置和大小
- 注释掉Main.ts中的物理调试绘制代码
2025-11-03 14:47:53 +08:00

176 lines
7.7 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 { _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 { DTType, EType, RType, SkillConfig, SkillSet } from "../common/config/SkillSet";
import { SDataCom } from "./SDataCom";
import { Attrs } from "../common/config/HeroAttrs";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { DamageQueueHelper } from "../hero/DamageQueueComp";
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
anim:Animation=null;
group:number=0;
SConf:SkillConfig=null;
sData:SDataCom=null;
s_uuid:number=1001
private collider: Collider2D = null; // 缓存碰撞体引用
private attackFrameCount: number = 0; // 攻击帧计数器
private maxAttackFrames: number = 1; // 最大攻击帧数,可配置
// 已命中目标追踪,防止重复伤害
start() {
this.SConf = SkillSet[this.s_uuid]
this.sData=this.ent.get(SDataCom)
this.anim=this.node.getComponent(Animation)
this.node.active = true;
this.collider = this.getComponent(Collider2D);
if(this.collider) {
this.collider.group = this.group;
this.collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
}
if(this.node.getComponent(Animation)){
let anim = this.node.getComponent(Animation);
//console.log("[SkillCom]:has anim",anim)
anim.on(Animation.EventType.FINISHED, this.onAnimationFinished, this);
}
}
onBeginContact (seCol: Collider2D, oCol: Collider2D) {
// 安全获取双方信息用于日志
const casterName = this.sData.caster?.ent?.get(HeroAttrsComp)?.hero_name ?? '未知施法者';
const casterEid = this.sData.caster?.ent?.eid ?? '未知EID';
const targetView = oCol.getComponent(HeroViewComp);
const targetName = targetView?.ent?.get(HeroAttrsComp)?.hero_name ?? '非英雄对象';
const targetEid = targetView?.ent?.eid ?? '未知EID';
console.log(`[skillView] 碰撞1 [${this.sData.caster.box_group}][${casterName}][${casterEid}]的[${seCol.group}]:[${this.SConf.name}][${this.ent.eid}]碰撞了 [${oCol.group}]:[ ${targetName}][${targetEid}]`);
// 基本空值与同组过滤
if (!this.sData || !this.SConf) {
console.warn('[SkillView] onBeginContact 缺少 sData 或 SConf忽略此次碰撞');
return;
}
if (oCol.group === seCol.group) return;
// 不是 HeroViewComp直接忽略
if (!targetView) return;
let model = targetView.ent.get(HeroAttrsComp);
// console.log(`[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(){
if(this.SConf.EType==EType.animationEnd){
this.ent.destroy()
}
}
// //动画帧事件 atk 触发
public atk(args:any){
this.attackFrameCount++;
// 开启碰撞检测
if(this.collider) {
this.collider.enabled = true;
console.log(`[SkillView] [${this.SConf?.name}] 第${this.attackFrameCount}次攻击帧开启碰撞检测`);
}
// let dis=this.node.getComponent(UITransform).width/2
// let enemys:any=[]
// if( this.sData.fac==FacSet.HERO){
// enemys=ecs.query(ecs.allOf(MonMoveComp))
// }else{
// enemys=ecs.query(ecs.allOf(HeroMoveComp))
// }
// let IRTargets: HeroViewComp[] = []
// // 收集范围内所有敌方目标
// enemys.some(e => {
// const view = e.get(HeroViewComp);
// const model=e.get(HeroAttrsComp)
// const distance = Math.abs(this.node.position.x - view.node.position.x);
// if(distance <= dis&&!model.is_dead) {
// IRTargets.push(view);
// }
// });
// // 根据配置的hit_num决定攻击模式
// const hitNum = SkillSet[this.s_uuid].hit_num || 0;
// if(hitNum > 0) {
// // 限制目标数量按距离排序选择最近的N个目标
// if(IRTargets.length > 0) {
// // 按距离排序(从近到远)
// IRTargets.sort((a, b) => {
// const distanceA = Math.abs(this.node.position.x - a.node.position.x);
// const distanceB = Math.abs(this.node.position.x - b.node.position.x);
// return distanceA - distanceB;
// });
// // 限制目标数量
// const maxTargets = Math.min(hitNum, IRTargets.length);
// const sTargets = IRTargets.slice(0, maxTargets);
// sTargets.forEach(target => {
// this.apply_damage(target, false);
// });
// }
// } else {
// // 范围伤害:对所有范围内目标造成伤害
// if(IRTargets.length > 0) {
// IRTargets.forEach(target => {
// this.apply_damage(target, true);
// });
// }
// }
}
//伤害应用
apply_damage(target:HeroViewComp,is_range:boolean=false){
if(target == null) return;
if (!this.SConf) return;
// 对于非持续碰撞类型的技能,在造成伤害后立即关闭碰撞检测
// 这样可以避免同一帧内的重复伤害
if(this.SConf.EType !== EType.collision && this.collider) {
this.collider.enabled = false;
console.log(`[SkillView] [${this.SConf.name}] 伤害后关闭碰撞检测`);
}
// console.log(`[skillView] 伤害 [${this.group}][${this.sData.caster.ent.get(HeroAttrsComp).hero_name}][${this.sData.caster.ent.eid}]的 [${this.SConf.name}]对 [${target.box_group}][ ${target.ent.get(HeroAttrsComp).hero_name}][${target.ent.eid}]`);
// if(this.sData.hit_count > this.SConf.hit_num) return 不能超出 最大伤害数量
// 使用伤害队列系统处理伤害
DamageQueueHelper.addDamageToEntity(
target.ent,
this.sData.Attrs,
this.sData.caster,
this.sData.s_uuid
);
// 更新技能命中次数
this.sData.hit_count++
// 检查技能是否应该销毁
if (
this.sData.hit_count >= (this.SConf.hit + this.sData.Attrs[Attrs.PUNCTURE]) &&
(this.SConf.DTType != DTType.range) &&
(this.SConf.EType != EType.animationEnd) &&
(this.SConf.EType != EType.timeEnd)
) {
this.ent.destroy(); // 技能命中次数达到上限后销毁
}
}
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
// 清理碰撞体事件监听
if (this.collider) {
this.collider.off(Contact2DType.BEGIN_CONTACT);
}
// 取消所有定时器
this.unscheduleAllCallbacks();
this.node.destroy();
}
}