之前想做一个按照固定的路线进行移动的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;
}