Unity中我们常见一些有弧度的曲线 足球射门之类的我们可以用到贝塞尔曲线
数学原理
看起来类似unity自带的Lerp一样
从绿色线的头部到尾部 同时绿色线的头从P0→P1
绿色线的尾从P1-P2
然后我们发现三次两个绿线又出来个蓝线 根据蓝线移动
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的长度 越长越精确计算量越大
速度就是移动到每一个点的速度(也是遍历速度)
这个要运行后勾选才可以动 因为一运行就动会卡顿一下 看着不爽 就麻烦点改为手动控制开始移动
但是有一个问题 他们移动时间是固定的 在除了两球距离其他不变的情况下 两个球距离越短 就看起来速度越慢 两球距离越长 速度就越快
然后开始优化
根据长度自动设置区间 (这个要找自己适应的规律) 控制最高点的球自动设置到两球中央一定高度
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就差不多
这个是项目源码
https://github.com/1004019267/SecondOrderBezier/tree/master