refactor(hero): 重构技能施放系统,分离敌友方效果处理
- 将 applyPrimaryEffect 拆分为 applyEnemySkillEffects 和 applyFriendlySkillEffects,明确职责边界 - 友方技能现在统一在 applyFriendlySkillEffects 中处理治疗、护盾和 buff 效果 - 移除冗余的 applyExtraEffects 方法,简化逻辑流程 - 为关键方法添加详细注释,提升代码可读性
This commit is contained in:
@@ -23,9 +23,14 @@ import { HType } from "../common/config/heroSet";
|
|||||||
@ecs.register('SCastSystem')
|
@ecs.register('SCastSystem')
|
||||||
export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||||||
debugMode: boolean = false; // 是否启用调试模式
|
debugMode: boolean = false; // 是否启用调试模式
|
||||||
|
/** 空施法计划:用于“当前无可施法技能”时的统一返回 */
|
||||||
private readonly emptyCastPlan = { skillId: 0, skillLv: 1, isFriendly: false, targetPos: null as Vec3 | null, targetEids: [] as number[] };
|
private readonly emptyCastPlan = { skillId: 0, skillLv: 1, isFriendly: false, targetPos: null as Vec3 | null, targetEids: [] as number[] };
|
||||||
|
/** 近战英雄默认施法射程 */
|
||||||
private readonly meleeCastRange = 64;
|
private readonly meleeCastRange = 64;
|
||||||
|
/** 查询缓存:避免每帧重复创建 matcher */
|
||||||
private heroMatcher: ecs.IMatcher | null = null;
|
private heroMatcher: ecs.IMatcher | null = null;
|
||||||
|
|
||||||
|
/** 获取英雄查询条件(包含属性与视图组件) */
|
||||||
private getHeroMatcher(): ecs.IMatcher {
|
private getHeroMatcher(): ecs.IMatcher {
|
||||||
if (!this.heroMatcher) {
|
if (!this.heroMatcher) {
|
||||||
this.heroMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp);
|
this.heroMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp);
|
||||||
@@ -33,10 +38,17 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
return this.heroMatcher;
|
return this.heroMatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 系统过滤器:仅处理英雄实体 */
|
||||||
filter(): ecs.IMatcher {
|
filter(): ecs.IMatcher {
|
||||||
return this.getHeroMatcher();
|
return this.getHeroMatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每帧更新:
|
||||||
|
* 1. 战斗状态校验
|
||||||
|
* 2. 英雄施法CD更新与显示
|
||||||
|
* 3. 选取本帧可施放技能并执行施法
|
||||||
|
*/
|
||||||
update(e: ecs.Entity): void {
|
update(e: ecs.Entity): void {
|
||||||
if(!smc.mission.play ) return;
|
if(!smc.mission.play ) return;
|
||||||
if(smc.mission.pause) return
|
if(smc.mission.pause) return
|
||||||
@@ -52,6 +64,11 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
this.castSkill(castPlan, heroAttrs, heroView);
|
this.castSkill(castPlan, heroAttrs, heroView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择当前应释放的技能。
|
||||||
|
* 选择顺序:技能候选列表顺序 + 条件过滤(CD、目标可达、目标类型匹配)。
|
||||||
|
* 返回内容同时包含“对敌施法”和“友方施法”两种执行所需数据。
|
||||||
|
*/
|
||||||
private pickCastSkill(heroAttrs: HeroAttrsComp, heroView: HeroViewComp): { skillId: number; skillLv: number; isFriendly: boolean; targetPos: Vec3 | null; targetEids: number[] } {
|
private pickCastSkill(heroAttrs: HeroAttrsComp, heroView: HeroViewComp): { skillId: number; skillLv: number; isFriendly: boolean; targetPos: Vec3 | null; targetEids: number[] } {
|
||||||
const type = heroAttrs.type as HType;
|
const type = heroAttrs.type as HType;
|
||||||
const maxRange = this.resolveMaxCastRange(heroAttrs, type);
|
const maxRange = this.resolveMaxCastRange(heroAttrs, type);
|
||||||
@@ -82,6 +99,12 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
return this.emptyCastPlan;
|
return this.emptyCastPlan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行施法:
|
||||||
|
* - 播放前摇与技能动作
|
||||||
|
* - 延迟到出手时机后,按技能目标类型分发到对敌/友方效果处理
|
||||||
|
* - 触发技能CD
|
||||||
|
*/
|
||||||
private castSkill(castPlan: { skillId: number; skillLv: number; isFriendly: boolean; targetPos: Vec3 | null; targetEids: number[] }, heroAttrs: HeroAttrsComp, heroView: HeroViewComp) {
|
private castSkill(castPlan: { skillId: number; skillLv: number; isFriendly: boolean; targetPos: Vec3 | null; targetEids: number[] }, heroAttrs: HeroAttrsComp, heroView: HeroViewComp) {
|
||||||
const s_uuid = castPlan.skillId;
|
const s_uuid = castPlan.skillId;
|
||||||
const skillLv = castPlan.skillLv;
|
const skillLv = castPlan.skillLv;
|
||||||
@@ -102,21 +125,25 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
if (castPlan.isFriendly) {
|
if (castPlan.isFriendly) {
|
||||||
const friendlyTargets = this.resolveFriendlyTargets(castPlan.targetEids, heroAttrs.fac);
|
const friendlyTargets = this.resolveFriendlyTargets(castPlan.targetEids, heroAttrs.fac);
|
||||||
if (friendlyTargets.length === 0) return;
|
if (friendlyTargets.length === 0) return;
|
||||||
this.applyPrimaryEffect(s_uuid, skillLv, config, heroView,heroAttrs, friendlyTargets, null);
|
this.applyFriendlySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, friendlyTargets, null);
|
||||||
this.applyExtraEffects(config, friendlyTargets);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.applyPrimaryEffect(s_uuid, skillLv, config, heroView,heroAttrs, [], castPlan.targetPos);
|
this.applyEnemySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, castPlan.targetPos);
|
||||||
}, delay);
|
}, delay);
|
||||||
heroAttrs.triggerSkillCD(s_uuid);
|
heroAttrs.triggerSkillCD(s_uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 构建技能尝试顺序:优先主动技能,其次普攻 */
|
||||||
private buildSkillCandidates(skillIds: number[]): number[] {
|
private buildSkillCandidates(skillIds: number[]): number[] {
|
||||||
if (!skillIds || skillIds.length === 0) return [];
|
if (!skillIds || skillIds.length === 0) return [];
|
||||||
if (skillIds.length === 1) return [skillIds[0]];
|
if (skillIds.length === 1) return [skillIds[0]];
|
||||||
return [...skillIds.slice(1), skillIds[0]];
|
return [...skillIds.slice(1), skillIds[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建技能实体(投射物/范围体等)。
|
||||||
|
* 仅用于对敌伤害技能的实体化表现与碰撞伤害分发。
|
||||||
|
*/
|
||||||
private createSkillEntity(s_uuid: number, skillLv: number, caster: HeroViewComp,cAttrsComp: HeroAttrsComp, targetPos: Vec3) {
|
private createSkillEntity(s_uuid: number, skillLv: number, caster: HeroViewComp,cAttrsComp: HeroAttrsComp, targetPos: Vec3) {
|
||||||
if (!caster.node || !caster.node.isValid) return;
|
if (!caster.node || !caster.node.isValid) return;
|
||||||
const parent = caster.node.parent;
|
const parent = caster.node.parent;
|
||||||
@@ -126,31 +153,36 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
skill.load(caster.node.position.clone(), parent, s_uuid, targetPos.clone(), caster, cAttrsComp, skillLv, 0);
|
skill.load(caster.node.position.clone(), parent, s_uuid, targetPos.clone(), caster, cAttrsComp, skillLv, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyPrimaryEffect(s_uuid: number, skillLv: number, config: SkillConfig, heroView: HeroViewComp, cAttrsComp: HeroAttrsComp,targets: HeroViewComp[], targetPos: Vec3 | null) {
|
/**
|
||||||
|
* 对敌技能效果处理。
|
||||||
|
* 当前职责:仅处理伤害技能并创建技能实体。
|
||||||
|
*/
|
||||||
|
private applyEnemySkillEffects(s_uuid: number, skillLv: number, config: SkillConfig, heroView: HeroViewComp, cAttrsComp: HeroAttrsComp, targetPos: Vec3 | null) {
|
||||||
const kind = config.kind ?? SkillKind.Damage;
|
const kind = config.kind ?? SkillKind.Damage;
|
||||||
if (kind === SkillKind.Damage) {
|
if (kind !== SkillKind.Damage) return;
|
||||||
if (config.ap <= 0 || !targetPos) return;
|
if (config.ap <= 0 || !targetPos) return;
|
||||||
this.createSkillEntity(s_uuid, skillLv, heroView,cAttrsComp, targetPos);
|
this.createSkillEntity(s_uuid, skillLv, heroView, cAttrsComp, targetPos);
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* 友方技能效果处理。
|
||||||
|
* 当前职责:
|
||||||
|
* 1. 处理治疗与护盾
|
||||||
|
* 2. 处理 buffs 配置追加
|
||||||
|
* 3. 保留完整施法信息参数,便于后续扩展更多友方效果
|
||||||
|
*/
|
||||||
|
private applyFriendlySkillEffects(_s_uuid: number, _skillLv: number, config: SkillConfig, _heroView: HeroViewComp, _cAttrsComp: HeroAttrsComp, targets: HeroViewComp[], _targetPos: Vec3 | null) {
|
||||||
|
const kind = config.kind ?? SkillKind.Support;
|
||||||
for (const target of targets) {
|
for (const target of targets) {
|
||||||
if (!target.ent) continue;
|
if (!target.ent) continue;
|
||||||
const model = target.ent.get(HeroAttrsComp);
|
const model = target.ent.get(HeroAttrsComp);
|
||||||
if (!model || model.is_dead) continue;
|
if (!model || model.is_dead) continue;
|
||||||
if (kind === SkillKind.Heal && config.ap !== 0) {
|
if (kind === SkillKind.Heal && config.ap !== 0) {
|
||||||
let addHp = model.add_hp(config.ap, false);
|
const addHp = model.add_hp(config.ap, false);
|
||||||
target.health(addHp);
|
target.health(addHp);
|
||||||
} else if (kind === SkillKind.Shield && config.ap !== 0) {
|
} else if (kind === SkillKind.Shield && config.ap !== 0) {
|
||||||
model.add_shield(config.ap, false);
|
model.add_shield(config.ap, false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
//应用额外效果
|
|
||||||
private applyExtraEffects(config: SkillConfig, targets: HeroViewComp[]) {
|
|
||||||
for (const target of targets) {
|
|
||||||
if (!target.ent) continue;
|
|
||||||
const model = target.ent.get(HeroAttrsComp);
|
|
||||||
if (!model || model.is_dead) continue;
|
|
||||||
if (!config.buffs || config.buffs.length === 0) continue;
|
if (!config.buffs || config.buffs.length === 0) continue;
|
||||||
for (const buffConf of config.buffs) {
|
for (const buffConf of config.buffs) {
|
||||||
if (!buffConf) continue;
|
if (!buffConf) continue;
|
||||||
@@ -159,6 +191,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 根据目标 eid 列表解析出有效友方视图目标 */
|
||||||
private resolveFriendlyTargets(targetEids: number[], fac: number): HeroViewComp[] {
|
private resolveFriendlyTargets(targetEids: number[], fac: number): HeroViewComp[] {
|
||||||
const targets: HeroViewComp[] = [];
|
const targets: HeroViewComp[] = [];
|
||||||
for (const eid of targetEids) {
|
for (const eid of targetEids) {
|
||||||
@@ -174,6 +207,10 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集可作为友方技能目标的实体 eid。
|
||||||
|
* includeSelf 控制是否包含施法者自身。
|
||||||
|
*/
|
||||||
private collectFriendlyTargetEids(fac: number, selfEid: number | undefined, includeSelf: boolean): number[] {
|
private collectFriendlyTargetEids(fac: number, selfEid: number | undefined, includeSelf: boolean): number[] {
|
||||||
const eids: number[] = [];
|
const eids: number[] = [];
|
||||||
ecs.query(this.getHeroMatcher()).forEach(entity => {
|
ecs.query(this.getHeroMatcher()).forEach(entity => {
|
||||||
@@ -189,20 +226,27 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
return eids;
|
return eids;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 判定施法计划是否具备有效目标 */
|
||||||
private hasCastTarget(castPlan: { skillId: number; skillLv: number; isFriendly: boolean; targetPos: Vec3 | null; targetEids: number[] }): boolean {
|
private hasCastTarget(castPlan: { skillId: number; skillLv: number; isFriendly: boolean; targetPos: Vec3 | null; targetEids: number[] }): boolean {
|
||||||
if (castPlan.skillId === 0) return false;
|
if (castPlan.skillId === 0) return false;
|
||||||
if (castPlan.isFriendly) return castPlan.targetEids.length > 0;
|
if (castPlan.isFriendly) return castPlan.targetEids.length > 0;
|
||||||
return !!castPlan.targetPos;
|
return !!castPlan.targetPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 是否为友方目标技能(队友/友军) */
|
||||||
private isFriendlySkill(group: TGroup): boolean {
|
private isFriendlySkill(group: TGroup): boolean {
|
||||||
return group === TGroup.Team || group === TGroup.Ally;
|
return group === TGroup.Team || group === TGroup.Ally;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 是否为自我目标技能 */
|
||||||
private isSelfSkill(group: TGroup): boolean {
|
private isSelfSkill(group: TGroup): boolean {
|
||||||
return group === TGroup.Self;
|
return group === TGroup.Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在施法距离内查找最近敌人。
|
||||||
|
* 用于单体技能与基础目标参考。
|
||||||
|
*/
|
||||||
private findNearestEnemyInRange(heroAttrs: HeroAttrsComp, heroView: HeroViewComp, maxRange: number): HeroViewComp | null {
|
private findNearestEnemyInRange(heroAttrs: HeroAttrsComp, heroView: HeroViewComp, maxRange: number): HeroViewComp | null {
|
||||||
if (!heroView.node) return null;
|
if (!heroView.node) return null;
|
||||||
const currentX = heroView.node.position.x;
|
const currentX = heroView.node.position.x;
|
||||||
@@ -223,6 +267,10 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
return nearest;
|
return nearest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在施法距离内查找“最前排”敌人。
|
||||||
|
* 依据施法者面向方向选择 x 轴上更前的目标。
|
||||||
|
*/
|
||||||
private findFrontEnemyInRange(heroAttrs: HeroAttrsComp, heroView: HeroViewComp, maxRange: number, nearestEnemy: HeroViewComp): HeroViewComp | null {
|
private findFrontEnemyInRange(heroAttrs: HeroAttrsComp, heroView: HeroViewComp, maxRange: number, nearestEnemy: HeroViewComp): HeroViewComp | null {
|
||||||
if (!heroView.node || !nearestEnemy.node) return null;
|
if (!heroView.node || !nearestEnemy.node) return null;
|
||||||
const currentX = heroView.node.position.x;
|
const currentX = heroView.node.position.x;
|
||||||
@@ -250,6 +298,11 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
return frontEnemy;
|
return frontEnemy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析对敌技能目标点:
|
||||||
|
* - 非范围技能:按施法方向生成目标点
|
||||||
|
* - 范围技能:优先前排敌人位置
|
||||||
|
*/
|
||||||
private resolveEnemyCastTargetPos(config: SkillConfig, heroAttrs: HeroAttrsComp, heroView: HeroViewComp, nearestEnemy: HeroViewComp, maxRange: number): Vec3 | null {
|
private resolveEnemyCastTargetPos(config: SkillConfig, heroAttrs: HeroAttrsComp, heroView: HeroViewComp, nearestEnemy: HeroViewComp, maxRange: number): Vec3 | null {
|
||||||
if (config.DTType !== DTType.range) {
|
if (config.DTType !== DTType.range) {
|
||||||
return this.buildEnemyCastTargetPos(heroView, nearestEnemy, maxRange);
|
return this.buildEnemyCastTargetPos(heroView, nearestEnemy, maxRange);
|
||||||
@@ -259,6 +312,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
return frontEnemy.node.position.clone();
|
return frontEnemy.node.position.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 计算英雄最大施法距离(优先缓存,其次按职业类型) */
|
||||||
private resolveMaxCastRange(heroAttrs: HeroAttrsComp, type: HType): number {
|
private resolveMaxCastRange(heroAttrs: HeroAttrsComp, type: HType): number {
|
||||||
const cached = heroAttrs.getCachedMaxSkillDistance();
|
const cached = heroAttrs.getCachedMaxSkillDistance();
|
||||||
if (cached > 0) return cached;
|
if (cached > 0) return cached;
|
||||||
@@ -267,6 +321,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
return this.meleeCastRange;
|
return this.meleeCastRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 生成沿目标方向的施法目标坐标 */
|
||||||
private buildEnemyCastTargetPos(caster: HeroViewComp, target: HeroViewComp, castRange: number): Vec3 {
|
private buildEnemyCastTargetPos(caster: HeroViewComp, target: HeroViewComp, castRange: number): Vec3 {
|
||||||
const casterPos = caster.node.position;
|
const casterPos = caster.node.position;
|
||||||
const targetPos = target.node.position;
|
const targetPos = target.node.position;
|
||||||
|
|||||||
Reference in New Issue
Block a user