最近工作需要,写了个匀速的贝赛尔曲线运动,用来替代cc.bezierTo,支持缓动。话不多说,直接上代码
// 匀速贝赛尔曲线
export class UniformBezierAction extends cc.ActionInterval {
private bezierPoints: cc.Vec2[];
private totalLength: number;
private startPos: cc.Vec2 | null = null;
private nowFrame: number = 0;
private easingFunc: (t:number) => number;
/**
* 创建一个匀速贝塞尔动作
* @param duration 动作持续时间
* @param bezierPoints 贝塞尔曲线控制点
*/
constructor(duration: number, bezierPoints: cc.Vec2[], easing?: (t:number) => number) {
super();
this.initWithDuration(duration);
this.bezierPoints = bezierPoints;
this.totalLength = this.estimateLengthUpToT(bezierPoints,1);
this.easingFunc = easing || (t => t);
}
/**
* 初始化动作时长
* @param duration 动作持续时间
*/
private initWithDuration(duration: number): void {
this.setDuration(duration);
}
/**
* 克隆当前动作
*/
public clone(): UniformBezierAction {
return new UniformBezierAction(this.getDuration(), this.bezierPoints);
}
/**
* 返回反向动作
*/
public reverse(): cc.ActionInterval {
const reversedPoints = [...this.bezierPoints].reverse();
return new UniformBezierAction(this.getDuration(), reversedPoints);
}
/**
* 估算贝塞尔曲线的部分长度,改进版
* @param points 贝塞尔曲线的控制点
* @param steps 分段数
* @param t 当前目标时间比例
*/
private estimateLengthUpToT(points: cc.Vec2[], t: number, steps: number = 100): number {
let length = 0;
let prevPoint = points[0];
for (let i = 1; i <= steps; i++) {
const currentT = (i / steps) * t; // 计算从 0 到 t 之间的点
const currPoint = this.calculateBezierPoint(points, currentT);
length += prevPoint.sub(currPoint).mag();
prevPoint = currPoint;
}
return length;
}
/**
* 计算贝塞尔曲线上的点
* @param points 贝塞尔曲线的控制点
* @param t 参数 t,范围 [0, 1]
*/
private calculateBezierPoint(points: cc.Vec2[], t: number): cc.Vec2 {
const u = 1 - t;
return points[0].mul(u * u)
.add(points[1].mul(2 * u * t))
.add(points[2].mul(t * t));
}
/**
* 根据目标长度计算对应的 t 值
* @param targetLength 目标长度
*/
private calculateTForLength(targetLength: number): number {
let t1 = 0, t2 = 1;
let currentLength = 0;
while (t2 - t1 > 0.0001) {
const midT = (t1 + t2) / 2;
currentLength = this.estimateLengthUpToT(this.bezierPoints, midT);
if (currentLength < targetLength) {
t1 = midT;
} else {
t2 = midT;
}
}
return (t1 + t2) / 2;
}
/**
* 每帧更新目标节点的位置
* @param dt 动作进度(0 到 1)
*/
public update(dt: number): void {
let target = this.getTarget();
if (!target) {
return;
}
if (!this.startPos) {
this.startPos = target.getPosition();
this.nowFrame = 0;
}
this.nowFrame++;
const easedDt = this.easingFunc(dt);
const targetLength = easedDt * this.totalLength;
const t = this.calculateTForLength(targetLength);
const position = this.calculateBezierPoint(this.bezierPoints, t);
target.setPosition(position);
}
/**
* 创建一个 UniformBezierAction 的实例
* @param duration 动作持续时间
* @param bezierPoints 贝塞尔曲线控制点
*/
public static create(duration: number, bezierPoints: cc.Vec2[], easing?: (t: number) => number): UniformBezierAction {
return new UniformBezierAction(duration, bezierPoints, easing);
}
}
调用的时候直接UniformBezierAction.create(1, bezierArr, cc.easing.quartOut);