RT,馬三最近在參與一款足球游戲的開發,其中涉及到足球的各種運動軌跡和路徑,比如射門的軌跡,高吊球,香蕉球的軌跡。最早的版本中馬三是使用物理引擎加力的方式實現的足球各種運動,后來的版本中使用了根據物理學公式手動計算位置和物體速度的方式實現,現在這個版本中使用的是DoTween+貝塞爾曲線調節來實現。(關於它們之間的各種優缺點我們會在以后單獨開一篇博客來探討,屆時也會放出源代碼互相學習下)好了,言歸正傳,今天馬三就來和大家一起學習一下游戲中的貝塞爾曲線以及其在Unity中如何實現。
一、簡介
貝塞爾曲線是最基本的曲線,一般用在計算機 圖形學和 圖像處理。貝塞爾曲線可以用來創建平滑的曲線的道路、 彎曲的路徑就像 祖瑪游戲、 彎曲型的河流等。
一條貝塞爾曲線是由一組定義的控制點 P0到 Pn,在 n 調用它的順序 (n = 1 為線性,2 為二次,等.)。第一個和最后一個控制點總是具有終結點的曲線;然而,中間兩個控制點 (如果有的話) 一般不會位於曲線上 。
(1)貝塞爾曲線包含兩個控制點即 n = 2 稱為線性的貝塞爾曲線
(2)貝塞爾曲線包含三個控制點即 n = 3 稱為二次貝塞爾曲線
(3)貝塞爾曲線包含四個控制點即 n = 4,所以稱為三次貝塞爾曲線。
貝塞爾曲線返回點的貝塞爾函數,使用線性插值的概念作為基礎。
二、公式
1.線性貝塞爾公式:
給定點P0、P1,線性貝茲曲線只是一條兩點之間的直線。這條線由下式給出:

其等同於線性插值。
效果圖(文章中部分圖片轉載自CSDN):

2.二次貝塞爾公式:
二次方貝茲曲線的路徑由給定點P0、P1、P2控制,這條線由下式給出:

效果圖:

3.三次貝塞爾方程:
4.一般參數形式的貝塞爾方程:
N階貝茲曲線可如下推斷。給定點P0、P1、…、Pn,其貝茲曲線即:

通過兩個低階的貝塞爾曲線插值的堆疊總能夠獲得更高階的貝塞爾曲線,通俗的來說通過對兩條低階的貝塞爾曲線插值,你可以求得一條高一階的貝塞爾曲線。
比如:二次貝塞爾曲線是點對點的兩個線性貝塞爾曲線的線性插值,三次貝塞爾曲線是兩條二次貝塞爾曲線的線性插值。
三、實現與應用
效果圖:


通過調節起始點(左邊的白球)、控制點(中間的白球)和結束點(右邊的白球)可以獲得到不同的貝塞爾曲線,然后使用LineRender組件將路徑繪制出來,以方便觀察。下面就是實現此功能的代碼:
1 using UnityEngine; 2 using System.Collections.Generic; 3 [RequireComponent(typeof(LineRenderer))] 4 public class Bezier : MonoBehaviour 5 { 6 public Transform[] controlPoints; 7 public LineRenderer lineRenderer; 8 9 private int layerOrder = 0; 10 private int _segmentNum = 50; 11 12 13 void Start() 14 { 15 if (!lineRenderer) 16 { 17 lineRenderer = GetComponent<LineRenderer>(); 18 } 19 lineRenderer.sortingLayerID = layerOrder; 20 } 21 22 void Update() 23 { 24 25 DrawCurve(); 26 27 } 28 29 void DrawCurve() 30 { 31 for (int i = 1; i <= _segmentNum; i++) 32 { 33 float t = i / (float)_segmentNum; 34 int nodeIndex = 0; 35 Vector3 pixel = CalculateCubicBezierPoint(t, controlPoints[nodeIndex].position, 36 controlPoints[nodeIndex+1].position, controlPoints[nodeIndex+2].position); 37 lineRenderer.numPositions = i; 38 lineRenderer.SetPosition(i - 1, pixel); 39 } 40 41 } 42 43 Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2) 44 { 45 float u = 1 - t; 46 float tt = t * t; 47 float uu = u * u; 48 49 Vector3 p = uu * p0; 50 p += 2 * u * t * p1; 51 p += tt * p2; 52 53 return p; 54 } 55 56 }
CalculateCubicBezierPoint()函數負責根據T值計算出對應的貝塞爾曲線中的點,DrawCurve()函數通過不斷的改變T值,並調用CalculateCubicBezierPoint()獲得坐標點,然后通過LineRenderer將這些點繪制出來。
為了使用方便,可以將計算貝賽爾曲線的方法放到一個工具類中——BezierUtils類:
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 5 public class BezierUtils 6 { 7 /// <summary> 8 /// 根據T值,計算貝塞爾曲線上面相對應的點 9 /// </summary> 10 /// <param name="t"></param>T值 11 /// <param name="p0"></param>起始點 12 /// <param name="p1"></param>控制點 13 /// <param name="p2"></param>目標點 14 /// <returns></returns>根據T值計算出來的貝賽爾曲線點 15 private static Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2) 16 { 17 float u = 1 - t; 18 float tt = t * t; 19 float uu = u * u; 20 21 Vector3 p = uu * p0; 22 p += 2 * u * t * p1; 23 p += tt * p2; 24 25 return p; 26 } 27 28 /// <summary> 29 /// 獲取存儲貝塞爾曲線點的數組 30 /// </summary> 31 /// <param name="startPoint"></param>起始點 32 /// <param name="controlPoint"></param>控制點 33 /// <param name="endPoint"></param>目標點 34 /// <param name="segmentNum"></param>采樣點的數量 35 /// <returns></returns>存儲貝塞爾曲線點的數組 36 public static Vector3 [] GetBeizerList(Vector3 startPoint, Vector3 controlPoint, Vector3 endPoint,int segmentNum) 37 { 38 Vector3 [] path = new Vector3[segmentNum]; 39 for (int i = 1; i <= segmentNum; i++) 40 { 41 float t = i / (float)segmentNum; 42 Vector3 pixel = CalculateCubicBezierPoint(t, startPoint, 43 controlPoint, endPoint); 44 path[i - 1] = pixel; 45 Debug.Log(path[i-1]); 46 } 47 return path; 48 49 } 50 }
通過調用 GetBeizerList( )方法就可以獲得到一個包含着計算出的貝塞爾曲線的數組,然后讓Obejct沿着數組里面的路徑移動就可以模擬出各種曲線運動的效果了,比如炮彈的飛行軌跡,香蕉球、弧圈球等等各種各樣的曲線效果了,比如下面的效果圖:



博客中貝塞爾曲線工程的開源地址:https://github.com/XINCGer/Unity3DTraining/tree/master/BezierTest
作者:馬三小伙兒
出處:http://www.cnblogs.com/msxh/p/6270468.html
請尊重別人的勞動成果,讓分享成為一種美德,歡迎轉載。另外,文章在表述和代碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!
