unity利用高阶贝塞尔曲线进行的轨道移动

之前想做一个按照固定的路线进行移动的demo,就想到了路径的曲线,然后就想到了贝塞尔曲线:
先上贝塞尔通用公式:
网上找的贝塞尔曲线公式
借鉴网上的代码和相应的函数公式,组成了一个demo
通用的贝塞尔曲线工具类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BezierUtils
{
    
    

    /// <summary>
    /// 线性贝塞尔曲线,根据T值,计算贝塞尔曲线上面相对应的点
    /// </summary>
    /// <param name="t"></param>T值
    /// <param name="p0"></param>起始点
    /// <param name="p1"></param>控制点
    /// <returns></returns>根据T值计算出来的贝赛尔曲线点
    private static Vector3 CalculateLineBezierPoint(float t, Vector3 p0, Vector3 p1)
    {
    
    
        float u = 1 - t;

        Vector3 p = u * p0;
        p += t * p1;

        return p;
    }

    /// <summary>
    /// 二次贝塞尔曲线,根据T值,计算贝塞尔曲线上面相对应的点
    /// </summary>
    /// <param name="t"></param>T值
    /// <param name="p0"></param>起始点
    /// <param name="p1"></param>控制点
    /// <param name="p2"></param>目标点
    /// <returns></returns>根据T值计算出来的贝赛尔曲线点
    private static Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2)
    {
    
    
        float u = 1 - t;
        float tt = t * t;
        float uu = u * u;

        Vector3 p = uu * p0;
        p += 2 * u * t * p1;
        p += tt * p2;

        return p;
    }

    /// <summary>
    /// 三次贝塞尔曲线,根据T值,计算贝塞尔曲线上面相对应的点
    /// </summary>
    /// <param name="t">插量值</param>
    /// <param name="p0">起点</param>
    /// <param name="p1">控制点1</param>
    /// <param name="p2">控制点2</param>
    /// <param name="p3">尾点</param>
    /// <returns></returns>
    private static Vector3 CalculateThreePowerBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
    {
    
    
        float u = 1 - t;
        float tt = t * t;
        float uu = u * u;
        float ttt = tt * t;
        float uuu = uu * u;

        Vector3 p = uuu * p0;
        p += 3 * t * uu * p1;
        p += 3 * tt * u * p2;
        p += ttt * p3;

        return p;
    }


    /// <summary>
    /// 获取存储贝塞尔曲线点的数组
    /// </summary>
    /// <param name="startPoint"></param>起始点
    /// <param name="controlPoint"></param>控制点
    /// <param name="endPoint"></param>目标点
    /// <param name="segmentNum"></param>采样点的数量
    /// <returns></returns>存储贝塞尔曲线点的数组
    public static Vector3[] GetLineBeizerList(Vector3 startPoint, Vector3 endPoint, int segmentNum)
    {
    
    
        Vector3[] path = new Vector3[segmentNum];
        for (int i = 1; i <= segmentNum; i++)
        {
    
    
            float t = i / (float)segmentNum;
            Vector3 pixel = CalculateLineBezierPoint(t, startPoint, endPoint);
            path[i - 1] = pixel;
            Debug.Log(path[i - 1]);
        }
        return path;

    }

    /// <summary>
    /// 获取存储的二次贝塞尔曲线点的数组
    /// </summary>
    /// <param name="startPoint"></param>起始点
    /// <param name="controlPoint"></param>控制点
    /// <param name="endPoint"></param>目标点
    /// <param name="segmentNum"></param>采样点的数量
    /// <returns></returns>存储贝塞尔曲线点的数组
    public static Vector3[] GetCubicBeizerList(Vector3 startPoint, Vector3 controlPoint, Vector3 endPoint, int segmentNum)
    {
    
    
        Vector3[] path = new Vector3[segmentNum];
        for (int i = 1; i <= segmentNum; i++)
        {
    
    
            float t = i / (float)segmentNum;
            Vector3 pixel = CalculateCubicBezierPoint(t, startPoint,
                controlPoint, endPoint);
            path[i - 1] = pixel;
            Debug.Log(path[i - 1]);
        }
        return path;

    }

    /// <summary>
    /// 获取存储的三次贝塞尔曲线点的数组
    /// </summary>
    /// <param name="startPoint"></param>起始点
    /// <param name="controlPoint1"></param>控制点1
    /// <param name="controlPoint2"></param>控制点2
    /// <param name="endPoint"></param>目标点
    /// <param name="segmentNum"></param>采样点的数量
    /// <returns></returns>存储贝塞尔曲线点的数组
    public static Vector3[] GetThreePowerBeizerList(Vector3 startPoint, Vector3 controlPoint1, Vector3 controlPoint2, Vector3 endPoint, int segmentNum)
    {
    
    
        Vector3[] path = new Vector3[segmentNum];
        for (int i = 1; i <= segmentNum; i++)
        {
    
    
            float t = i / (float)segmentNum;
            Vector3 pixel = CalculateThreePowerBezierPoint(t, startPoint,
                controlPoint1, controlPoint2, endPoint);

            path[i - 1] = pixel;
            Debug.Log(path[i - 1]);
        }
        return path;

    }
    /// <summary>
    /// 高阶贝塞尔曲线
    /// </summary>
    /// <param name="posArr">坐标数组</param>
    /// <param name="t">所占比例t为0-1之间,起始点到终点的路径的比例</param>
    /// <returns></returns>
    public static Vector3 Bezier(Vector3[] posArr, float t)
    {
    
    
        Vector3 localPos = Vector3.zero;
        int n = posArr.Length - 1;
        for (int i = 0; i <= n; i++)
        {
    
    
            Vector3 item = posArr[i];
            int index = i;
            if (index == 0)
            {
    
    
                localPos += item * Mathf.Pow((1 - t), n - index) * Mathf.Pow(t, index);
            }
            else
            {
    
    
                float m1 = fact(n);
                float m2 = fact(index);
                float m3 = fact(n - index);
                float m4 = Mathf.Pow((1 - t), n - index);
                float m5 = Mathf.Pow(t, index);
                localPos += m1 / m2 / m3 * item * m4 * m5;
            }
        }
        return localPos;
    }

    public static float fact(int n)
    {
    
    
        if (n == 0)
        {
    
    
            return 1.0f;
        }
        else
        {
    
    
            float f = 1;
            for (int i = 1; i <= n; i++)
            {
    
    
                f *= i;
            }
            return f;
        }
    }
    public static Vector3[] GetPowerBeizerList(Vector3[] PosArr, int segmentNum)
    {
    
    
        Vector3[] path = new Vector3[segmentNum];
        for (int i = 1; i <= segmentNum; i++)
        {
    
    
            float t = i /(float)segmentNum;
            Vector3 pixel = Bezier(PosArr, t);
            path[i - 1] = pixel;
            Debug.Log(path[i - 1]);
        }
        return path;
    }

}

我这边用了两个脚本来实现的,一个是画线的,LineRender:

using UnityEngine;

[RequireComponent(typeof(LineRenderer))]
public class BezierDemo : MonoBehaviour
{
    
    
    // 三次贝塞尔控制点
    public Transform[] controlPoints;

    // LineRenderer 
    private LineRenderer lineRenderer;
    private int layerOrder = 0;

    // 设置贝塞尔插值个数
    private int _segmentNum = 50;


    void Start()
    {
    
    
        if (!lineRenderer)
        {
    
    
            lineRenderer = GetComponent<LineRenderer>();
        }
        lineRenderer.sortingLayerID = layerOrder;
        DrawThreePowerCurve();

    }

    Vector3[] points3;
    void DrawThreePowerCurve()
    {
    
    
        Vector3[] localpos = new Vector3[controlPoints.Length];
        for (int i = 0; i < controlPoints.Length; i++)
        {
    
    
            localpos[i] = controlPoints[i].position;
        }
        // 获取三次贝塞尔方程曲线
        points3 = BezierUtils.GetPowerBeizerList(localpos, _segmentNum);
        // 设置 LineRenderer 的点个数,并赋值点值
        lineRenderer.positionCount = (_segmentNum);
        lineRenderer.SetPositions(points3);

    }

}

一个是移动的脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class test : MonoBehaviour
{
    
    
    public Transform[] TranArr;
    public Vector3[] PosArr;
    private void Start()
    {
    
    
        
        TranArr = GetComponent<BezierDemo>().controlPoints;
        Vector3[] _posarr = new Vector3[TranArr.Length];
        for(int i=0;i< TranArr.Length;i++)
        {
    
    
            _posarr[i] = TranArr[i].position;
        }
        PosArr = BezierUtils.GetPowerBeizerList(_posarr, 20);
    }
    int index = 0;
    private void Update()
    {
    
    
        if (Vector3.Distance(PosArr[index], transform.position) <= 0.1f) index++;
        if (index >= PosArr.Length) return;
        Vector3 dir = (PosArr[index] - transform.position).normalized;
        //transform.position = Vector3.Slerp(transform.position, PosArr[index], Time.deltaTime);
        transform.position += dir * 5 * Time.deltaTime;
    }
    private void OnCollisionEnter(Collision collision)
    {
    
    
        
    }
}

然后直接在起始的用来移动的物体上面挂脚本,就可以了:
在这里插入图片描述
参考:
高阶贝塞尔曲线
贝塞尔曲线工具类

更新:今天项目中出现了一个问题,fact方法参数n>=35后,超出了float计算的精度,需要改成double类型的,所以改一下:

public static double fact(int n)
{
    
    
    if (n == 0)
    {
    
    
        return 1.0f;
    }
    else
    {
    
    
        double f = 1;
        for (int i = 1; i <= n; i++)
        {
    
    
            f *= i;
        }
        return f;
    }
}
public static Vector3 Bezier(Vector3[] posArr, float t)
{
    
    
    Vector3 localPos = Vector3.zero;
    int n = posArr.Length - 1;
    for (int i = 0; i <= n; i++)
    {
    
    
        Vector3 item = posArr[i];
        int index = i;
        if (index == 0)
        {
    
    
            localPos += item * Mathf.Pow((1 - t), n - index) * Mathf.Pow(t, index);
        }
        else
        {
    
    
            double m1 = fact(n);
            double m2 = fact(index);
            double m3 = fact(n - index);
            double m4 = Mathf.Pow((1 - t), n - index);
            double m5 = Mathf.Pow(t, index);
            double m6 = (m1 / m2 / m3) * m4 * m5;
            localPos += new Vector3((float)m6 * item.x, (float)m6 * item.y, (float)m6 * item.z);
        }
    }
    return localPos;
}

猜你喜欢

转载自blog.csdn.net/QO_GQ/article/details/116711205