From f63f5c66563f809b0827369957d2ef59fee1447f Mon Sep 17 00:00:00 2001 From: walkpan Date: Mon, 16 Mar 2026 20:30:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=88=98=E6=96=97):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=96=BD=E6=B3=95=E7=9B=AE=E6=A0=87=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 HeroAttrsComp 中新增 combat_target_eid 和 enemy_in_cast_range 字段,用于跟踪当前战斗目标 - 修改 MoveSystem 在移动时同步更新战斗目标状态,并清理无效目标 - 重构 SCastSystem 的自动施法逻辑,优先使用已锁定的战斗目标而非重新搜索 - 调整技能 6005 和 6006 的 hit_count 参数,分别改为 2 和 3 次打击 - 为友方技能施法添加事件派发机制,通知其他系统技能释放 --- assets/script/game/common/config/SkillSet.ts | 4 +- assets/script/game/hero/HeroAttrsComp.ts | 4 + assets/script/game/hero/MoveComp.ts | 35 +++++++ assets/script/game/hero/SCastSystem.ts | 96 +++++++------------- 4 files changed, 75 insertions(+), 64 deletions(-) diff --git a/assets/script/game/common/config/SkillSet.ts b/assets/script/game/common/config/SkillSet.ts index af755b11..e2e8d5ff 100644 --- a/assets/script/game/common/config/SkillSet.ts +++ b/assets/script/game/common/config/SkillSet.ts @@ -211,13 +211,13 @@ export const SkillSet: Record = { }, 6005: { uuid:6005,name:"蓝箭",sp_name:"arrow_blue",icon:"1135",TGroup:TGroup.Enemy,TType:TType.Frontline,readyAnm:"",endAnm:"",act:"atk",DTType:DTType.single, - ap:100,hit_count:1,hitcd:0.2,speed:720,with:0, + ap:100,hit_count:2,hitcd:0.2,speed:720,with:0, ready:0,EAnm:0,DAnm:9001,RType:RType.linear,EType:EType.collision, buffs:[],debuffs:[],info:"对前方单个目标造成100%攻击的伤害", }, 6006: { uuid:6006,name:"绿箭",sp_name:"arrow_green",icon:"1135",TGroup:TGroup.Enemy,TType:TType.Frontline,readyAnm:"",endAnm:"",act:"atk",DTType:DTType.single, - ap:100,hit_count:1,hitcd:0.2,speed:720,with:0, + ap:100,hit_count:3,hitcd:0.2,speed:720,with:0, ready:0,EAnm:0,DAnm:9001,RType:RType.linear,EType:EType.collision, buffs:[],debuffs:[],info:"对前方单个目标造成100%攻击的伤害", }, diff --git a/assets/script/game/hero/HeroAttrsComp.ts b/assets/script/game/hero/HeroAttrsComp.ts index 42d7493c..6fa3b46b 100644 --- a/assets/script/game/hero/HeroAttrsComp.ts +++ b/assets/script/game/hero/HeroAttrsComp.ts @@ -122,6 +122,8 @@ export class HeroAttrsComp extends ecs.Comp { skill_id:number=0; //技能攻击技能id can_atk=false can_skill=false + combat_target_eid: number = -1; + enemy_in_cast_range: boolean = false; start(){ } // ==================== BUFF 系统初始化 ==================== @@ -548,6 +550,8 @@ export class HeroAttrsComp extends ecs.Comp { this.skill_id = 0; this.can_atk=false this.can_skill=false + this.combat_target_eid = -1; + this.enemy_in_cast_range = false; // 重置脏标签 this.dirty_hp = false; this.dirty_shield = false; diff --git a/assets/script/game/hero/MoveComp.ts b/assets/script/game/hero/MoveComp.ts index b64471a2..8ca2331b 100644 --- a/assets/script/game/hero/MoveComp.ts +++ b/assets/script/game/hero/MoveComp.ts @@ -88,10 +88,12 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate if (model.fac !== FacSet.HERO && model.fac !== FacSet.MON) return; if (!move.moving) return; if (model.fac === FacSet.MON && smc.mission.stop_mon_action) { + this.clearCombatTarget(model); view.status_change("idle"); return; } if (model.is_stop || model.is_dead || model.is_reviving || model.in_stun || model.in_frost) { + this.clearCombatTarget(model); if (!model.is_reviving) view.status_change("idle"); return; } @@ -103,13 +105,46 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate const nearestEnemy = this.findNearestEnemy(e); if (nearestEnemy) { this.processCombatLogic(e, move, view, model, nearestEnemy); + this.syncCombatTarget(model, view, nearestEnemy); } else { + this.clearCombatTarget(model); move.targetY = 0; this.processReturnFormation(e, move, view, model); model.is_atking = false; } } + private clearCombatTarget(model: HeroAttrsComp): void { + model.combat_target_eid = -1; + model.enemy_in_cast_range = false; + } + + private syncCombatTarget(model: HeroAttrsComp, selfView: HeroViewComp, enemyView: HeroViewComp): void { + if (!enemyView || !enemyView.node || !enemyView.ent) { + this.clearCombatTarget(model); + return; + } + const enemyAttrs = enemyView.ent.get(HeroAttrsComp); + if (!enemyAttrs || enemyAttrs.is_dead || enemyAttrs.is_reviving || enemyAttrs.fac === model.fac) { + this.clearCombatTarget(model); + return; + } + model.combat_target_eid = enemyView.ent.eid; + model.enemy_in_cast_range = this.isEnemyInAttackRange(model, selfView.node.position.x, enemyView.node.position.x); + } + + private isEnemyInAttackRange(model: HeroAttrsComp, selfX: number, enemyX: number): boolean { + const dist = Math.abs(selfX - enemyX); + const rangeType = model.type as HType.Melee | HType.Mid | HType.Long; + if (rangeType === HType.Melee) return dist <= this.meleeAttackRange; + if (rangeType === HType.Long) { + const [, maxRange] = this.resolveCombatRange(model, 360, 720); + return dist <= maxRange; + } + const [, maxRange] = this.resolveCombatRange(model, 120, 360); + return dist <= maxRange; + } + private processCombatLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) { const rangeType = model.type as HType.Melee | HType.Mid | HType.Long; switch (rangeType) { diff --git a/assets/script/game/hero/SCastSystem.ts b/assets/script/game/hero/SCastSystem.ts index 247bcf63..bff3d180 100644 --- a/assets/script/game/hero/SCastSystem.ts +++ b/assets/script/game/hero/SCastSystem.ts @@ -2,10 +2,12 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec import { Vec3 } from "cc"; import { HeroAttrsComp } from "./HeroAttrsComp"; import { HeroViewComp } from "./HeroViewComp"; -import { BuffsList, SkillConfig, SkillKind, SkillSet, TGroup, TType } from "../common/config/SkillSet"; +import { BuffsList, SkillConfig, SkillKind, SkillSet, TGroup } from "../common/config/SkillSet"; import { Skill } from "../skill/Skill"; import { smc } from "../common/SingletonModuleComp"; import { GameConst } from "../common/config/GameConst"; +import { oops } from "db://oops-framework/core/Oops"; +import { GameEvent } from "../common/config/GameEvent"; /** * ==================== 自动施法系统 ==================== @@ -25,8 +27,7 @@ import { GameConst } from "../common/config/GameConst"; @ecs.register('SCastSystem') export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { debugMode: boolean = false; // 是否启用调试模式 - private readonly emptyCastPlan = { skillId: 0, targets: [] as HeroViewComp[] }; - private readonly emptyCandidates: { view: HeroViewComp; attrs: HeroAttrsComp; dis: number; lane: number; isSameFac: boolean }[] = []; + private readonly emptyCastPlan = { skillId: 0, targets: [] as HeroViewComp[], isFriendly: false }; filter(): ecs.IMatcher { return ecs.allOf(HeroAttrsComp, HeroViewComp); @@ -46,9 +47,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate this.castSkill(e, castPlan, heroAttrs, heroView); } - private pickCastSkill(heroAttrs: HeroAttrsComp, heroView: HeroViewComp): { skillId: number; targets: HeroViewComp[] } { - const range = heroAttrs.getCachedMaxSkillDistance() || GameConst.Battle.DEFAULT_SEARCH_RANGE; - const candidates = this.collectCandidates(heroView, heroAttrs, range); + private pickCastSkill(heroAttrs: HeroAttrsComp, heroView: HeroViewComp): { skillId: number; targets: HeroViewComp[]; isFriendly: boolean } { const skillCandidates = [heroAttrs.skill_id, heroAttrs.atk_id]; for (const s_uuid of skillCandidates) { if (!s_uuid) continue; @@ -57,14 +56,18 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate const isMainSkill = s_uuid === heroAttrs.skill_id; if (isMainSkill && !heroAttrs.can_skill) continue; if (!isMainSkill && !heroAttrs.can_atk) continue; - const targets = this.findTargetsByCandidates(heroView, config, candidates); - if (targets.length === 0) continue; - return { skillId: s_uuid, targets }; + if (this.isFriendlySkill(config.TGroup)) { + return { skillId: s_uuid, targets: [heroView], isFriendly: true }; + } + if (!heroAttrs.enemy_in_cast_range) continue; + const target = this.resolveCombatTarget(heroAttrs); + if (!target) continue; + return { skillId: s_uuid, targets: [target], isFriendly: false }; } return this.emptyCastPlan; } - private castSkill(entity: ecs.Entity, castPlan: { skillId: number; targets: HeroViewComp[] }, heroAttrs: HeroAttrsComp, heroView: HeroViewComp) { + private castSkill(entity: ecs.Entity, castPlan: { skillId: number; targets: HeroViewComp[]; isFriendly: boolean }, heroAttrs: HeroAttrsComp, heroView: HeroViewComp) { const s_uuid = castPlan.skillId; const config = SkillSet[s_uuid]; if (!config) return; @@ -78,6 +81,15 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate heroView.scheduleOnce(() => { if (!heroView.node || !heroView.node.isValid || heroAttrs.is_dead) return; + if (castPlan.isFriendly) { + oops.message.dispatchEvent(GameEvent.CastHeroSkill, { + casterEid: entity.eid, + s_uuid, + fac: heroAttrs.fac, + targetEids: castPlan.targets.map(target => target.ent?.eid).filter((eid): eid is number => typeof eid === "number") + }); + return; + } const validTargets = this.filterValidTargets(castPlan.targets); if (validTargets.length === 0) return; this.applyPrimaryEffect(entity, s_uuid, config, heroView, validTargets); @@ -153,59 +165,19 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate }); } - private collectCandidates(caster: HeroViewComp, casterAttrs: HeroAttrsComp, range: number): { view: HeroViewComp; attrs: HeroAttrsComp; dis: number; lane: number; isSameFac: boolean }[] { - if (!caster || !caster.node || !caster.node.isValid) return this.emptyCandidates; - const currentPos = caster.node.position; - const list: { view: HeroViewComp; attrs: HeroAttrsComp; dis: number; lane: number; isSameFac: boolean }[] = []; - ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(ent => { - const targetAttrs = ent.get(HeroAttrsComp); - const targetView = ent.get(HeroViewComp); - if (!targetAttrs || !targetView || !targetView.node || targetAttrs.is_dead) return; - if (targetView === caster) return; - const dis = Math.abs(currentPos.x - targetView.node.position.x); - if (dis > range) return; - const lane = Math.abs(currentPos.y - targetView.node.position.y); - const isSameFac = targetAttrs.fac === casterAttrs.fac; - list.push({ view: targetView, attrs: targetAttrs, dis, lane, isSameFac }); - }); - return list; + private isFriendlySkill(group: TGroup): boolean { + return group === TGroup.Self || group === TGroup.Team || group === TGroup.Ally; } - private findTargetsByCandidates(caster: HeroViewComp, config: SkillConfig, candidates: { view: HeroViewComp; attrs: HeroAttrsComp; dis: number; lane: number; isSameFac: boolean }[]): HeroViewComp[] { - const isEnemy = config.TGroup === TGroup.Enemy; - const isSelf = config.TGroup === TGroup.Self; - const isTeam = config.TGroup === TGroup.Team || config.TGroup === TGroup.Ally; - const isAll = config.TGroup === TGroup.All; - if (isSelf) return [caster]; - if (!isEnemy && !isTeam && !isAll) return this.emptyCastPlan.targets; - const list = candidates.filter(item => { - if (isEnemy && item.isSameFac) return false; - if (isTeam && !item.isSameFac) return false; - return true; - }); - list.sort((a, b) => { - const type = config.TType ?? TType.Frontline; - switch (type) { - case TType.Backline: - if (a.lane !== b.lane) return a.lane - b.lane; - return b.dis - a.dis; - case TType.LowestHP: - if (a.attrs.hp !== b.attrs.hp) return a.attrs.hp - b.attrs.hp; - return a.dis - b.dis; - case TType.HighestHP: - if (a.attrs.hp !== b.attrs.hp) return b.attrs.hp - a.attrs.hp; - return a.dis - b.dis; - case TType.HighestAP: - if (a.attrs.ap !== b.attrs.ap) return b.attrs.ap - a.attrs.ap; - return a.dis - b.dis; - case TType.Frontline: - default: - if (a.lane !== b.lane) return a.lane - b.lane; - return a.dis - b.dis; - } - }); - - const maxTargets = Math.max(GameConst.Skill.MIN_TARGET_COUNT, 1); - return list.slice(0, maxTargets).map(item => item.view); + private resolveCombatTarget(heroAttrs: HeroAttrsComp): HeroViewComp | null { + if (heroAttrs.combat_target_eid <= 0) return null; + const targetEntity = ecs.getEntityByEid(heroAttrs.combat_target_eid); + if (!targetEntity) return null; + const targetAttrs = targetEntity.get(HeroAttrsComp); + const targetView = targetEntity.get(HeroViewComp); + if (!targetAttrs || !targetView || !targetView.node || !targetView.node.isValid) return null; + if (targetAttrs.is_dead || targetAttrs.is_reviving) return null; + if (targetAttrs.fac === heroAttrs.fac) return null; + return targetView; } }