Unity 中贝赛尔曲线的应用

Unity中我们常见一些有弧度的曲线 足球射门之类的我们可以用到贝塞尔曲线

数学原理

线性贝塞尔曲线
线性贝塞尔曲线

看起来类似unity自带的Lerp一样

二次贝塞尔曲线
二次贝塞尔曲线

从绿色线的头部到尾部 同时绿色线的头从P0→P1
绿色线的尾从P1-P2

三次贝塞尔曲线和N次
三次贝塞尔曲线

然后我们发现三次两个绿线又出来个蓝线 根据蓝线移动

N次的图就没找到 反正就是在一堆点之内控制移动 N次依此类推

Unity应用

直接整一个工具类使用 这是常用的2次贝赛尔曲线

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// 
/// 二次贝塞尔函数工具类
/// 
public class BezierUtils : MonoBehaviour
{
    /// 
    /// 根据T值,计算贝赛尔曲线上面对应的点
    /// 
    /// T值
    /// 起始点
    /// 控制点
    /// 目标点
    /// 根据T值计算出来的贝赛尔曲线
    /// (1-t)2P0+2(1-t)tP1+t2P2
    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;//(1-t)2P0
        p += 2 * u * t * p1;//2(1-t)tP1
        p += tt * p2;//t2P2

        return p;
    }
    /// 
    /// 获取一个贝赛尔曲线点的数组
    /// 
    /// 起始点
    /// 控制点
    /// 目标点
    /// 采样点的数量 精度
    /// 一个贝赛尔曲线的数组
    public static List GetBeizerList(Vector3 startPoint, Vector3 contiolPoint, Vector3 endPoint, int segmentNum)
    {
        List path = new List();
       // Vector3[] path = new Vector3[segmentNum];
        //若长度为5 采样点为 1/5 2/5 3/5 4/5 5/5k
        for (int i = 1; i <= segmentNum; i++)
        {
            float t = i / (float)segmentNum;
            Vector3 pixel = CalculateCubicBezierPoint(t, startPoint, contiolPoint, endPoint);
            path.Add(pixel);           
            Debug.Log(path[i - 1]);
        }
        return path;
    }
}

然后是测试类

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

public class TestBezier : MonoBehaviour
{
    [Header("开始目标")]
    public Transform targe1;
    [Header("控制点")]
    public Transform controlPoint;
    [Header("终点目标")]
    public Transform targe2;
    [Header("精确度")]
    public int accuracy = 5;
    [Header("速度")]
    public float speed = 1;
    float t;
    [Header("是否开始移动")]
    public bool isMove = false;//手动控制
    List path;

    void Awake()
    {
        path = BezierUtils.GetBeizerList(targe1.position, controlPoint.position, targe2.position, accuracy);
    }
    /// 
    /// 寻路模拟
    /// 
    /// 
    public void FindPath(List path)
    {
        t += Time.deltaTime;
        if (t >= speed)
        {
            t = 0;
            if (path != null && path.Count > 0)
            {
                var startPos = path[0];
                targe1.transform.position = startPos;
                path.Remove(startPos);
            }
        }
    }
    // Update is called once per frame
    void Update()
    {
        if (isMove == true)
        {
            FindPath(path);
        }

    }
}

控制点控制的是曲线的弯曲程度
精度就是那个List的长度 越长越精确计算量越大
速度就是移动到每一个点的速度(也是遍历速度)

image.png

这个要运行后勾选才可以动 因为一运行就动会卡顿一下 看着不爽 就麻烦点改为手动控制开始移动

但是有一个问题 他们移动时间是固定的 在除了两球距离其他不变的情况下 两个球距离越短 就看起来速度越慢 两球距离越长 速度就越快

然后开始优化
根据长度自动设置区间 (这个要找自己适应的规律) 控制最高点的球自动设置到两球中央一定高度

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// 
/// 二次贝塞尔函数工具类
/// 
public class BezierUtils 
{
    /// 
    /// 根据T值,计算贝赛尔曲线上面对应的点
    /// 
    /// T值
    /// 起始点
    /// 控制点
    /// 目标点
    /// 根据T值计算出来的贝赛尔曲线
    /// (1-t)2P0+2(1-t)tP1+t2P2
    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;//(1-t)2P0
        p += 2 * u * t * p1;//2(1-t)tP1
        p += tt * p2;//t2P2

        return p;
    }
    /// 
    /// 获取一个贝赛尔曲线点的数组
    /// 
    /// 起始点
    /// 控制点
    /// 目标点
    /// 采样点的数量 精度
    /// 一个贝赛尔曲线的数组
    public static List GetBeizerList(Vector3 startPoint, Vector3 contiolPoint, Vector3 endPoint)
    {
        //计算所合适的区间数 优化远近差值速度 根据自身情况找规律设置 我的是按2D屏幕坐标左到右 从上到下
        var segmentNum =15+ (int)(Mathf.Abs(endPoint.x - startPoint.x) * 0.02f) +(int)(Mathf.Abs(endPoint.y - startPoint.y) * 0.02f);


        List path = new List();
       // Vector3[] path = new Vector3[segmentNum];
        //若长度为5 采样点为 1/5 2/5 3/5 4/5 5/5k
        for (int i = 1; i <= segmentNum; i++)
        {
            float t = i / (float)segmentNum;
            Vector3 pixel = CalculateCubicBezierPoint(t, startPoint, contiolPoint, endPoint);
            path.Add(pixel);           
           // Debug.Log(path[i - 1]);
        }
        return path;
    }

    /// 
    /// 获得他们中心点向上一定距离的点 只适用2D
    /// 
    /// 
    /// 
    /// 
   public static Vector3 GetControlPoint(Vector3 startPoint, Vector3 endPoint, float dValue)
    {
        Vector3 p1 = startPoint;
        Vector3 p2 = endPoint;
        //向量p1p2
        Vector3 p1p2 = (p2 - p1);
        Debug.Log(p1p2);
        //求中心开始向量和结束向量的一半向量
        Vector3 halfP1P2 = p1 + p1p2 * 0.5f;    
        //求他们的旋转向量的方法 把向量p2p1以Z轴旋转90度 如果end在start左边就p1p2为负数改变方向
        Vector3 pVertical = Quaternion.AngleAxis(90, Vector3.forward*p1p2.normalized.x) * p1p2;
        //那么中心点坐标为从一半开始的向量 加上pVertical的方向乘上长度
        Vector3 controlPoint = halfP1P2 + pVertical.normalized * dValue;
        return controlPoint;
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestBezier : MonoBehaviour
{
    public Transform targe3;
    [Header("开始目标")]
    public Transform targe1;
    [Header("控制点高度")]
    public float hight;
    [Header("终点目标")]
    public Transform targe2;
    [Header("速度")]
    public float speed = 1;
    float t;
    [Header("是否开始移动")]
    public bool isMove = false;//手动控制
    List path;

    void Awake()
    {
        var controlPoint = BezierUtils.GetControlPoint(targe1.position,targe2.position,hight);
        targe3.position = controlPoint;
        path = BezierUtils.GetBeizerList(targe1.position, controlPoint, targe2.position);
    }
    /// 
    /// 寻路模拟
    /// 
    /// 
    public void FindPath(List path)
    {
        t += Time.deltaTime;
        if (t >= speed)
        {
            t = 0;
            if (path != null && path.Count > 0)
            {
                var startPos = path[0];            
                targe1.transform.position = startPos;
                path.Remove(startPos);
            }
        }
    }
   
    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            isMove = true;
        }
        if (isMove == true)
        {
            FindPath(path);
        }

    }
}

按空格开始这样 距离远近速度就更平缓 中心点按高度设置 不过只能在两者中点
我觉得高度100 速度0.01就差不多

GIF.gif

这个是项目源码
https://github.com/1004019267/SecondOrderBezier/tree/master

你可能感兴趣的:(Unity 中贝赛尔曲线的应用)