写了个简单的支持缓动的匀速贝赛尔曲线运动

最近工作需要,写了个匀速的贝赛尔曲线运动,用来替代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);

6赞