feat(技能): 为友方技能添加飞行特效并优化目标选择逻辑

- 新增友方技能释放时的飞行特效,包括抛物线动画和缩放旋转效果
- 重构 applyFriendlySkillEffects 方法,将特效播放与实际效果应用分离
- 调整 buff.prefab 的缩放比例从 0.2 增大到 0.5 以适配新特效
- 优化友方技能目标选择逻辑,确保特效从施法位置正确飞向目标
This commit is contained in:
walkpan
2026-04-06 15:35:11 +08:00
parent 310d4f0eb0
commit 197ecefe80
2 changed files with 107 additions and 30 deletions

View File

@@ -99,8 +99,8 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.2,
"y": 0.2,
"x": 0.5,
"y": 0.5,
"z": 1
},
"_mobility": 0,

View File

@@ -1,5 +1,5 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Vec3 } from "cc";
import { Vec3, Prefab, instantiate, tween, Node } from "cc";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { DTType, RType, SkillConfig, SkillKind, SkillSet, SkillUpList, TGroup } from "../common/config/SkillSet";
@@ -113,7 +113,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (isFriendly) {
const friendlyTargets = this.resolveFriendlyTargets(targetEids, FacSet.HERO);
if (friendlyTargets.length === 0) continue;
this.applyFriendlySkillEffects(s_uuid, cardLv, config, null as any, mockAttrs, friendlyTargets, null);
this.applyFriendlySkillEffects(s_uuid, cardLv, config, null as any, mockAttrs, friendlyTargets, spawnPos);
} else {
const enemyTargetPos = this.resolveRepeatCastTargetPos(new Vec3(spawnPos.x + 300, spawnPos.y, spawnPos.z), i);
this.createSkillEntityForCard(s_uuid, cardLv, mockAttrs, spawnPos, enemyTargetPos, i);
@@ -384,37 +384,114 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
const applyTargets = kind === SkillKind.Heal
? this.pickHealTargetsByMostMissingHp(targets, sHit)
: this.pickRandomFriendlyTargets(targets, sHit);
for (const target of applyTargets) {
if (!target.ent) continue;
const model = target.ent.get(HeroAttrsComp);
if (!model || model.is_dead) continue;
if (kind === SkillKind.Heal && sAp !== 0) {
const addHp = Math.floor(sAp*_cAttrsComp.ap/100);//技能的ap是百分值 需要/100
model.add_hp(addHp);
target.health(addHp);
} else if (kind === SkillKind.Shield && sAp !== 0) {
const addShield = Math.max(0, Math.floor(sAp));
model.add_shield(addShield);
}
if (!config.buffs || config.buffs.length === 0) continue;
for (const buffConf of config.buffs) {
if (!buffConf) continue;
const sBuffAp=buffConf.value+sUp.buff_ap
const sBuffHp=buffConf.value+sUp.buff_hp
switch (buffConf.buff){
case Attrs.ap:
model.add_ap(sBuffAp)
//加工动画
break
case Attrs.hp_max:
model.add_hp_max(sBuffHp)
//加最大生命值动画
break
}
const startPos = _heroView?.node?.position || _targetPos;
if (startPos) {
this.playFriendlyCastEffect(startPos, target, () => {
this.applyActualFriendlyEffect(target, kind, sAp, _cAttrsComp, config, sUp);
});
} else {
this.applyActualFriendlyEffect(target, kind, sAp, _cAttrsComp, config, sUp);
}
}
}
private playFriendlyCastEffect(startPos: Vec3, target: HeroViewComp, callback: Function) {
if (!target.node || !target.node.isValid) {
callback();
return;
}
const prefabPath = "game/skill/buff/buff";
const prefab = oops.res.get(prefabPath, Prefab);
if (!prefab) {
oops.res.load(prefabPath, Prefab, (err, res) => {
if (err) {
callback();
return;
}
this.doPlayFriendlyCastEffect(startPos.clone(), target, res as Prefab, callback);
});
} else {
this.doPlayFriendlyCastEffect(startPos.clone(), target, prefab as Prefab, callback);
}
}
private doPlayFriendlyCastEffect(startPos: Vec3, target: HeroViewComp, prefab: Prefab, callback: Function) {
if (!target.node || !target.node.isValid) {
callback();
return;
}
const scene = smc.map?.MapView?.scene;
const parent = scene?.entityLayer?.node?.getChildByName("SKILL") || target.node.parent;
if (!parent) {
callback();
return;
}
const node = instantiate(prefab);
node.parent = parent;
node.setPosition(startPos);
const targetPos = target.node.position.clone();
targetPos.y += 50;
const midX = (startPos.x + targetPos.x) / 2;
const midY = Math.max(startPos.y, targetPos.y) + 200;
const dist = Vec3.distance(startPos, targetPos);
const duration = Math.min(0.6, Math.max(0.3, dist / 800));
const proxy = { ratio: 0 };
tween(proxy)
.to(duration, { ratio: 1 }, {
easing: 'sineOut',
onUpdate: () => {
if (!node.isValid) return;
const r = proxy.ratio;
const x = (1 - r) * (1 - r) * startPos.x + 2 * r * (1 - r) * midX + r * r * targetPos.x;
const y = (1 - r) * (1 - r) * startPos.y + 2 * r * (1 - r) * midY + r * r * targetPos.y;
node.setPosition(new Vec3(x, y, startPos.z));
node.setScale(new Vec3(1 - r * 0.3, 1 - r * 0.3, 1));
node.angle = -r * 720;
}
})
.call(() => {
if (node.isValid) node.destroy();
callback();
})
.start();
}
private applyActualFriendlyEffect(target: HeroViewComp, kind: SkillKind, sAp: number, _cAttrsComp: HeroAttrsComp, config: SkillConfig, sUp: any) {
if (!target.ent) return;
const model = target.ent.get(HeroAttrsComp);
if (!model || model.is_dead) return;
if (kind === SkillKind.Heal && sAp !== 0) {
const addHp = Math.floor(sAp*_cAttrsComp.ap/100);
model.add_hp(addHp);
target.health(addHp);
} else if (kind === SkillKind.Shield && sAp !== 0) {
const addShield = Math.max(0, Math.floor(sAp));
model.add_shield(addShield);
}
if (!config.buffs || config.buffs.length === 0) return;
for (const buffConf of config.buffs) {
if (!buffConf) continue;
const sBuffAp=buffConf.value+sUp.buff_ap
const sBuffHp=buffConf.value+sUp.buff_hp
switch (buffConf.buff){
case Attrs.ap:
model.add_ap(sBuffAp)
break
case Attrs.hp_max:
model.add_hp_max(sBuffHp)
break
}
}
}
private pickRandomFriendlyTargets(targets: HeroViewComp[], hitCount: number): HeroViewComp[] {
if (!targets || targets.length === 0) return [];
const validHitCount = Math.max(1, Math.floor(hitCount));