feat(skill): 改进贝塞尔曲线弹道运动实现

- 为技能配置添加贝塞尔曲线参数控制:起始高度、中间高度和弧度系数
- 重构贝塞尔曲线控制点计算逻辑,移除冗余的水平投影计算
- 优化控制点生成算法,考虑距离、弧度系数和最小峰值高度
- 增加速度安全检查和最小时间限制,避免除零和异常情况
- 将多个射击技能从直线运动改为贝塞尔曲线运动,提供更自然的弹道效果
This commit is contained in:
panw
2026-03-30 16:46:10 +08:00
parent e1298bfe96
commit bbced29a0e
4 changed files with 39 additions and 24 deletions

View File

@@ -145,6 +145,9 @@ export interface SkillConfig {
IType:IType, // 技能类型(近战/远程/辅助)
RType:RType, // 技能运行类型(直线/贝塞尔/固定起点/固定终点)
EType:EType, // 结束条件(动画结束/时间结束/距离结束/碰撞/次数结束)
bezier_start_y?:number, // 贝塞尔起始抬升高度
bezier_mid_y?:number, // 贝塞尔中间高度增量
bezier_arc?:number, // 贝塞尔弧度系数
time?:number, // timeEnd 持续时间(秒)
kind?:SkillKind, // 主效果类型
crt?:number, // 额外暴击率
@@ -199,17 +202,17 @@ export const SkillSet: Record<number, SkillConfig> = {
6101: {
uuid:6101,name:"普通射击",sp_name:"arrow",icon:"1135",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
DTType:DTType.single,ap:100,hit_count:1,hitcd:0.2,speed:720,with:0,ready:0.1,EAnm:0,DAnm:"",IType:IType.remote,
RType:RType.linear,EType:EType.collision,buffs:[],info:"对单个目标造成100%攻击的伤害",
RType:RType.bezier,EType:EType.collision,bezier_start_y:20,bezier_mid_y:140,bezier_arc:1.05,buffs:[],info:"对单个目标造成100%攻击的伤害",
},
6102: {
uuid:6102,name:"冰冻射击",sp_name:"arrow_blue",icon:"1135",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
DTType:DTType.single,ap:100,hit_count:1,hitcd:0.2,speed:720,with:0,ready:0.1,EAnm:0,DAnm:"",IType:IType.remote,
RType:RType.linear,EType:EType.collision,buffs:[],info:"对单个目标造成100%攻击的伤害",
RType:RType.bezier,EType:EType.collision,bezier_start_y:20,bezier_mid_y:150,bezier_arc:1.1,buffs:[],info:"对单个目标造成100%攻击的伤害",
},
6103: {
uuid:6103,name:"暴击射击",sp_name:"arrow_red",icon:"1135",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
DTType:DTType.single,ap:100,hit_count:1,hitcd:0.2,speed:720,with:0,ready:0.1,EAnm:0,DAnm:"",IType:IType.remote,
RType:RType.linear,EType:EType.collision,buffs:[],info:"对单个目标造成100%攻击的伤害",
RType:RType.bezier,EType:EType.collision,bezier_start_y:20,bezier_mid_y:145,bezier_arc:1.08,buffs:[],info:"对单个目标造成100%攻击的伤害",
},
//大招
6104: {
@@ -222,12 +225,12 @@ export const SkillSet: Record<number, SkillConfig> = {
6201: {
uuid:6201,name:"蓝波",sp_name:"ball_blue",icon:"1126",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
DTType:DTType.single,frz:0,ap:100,hit_count:1,hitcd:0.3,speed:720,with:90,ready:0.1,EAnm:0,DAnm:"",IType:IType.remote,
RType:RType.linear,EType:EType.collision,buffs:[],info:"对单个目标造成100%攻击的伤害,冰冻率20%",
RType:RType.bezier,EType:EType.collision,buffs:[],info:"对单个目标造成100%攻击的伤害,冰冻率20%",
},
6202: {
uuid:6202,name:"绿波",sp_name:"ball_green",icon:"1126",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
DTType:DTType.single,ap:100,hit_count:1,hitcd:0.3,speed:720,with:90,ready:0.1,EAnm:0,DAnm:"",IType:IType.remote,
RType:RType.linear,EType:EType.collision,buffs:[],info:"对单个目标造成100%攻击的伤害",
RType:RType.bezier,EType:EType.collision,buffs:[],info:"对单个目标造成100%攻击的伤害",
},
6203: {
uuid:6203,name:"红波",sp_name:"ball_red",icon:"1126",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",

View File

@@ -65,6 +65,9 @@ export class SMoveDataComp extends ecs.Comp {
/** 贝塞尔曲线控制点 */
controlPoint: Vec3 = v3(0, 0, 0);
bezierStartHeight: number = 18;
bezierMidHeight: number = 140;
bezierArc: number = 1;
/** 是否自动销毁(到达目标后) */
autoDestroy: boolean = true;
@@ -74,6 +77,9 @@ export class SMoveDataComp extends ecs.Comp {
this.targetPos.set(0, 0, 0);
this.currentPos.set(0, 0, 0);
this.controlPoint.set(0, 0, 0);
this.bezierStartHeight = 18;
this.bezierMidHeight = 140;
this.bezierArc = 1;
this.speed = 500;
this.progress = 0;
this.scale = 1;
@@ -109,7 +115,8 @@ export class SMoveDataComp extends ecs.Comp {
*/
private calculateMoveParameters() {
const distance = Vec3.distance(this.startPos, this.targetPos);
this.totalTime = distance / this.speed;
const safeSpeed = Math.max(1, this.speed);
this.totalTime = Math.max(0.05, distance / safeSpeed);
// 为贝塞尔移动生成控制点
if (this.runType === RType.bezier) {
@@ -127,12 +134,25 @@ export class SMoveDataComp extends ecs.Comp {
// 计算垂直方向
const direction = new Vec3();
Vec3.subtract(direction, this.targetPos, this.startPos);
if (direction.length() < 0.0001) {
this.controlPoint.set(midPoint.x, midPoint.y + this.bezierMidHeight, midPoint.z);
return;
}
const perpendicular = v3(-direction.y, direction.x, 0);
perpendicular.normalize();
// 根据scale决定控制点方向
const offset = 100 * this.scale;
const distance = Vec3.distance(this.startPos, this.targetPos);
const baseOffset = Math.max(120, Math.min(220, distance * 0.35));
const offset = baseOffset * Math.max(0.2, this.bezierArc);
if (perpendicular.y < 0) {
perpendicular.x *= -1;
perpendicular.y *= -1;
}
Vec3.scaleAndAdd(this.controlPoint, midPoint, perpendicular, offset);
const minPeakY = midPoint.y + Math.max(0, this.bezierMidHeight);
if (this.controlPoint.y < minPeakY) {
this.controlPoint.y = minPeakY;
}
}
/**

View File

@@ -104,19 +104,17 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
break;
case RType.bezier:
const bezierStartPos = v3(
moveComp.startPos.x + moveComp.atk_x,
moveComp.startPos.y + moveComp.atk_y,
moveComp.startPos.x,
moveComp.startPos.y + moveComp.bezierStartHeight,
moveComp.startPos.z
);
const bezierTargetPos = v3(
moveComp.targetPos.x + moveComp.atk_x,
moveComp.targetPos.y + moveComp.atk_y,
moveComp.targetPos.x,
moveComp.targetPos.y + moveComp.bezierStartHeight,
moveComp.targetPos.z
);
const horizonY = bezierStartPos.y;
const finalX = this.resolveBezierFinalXByHorizon(bezierStartPos, bezierTargetPos, horizonY);
moveComp.startPos.set(bezierStartPos);
moveComp.targetPos.set(finalX, horizonY, bezierTargetPos.z);
moveComp.targetPos.set(bezierTargetPos);
node.setPosition(bezierStartPos);
break;
default:
@@ -134,15 +132,6 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
}
}
private resolveBezierFinalXByHorizon(startPos: Vec3, targetPos: Vec3, horizonY: number): number {
const deltaY = targetPos.y - startPos.y;
if (Math.abs(deltaY) < 0.0001) {
return targetPos.x;
}
const t = (horizonY - startPos.y) / deltaY;
return startPos.x + (targetPos.x - startPos.x) * t;
}
update(entity: ecs.Entity): void {
if(!smc.mission.play ) return;
if(smc.mission.pause) return

View File

@@ -180,6 +180,9 @@ export class Skill extends ecs.Entity {
sMoveCom.scale = caster.node.scale.x < 0 ? -1 : 1;
sMoveCom.runType = config.RType;
sMoveCom.endType = config.EType;
sMoveCom.bezierStartHeight = config.bezier_start_y ?? 18;
sMoveCom.bezierMidHeight = config.bezier_mid_y ?? 140;
sMoveCom.bezierArc = config.bezier_arc ?? 1;
// 从SkillView获取移动参数位置初始化由SMoveSystem统一处理
sMoveCom.atk_x = SView.atk_x;
sMoveCom.atk_y = SView.atk_y;