From b8f48e09d6bb2878d934dfba6cef79b3db8405ad Mon Sep 17 00:00:00 2001 From: walkpan Date: Fri, 31 Oct 2025 20:08:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=88=98=E6=96=97=E7=B3=BB=E7=BB=9F):=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BC=A4=E5=AE=B3=E9=98=9F=E5=88=97=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=E4=BC=98=E5=8C=96=E6=88=98=E6=96=97=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构伤害处理逻辑,将直接伤害组件改为队列系统 - 新增DamageQueueComp组件管理伤害事件队列 - 添加DamageQueueHelper工具类处理伤害事件添加和查询 - 修改HeroAtkSystem改为处理伤害队列而非单个伤害 - 移除旧的DmgDataCom组件及相关引用 - 优化SkillView.apply_damage使用新队列系统 --- assets/script/game/hero/DamageQueueComp.ts | 185 ++++++++++++++++++ .../script/game/hero/DamageQueueComp.ts.meta | 9 + assets/script/game/hero/Hero.ts | 1 - assets/script/game/hero/HeroAtk.ts | 116 ++++++----- assets/script/game/hero/HeroAttrsComp.ts | 2 - assets/script/game/skill/SDataCom.ts | 26 --- assets/script/game/skill/SkillView.ts | 24 ++- 7 files changed, 278 insertions(+), 85 deletions(-) create mode 100644 assets/script/game/hero/DamageQueueComp.ts create mode 100644 assets/script/game/hero/DamageQueueComp.ts.meta diff --git a/assets/script/game/hero/DamageQueueComp.ts b/assets/script/game/hero/DamageQueueComp.ts new file mode 100644 index 00000000..9dbd94d6 --- /dev/null +++ b/assets/script/game/hero/DamageQueueComp.ts @@ -0,0 +1,185 @@ +import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; +import { HeroViewComp } from "./HeroViewComp"; + +/** + * ==================== 伤害事件数据 ==================== + * + * 单个伤害事件的数据结构 + */ +export interface DamageEvent { + /** 伤害属性数据 */ + Attrs: any; + + /** 施法者 */ + caster: HeroViewComp; + + /** 技能UUID */ + s_uuid: number; + + /** 伤害创建时间戳(用于排序和调试) */ + timestamp: number; + + /** 伤害事件ID(用于去重和调试) */ + eventId: string; +} + +/** + * ==================== 伤害队列组件 ==================== + * + * 用途: + * - 存储一个实体在当前帧收到的所有伤害事件 + * - 支持批量处理,避免同帧多伤害冲突 + * - 提供伤害事件的排序和去重功能 + */ +@ecs.register('DamageQueue') +export class DamageQueueComp extends ecs.Comp { + /** 伤害事件队列 */ + damageEvents: DamageEvent[] = []; + + /** 队列创建时间 */ + createTime: number = 0; + + /** 是否正在处理中 */ + isProcessing: boolean = false; + + /** 已处理的伤害数量 */ + processedCount: number = 0; + + reset() { + this.damageEvents = []; + this.createTime = 0; + this.isProcessing = false; + this.processedCount = 0; + } + + /** + * 添加伤害事件到队列 + */ + addDamageEvent(attrs: any, caster: HeroViewComp, s_uuid: number): void { + const timestamp = Date.now(); + const eventId = `${caster.ent.eid}_${s_uuid}_${timestamp}_${Math.random()}`; + + const damageEvent: DamageEvent = { + Attrs: attrs ? { ...attrs } : null, // 深拷贝属性数据 + caster: caster, + s_uuid: s_uuid, + timestamp: timestamp, + eventId: eventId + }; + + this.damageEvents.push(damageEvent); + + // 如果是第一个伤害事件,记录创建时间 + if (this.damageEvents.length === 1) { + this.createTime = timestamp; + } + } + + /** + * 获取下一个待处理的伤害事件 + */ + getNextDamageEvent(): DamageEvent | null { + if (this.damageEvents.length === 0) return null; + return this.damageEvents.shift() || null; + } + + /** + * 获取队列中剩余的伤害事件数量 + */ + getRemainingCount(): number { + return this.damageEvents.length; + } + + /** + * 检查队列是否为空 + */ + isEmpty(): boolean { + return this.damageEvents.length === 0; + } + + /** + * 清空队列 + */ + clear(): void { + this.damageEvents = []; + this.processedCount = 0; + this.isProcessing = false; + } + + /** + * 按时间戳排序伤害事件(可选功能) + */ + sortByTimestamp(): void { + this.damageEvents.sort((a, b) => a.timestamp - b.timestamp); + } + + /** + * 去除重复的伤害事件(基于eventId) + */ + removeDuplicates(): void { + const seen = new Set(); + this.damageEvents = this.damageEvents.filter(event => { + if (seen.has(event.eventId)) { + return false; + } + seen.add(event.eventId); + return true; + }); + } + + /** + * 获取队列统计信息(用于调试) + */ + getQueueStats(): { + totalEvents: number; + processedEvents: number; + remainingEvents: number; + isProcessing: boolean; + createTime: number; + } { + return { + totalEvents: this.processedCount + this.damageEvents.length, + processedEvents: this.processedCount, + remainingEvents: this.damageEvents.length, + isProcessing: this.isProcessing, + createTime: this.createTime + }; + } +} + +/** + * ==================== 伤害队列辅助工具 ==================== + */ +export class DamageQueueHelper { + /** + * 为实体添加伤害事件 + * 如果实体没有伤害队列组件,会自动创建 + */ + static addDamageToEntity(entity: ecs.Entity, attrs: any, caster: HeroViewComp, s_uuid: number): void { + let damageQueue = entity.get(DamageQueueComp); + + // 如果实体没有伤害队列组件,创建一个 + if (!damageQueue) { + damageQueue = entity.add(DamageQueueComp); + } + + // 添加伤害事件到队列 + damageQueue.addDamageEvent(attrs, caster, s_uuid); + } + + /** + * 检查实体是否有待处理的伤害 + */ + static hasPendingDamage(entity: ecs.Entity): boolean { + const damageQueue = entity.get(DamageQueueComp); + return damageQueue ? !damageQueue.isEmpty() : false; + } + + /** + * 获取实体的伤害队列统计信息 + */ + static getEntityDamageStats(entity: ecs.Entity): any { + const damageQueue = entity.get(DamageQueueComp); + return damageQueue ? damageQueue.getQueueStats() : null; + } +} \ No newline at end of file diff --git a/assets/script/game/hero/DamageQueueComp.ts.meta b/assets/script/game/hero/DamageQueueComp.ts.meta new file mode 100644 index 00000000..9dc6aee4 --- /dev/null +++ b/assets/script/game/hero/DamageQueueComp.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "d109c47a-c1c5-4a6d-ba8d-81f6510655a1", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index 69112d5f..50a48c87 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -36,7 +36,6 @@ export class Hero extends ecs.Entity { } - /** 加载角色 */ load(pos: Vec3 = Vec3.ZERO,scale:number = 1,uuid:number=1001,fight_pos:number=1) { // console.log("英雄加载:",uuid,pos,scale,info) diff --git a/assets/script/game/hero/HeroAtk.ts b/assets/script/game/hero/HeroAtk.ts index 380331bc..c86d4acb 100644 --- a/assets/script/game/hero/HeroAtk.ts +++ b/assets/script/game/hero/HeroAtk.ts @@ -3,9 +3,9 @@ import { FacSet } from "../common/config/BoxSet"; import { Attrs } from "../common/config/HeroAttrs"; import { FightSet } from "../common/config/Mission"; import { SkillSet } from "../common/config/SkillSet"; -import { DmgDataCom } from "../skill/SDataCom"; import { HeroAttrsComp } from "./HeroAttrsComp"; import { HeroViewComp } from "./HeroViewComp"; +import { DamageQueueComp, DamageEvent, DamageQueueHelper } from "./DamageQueueComp"; /** 业务层对象 */ @ecs.register('HeroAtk') @@ -15,72 +15,99 @@ export class HeroAtkComp extends ecs.Comp { } } -interface FDData{ - damage:number, - isCrit:boolean, - isDodge:boolean, +/** 最终伤害数据接口 */ +interface FinalData { + damage: number; + isCrit: boolean; + isDodge: boolean; } /** 业务层业务逻辑处理对象 伤害处理系统 */ @ecs.register('HeroAtkSystem') -export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate, ecs.IEntityEnterSystem { +export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { private debugMode: boolean = false; // 是否启用调试模式 /** - * 过滤器:只处理拥有伤害数据的实体 + * 过滤器:处理拥有伤害队列的实体 */ filter(): ecs.IMatcher { - return ecs.allOf(HeroAttrsComp, DmgDataCom); + return ecs.allOf(HeroAttrsComp, DamageQueueComp); } /** - * 实体首次进入系统时调用(每个实体只调用一次) + * 系统更新(每帧调用) + * 处理伤害队列中的所有伤害事件 */ - entityEnter(e: ecs.Entity): void { + update(e: ecs.Entity): void { const model = e.get(HeroAttrsComp); - const dmgData = e.get(DmgDataCom); - if (!model || !dmgData) return; - const caster = dmgData.caster; - // 深度拷贝伤害数据,避免组件移除后数据丢失 - const dmgDataCopy = this.deepCopyDmgData(dmgData); - // 立即移除组件,避免重复处理 - e.remove(DmgDataCom); + const damageQueue = e.get(DamageQueueComp); - // 使用拷贝的数据进行伤害计算 - let FDData = this.doAttack(e, dmgDataCopy); - console.log(`[HeroAtkSystem] 英雄${model.hero_name} (uuid: ${model.hero_uuid}) 受到 ${caster.ent.get(HeroAttrsComp).hero_name}(uuid: ${caster.ent.get(HeroAttrsComp).hero_uuid})的 伤害 ${FDData.damage},${FDData.isCrit?"暴击":"普通"}攻击,技能ID ${dmgDataCopy.s_uuid}`); + if (!model || !damageQueue || damageQueue.isEmpty()) return; + + // 标记正在处理 + damageQueue.isProcessing = true; + + // 处理队列中的所有伤害事件 + let processedCount = 0; + while (!damageQueue.isEmpty()) { + const damageEvent = damageQueue.getNextDamageEvent(); + if (!damageEvent) break; + + // 处理单个伤害事件 + const FDData = this.doAttack(e, damageEvent); + processedCount++; + damageQueue.processedCount++; + + if (this.debugMode) { + const casterName = damageEvent.caster?.ent?.get(HeroAttrsComp)?.hero_name || "未知"; + const casterUuid = damageEvent.caster?.ent?.get(HeroAttrsComp)?.hero_uuid || 0; + console.log(`[HeroAtkSystem] 英雄${model.hero_name} (uuid: ${model.hero_uuid}) 受到 ${casterName}(uuid: ${casterUuid})的 伤害 ${FDData.damage},${FDData.isCrit?"暴击":"普通"}攻击,技能ID ${damageEvent.s_uuid}`); + } + + // 如果目标已死亡,停止处理后续伤害 + if (model.is_dead) { + if (this.debugMode) { + console.log(`[HeroAtkSystem] ${model.hero_name} 已死亡,停止处理剩余伤害`); + } + damageQueue.clear(); // 清空剩余伤害 + break; + } + } + + // 如果队列已空,移除伤害队列组件 + if (damageQueue.isEmpty()) { + e.remove(DamageQueueComp); + + if (this.debugMode && processedCount > 0) { + console.log(`[HeroAtkSystem] ${model.hero_name} 伤害队列处理完成,共处理 ${processedCount} 个伤害事件`); + } + } } - /** - * 深度拷贝伤害数据 + * 执行攻击计算 + * @param target 目标实体 + * @param damageEvent 伤害事件数据 + * @returns 最终伤害数据 */ - private deepCopyDmgData(dmgData: DmgDataCom): any { - return { - Attrs: dmgData.Attrs ? { ...dmgData.Attrs } : null, - s_uuid: dmgData.s_uuid - }; - } - - /** - * 处理角色被攻击 - * @param target 被攻击的目标实体 - * @param dmgData 伤害数据(可以是组件或拷贝的对象) - * @returns 实际造成的伤害 - */ - public doAttack(target: ecs.Entity, dmgDataCopy: any): FDData { + private doAttack(target: ecs.Entity, damageEvent: DamageEvent): FinalData { const targetModel = target.get(HeroAttrsComp); const targetView = target.get(HeroViewComp); - let reDate:FDData={ + let reDate:FinalData={ damage:0, isCrit:false, isDodge:false, } if (!targetModel || targetModel.is_dead) return reDate; + // 获取攻击者数据 + const caster = damageEvent.caster; + const casterModel = caster.ent.get(HeroAttrsComp); + if (!casterModel) return reDate; + // 获取技能配置 - const skillConf = SkillSet[dmgDataCopy.s_uuid]; + const skillConf = SkillSet[damageEvent.s_uuid]; if (!skillConf) return reDate; // 触发被攻击事件 @@ -95,7 +122,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd // 暴击判定 const isCrit = this.checkCrit(targetModel.Attrs[Attrs.CRITICAL]); - let damage = this.dmgCount(dmgDataCopy.Attrs,dmgDataCopy.s_uuid); + let damage = this.dmgCount(damageEvent.Attrs,damageEvent.s_uuid); if (isCrit) { damage = Math.floor(damage * (1 + (FightSet.CRIT_DAMAGE + targetModel.Attrs[Attrs.CRITICAL_DMG]) / 100)); reDate.isCrit=true; @@ -114,7 +141,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd // ✅ 触发视图层表现(伤害数字、受击动画、后退) if (targetView) { - targetView.do_atked(damage, isCrit, dmgDataCopy.s_uuid); + targetView.do_atked(damage, isCrit, damageEvent.s_uuid); } // 检查死亡 @@ -273,12 +300,5 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd this.debugMode = false; } - /** - * 系统更新(每帧调用) - * 可以在这里添加需要每帧处理的战斗逻辑 - */ - update(e: ecs.Entity): void { - // 这里可以添加需要每帧处理的战斗逻辑 - // 例如:持续伤害、战斗状态检查等 - } + } \ No newline at end of file diff --git a/assets/script/game/hero/HeroAttrsComp.ts b/assets/script/game/hero/HeroAttrsComp.ts index f7c1cdd2..0132b994 100644 --- a/assets/script/game/hero/HeroAttrsComp.ts +++ b/assets/script/game/hero/HeroAttrsComp.ts @@ -2,8 +2,6 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec import { Attrs, AttrsType, BType, NeAttrs } from "../common/config/HeroAttrs"; import { BuffConf, SkillSet } from "../common/config/SkillSet"; import { HeroInfo, AttrSet, HeroUpSet } from "../common/config/heroSet"; -import { DmgDataCom } from "../skill/SDataCom"; -import { EBusComp } from "./EBusComp"; @ecs.register('HeroAttrs') diff --git a/assets/script/game/skill/SDataCom.ts b/assets/script/game/skill/SDataCom.ts index 52227b99..205384d4 100644 --- a/assets/script/game/skill/SDataCom.ts +++ b/assets/script/game/skill/SDataCom.ts @@ -22,30 +22,4 @@ export class SDataCom extends ecs.Comp { this.hit_count=0 } } -//伤害数据 -@ecs.register('DmgDataCom') -export class DmgDataCom extends ecs.Comp { - /** 业务层组件移除时,重置所有数据为默认值 */ - Attrs:any=null - caster:HeroViewComp=null - s_uuid:number=0 - reset() { - this.Attrs=null - this.s_uuid=0 - this.caster=null - } -} -// /** 业务层业务逻辑处理对象 */ -// export class SDataComSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem { -// filter(): ecs.IMatcher { -// return ecs.allOf(SDataCom); -// } - -// entityEnter(e: ecs.Entity): void { -// // 注:自定义业务逻辑 - - -// e.remove(SDataCom); -// } -// } \ No newline at end of file diff --git a/assets/script/game/skill/SkillView.ts b/assets/script/game/skill/SkillView.ts index 98ce102b..954de374 100644 --- a/assets/script/game/skill/SkillView.ts +++ b/assets/script/game/skill/SkillView.ts @@ -5,12 +5,13 @@ import { HeroViewComp } from "../hero/HeroViewComp"; import { DTType, EType, RType, SkillConfig, SkillSet } from "../common/config/SkillSet"; import { BezierMove } from "../BezierMove/BezierMove"; import { BoxSet, FacSet } from "../common/config/BoxSet"; -import { DmgDataCom, SDataCom } from "./SDataCom"; +import { SDataCom } from "./SDataCom"; import { SMoveDataComp } from "./SMoveComp"; import { Attrs } from "../common/config/HeroAttrs"; import { MonMoveComp } from "../hero/MonMove"; import { HeroAttrsComp } from "../hero/HeroAttrsComp"; import { HeroMoveComp } from "../hero/HeroMove"; +import { DamageQueueHelper } from "../hero/DamageQueueComp"; const { ccclass, property } = _decorator; @@ -178,14 +179,21 @@ export class SkillView extends CCComp { apply_damage(target:HeroViewComp,is_range:boolean=false){ if(target == null) return; if (!this.SConf) return; - //伤害处理 - target.ent.addComponents(DmgDataCom) - let dmgData=target.ent.get(DmgDataCom) - dmgData.Attrs= this.sData.Attrs - dmgData.caster= this.sData.caster - dmgData.s_uuid= this.sData.s_uuid + + // 使用伤害队列系统处理伤害 + DamageQueueHelper.addDamageToEntity( + target.ent, + this.sData.Attrs, + this.sData.caster, + this.sData.s_uuid + ); + // console.log(`[SkillCom]: ${this.sData.caster.ent.get(HeroAttrsComp).hero_name}[${this.sData.caster.ent.get(HeroAttrsComp).fac}:${ this.sData.fac}:${target.ent.get(HeroAttrsComp).fac}] 对 ${target.ent.get(HeroAttrsComp).hero_name} 释放技能 ${this.SConf.name}`) - this.sData.hit_count++ + + // 更新技能命中次数 + this.sData.hit_count++ + + // 检查技能是否应该销毁 if( this.sData.hit_count>=(this.SConf.hit+ this.sData.Attrs[Attrs.PUNCTURE])&&(this.SConf.DTType!=DTType.range)&&(this.endType!=EType.animationEnd)&&(this.endType!=EType.timeEnd)) this.ent.destroy// 技能命中次数 } /** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */