在unity3D中常常用線性插值函數Lerp()來在二者之間插值,二者之間能夠是兩個材質之間、兩個向量之間、兩個浮點數之間、兩個顏色之間,其函數原型以下:html
Material.Lerp 插值數組
function Lerp (start : Material, end : Material, t : float) : voiddom
在兩個材質之間插值ide
Vector2.Lerp 插值函數
static function Lerp (from : Vector2, to : Vector2, t : float) : Vector2動畫
兩個向量之間的線性插值。按照數字t在form到to之間插值。this
t是夾在0到1之間。當t=0時,返回from。當t=1時,返回to。當t=0.5時放回from和to之間的平均數。 spa
Vector3.Lerp 插值設計
static function Lerp (from : Vector3, to : Vector3, t : float) : Vector3 3d
兩個向量之間的線性插值。按照數字t在from到to之間插值。
Vector4.Lerp 插值
static function Lerp (from : Vector4, to : Vector4, t : float) : Vector4
兩個向量之間的線形插值。按照數字t在from到to之間插值。t是夾在[0...1]之間的值。,當t = 0時,返回from。當t = 1時,返回to。當t = 0.5 返回from和to的平均數。
Mathf.Lerp 插值
static function Lerp (from : float, to : float, t : float) : float
基於浮點數t返回a到b之間的插值,t限制在0~1之間。當t = 0返回from,當t = 1 返回to。當t = 0.5 返回from和to的平均值。
Color.Lerp 插值
static function Lerp (a : Color, b : Color, t : float) : Color
經過t在顏色a和b之間插值。
"t"是夾在0到1之間的值。當t是0時返回顏色a。當t是1時返回顏色b。
插值,從字面意思上看,就是在其間插入一個數值,這種理解是否正確呢?咱們先從最簡單的浮點數插值函數來分析:
Mathf.Lerp 插值
static function Lerp (from : float, to : float, t : float) : float
基於浮點數t返回a到b之間的插值,t限制在0~1之間。當t = 0返回from,當t = 1 返回to。當t = 0.5 返回from和to的平均值。
首先,咱們來作一個試驗,啓動Unity3D,任建一個腳本文件,在其Start()中輸入內容以下:
void Start () {
print(Mathf.Lerp(0.0f, 100.0f, 0.0f).ToString());
print(Mathf.Lerp(0.0f, 100.0f, 0.1f).ToString());
print(Mathf.Lerp(0.0f, 100.0f, 0.2f).ToString());
print(Mathf.Lerp(0.0f, 100.0f, 0.3f).ToString());
print(Mathf.Lerp(0.0f, 100.0f, 0.4f).ToString());
print(Mathf.Lerp(0.0f, 100.0f, 0.5f).ToString());
print(Mathf.Lerp(0.0f, 100.0f, 0.6f).ToString());
print(Mathf.Lerp(0.0f, 100.0f, 0.7f).ToString());
print(Mathf.Lerp(0.0f, 100.0f, 0.8f).ToString());
print(Mathf.Lerp(0.0f, 100.0f, 0.9f).ToString());
print(Mathf.Lerp(0.0f, 100.0f, 1.0f).ToString());
}
運行Unity,在控制檯將打印出:
這個實驗是在0到100之間插值,插入什麼值,取決於第3個參數,從打印結果可看出,第3個參數是個比例因數,是0.1時表示0到100這個長度的十分之一,同理,0.2表示十分之二,依此類推。從這點上看來,咱們起初從字面上所理解的插值就是插入一個數值是能夠這樣理解的。
若是咱們把上面那個腳本里的插值函數裏的第一個參數變爲100.0f,第二個參數變爲110.0f,第三個參數保持不變,你們想一想其運行結果該是什麼呢?可不要認爲是0、1、2、3、4、5、6、7、8、9、10了喲,實際結果是100、101、102、103、104、105、106….,因插值是把值插在原來的兩數之間,這說明這個函數首先是根據第三個參數所給定的比例算出淨增量,再加上起始數,最終算出插值值的。
在Unity3D遊戲開發中,應用最多的是Vector3.Lerp 向量插值,下面咱們以此插值來猜推其內部實現機理以及一些應用。
如圖,在空間中存在兩點A(0,10,0)與B(10,0,-10),咱們在A、B兩點間插入一C點,假設C點的位置在AB的五分之二處,即AC/AB=0.4,根據類似圖形對應邊成比例的初中幾何知識可知,在⊿ABO中AC/AB=OD/OB,同理在⊿OBF中OD/OB=OE/OF,因此AC/AB=OD/O=OE/OF = 0.4,則C點的X座標值爲:OE=0.4*OF=0.4*10=4。
根據上圖,還可知ED/FB=0.4,因此C點的Z座標值DE=0.4*BF=0.4*(-10)=-4。
C點的Y座標值請看下圖:10,-10,-10=4,-4,-4
O/AO=DF/AF=CB/AC=1-0.4=0.6,則C點的Y座標值EO=0.6*AO=0.6*10=6。
綜上所述,C點的三維座標爲C(4,6,-4)。
下面咱們利用Unity3D中的Vector3.Lerp 插值函數:static function Lerp (from : Vector3, to : Vector3, t : float) : Vector3來計算上面演算的插值。
咱們把先前腳本中的Start()函數改寫成:
void Start()
{ print(Vector3.Lerp(new Vector3(0, 10, 0), new Vector3(10, 0, -10), 0.4f).ToString()); }
其運行結果爲:
這與咱們的演算結果是一致的。
上面的演算,咱們爲了簡便,A、B兩點取得較特殊,下降了演算的複雜度。而對普通的A、B兩點,以下圖所示:
咱們一樣能夠獲得三角形EGL與三角形EFK,使用一樣的方法可計算出HI的長度,再加上OH的長度就是C點的X座標值了。一樣的方法可推演出Y與Z的座標。
手工計算是很複雜的,而Lerp函數能夠高效地爲咱們返回這個插值的,咱們在這裏作出的演算,只是幫助咱們來推測Lerp這個函數的內部實現機理而也,實際運用中,一切工做都是交於Lerp函數去完成。
Lerp函數在遊戲開發過程使用較多,在Unity的幫助文檔裏就有爲咱們列舉了Vector3.Lerp的兩個應用的例子,一個是在1秒時間動畫位置移動從start.position開始到end.position結束:
using UnityEngine; using System.Collections; public class example : MonoBehaviour { public Transform start; public Transform end; void Update() { transform.position = Vector3.Lerp(start.position, end.position, Time.time); } }
另外一個例子:
//像彈簧同樣跟隨目標物體 using UnityEngine; using System.Collections; public class example : MonoBehaviour { public Transform target; public float smooth = 5.0F; void Update() { transform.position = Vector3.Lerp(transform.position, target.position, Time.deltaTime * smooth); } }
這個例子中的transform.position是去跟隨的那個物體的空間座標,target.position是目標物體的空間座標,整句的結果是讓跟隨物體的座標不斷地變化爲它們二者之間的插值,然而隨着時間的推移,第三個參數的值最終會爲1,因此最終跟隨物體的位置會與目標物體重合的。咱們之前所玩的遊戲中,主人公身上依附着一隻寵物如鷹,主人公移動時,鷹會跟隨着飛動,主人公移動得快它就飛行跟動得快,始終不會離開主人公,使用Lerp插值函數就可實現。
下面咱們來看另外一個應用實例。
這是酷跑遊戲場景,囚犯沿着一條森林道路向前奔跑,後面有警車追趕,前面有路障,在遊戲過程當中,咱們要在囚犯奔跑的固定路線上隨機產生路障,而道路不是平直的,既左右彎曲,又上下起伏,由程序隨機生成的路障怎樣肯定其空間位置呢?這時,Lerp函數就派上了用場。
先根據道路的彎曲與起伏,在轉折處設置一個空物體,此空物體的Position值即空間座標與此處道路一致,咱們把這些空物體所在的點稱爲道路轉折點,這些點鏈接而成的線段所組成的多段折線貼合在路面上,是這條道路的近似路徑,這些點取得越多、越準確,這條路徑與道路的類似程度就越高。
如今咱們用那條路徑來代替那條道路,把隨機產生的路障放在這條路徑上也就是放在道路上了。
假設咱們想每隔100米至200米之間產生一個路障,用變量z += Random.Range(100, 200)記錄下該路障的Z座標值(因囚犯整體上是沿着Z軸往前跑)而後根據此Z座標值判斷該座標值在前面所設置的轉折點中的哪兩個點之間,找到後就在這兩個點之間插值,其插值的比例因數(Lerp()函數的第3個參數)可由兩個轉折點與這個插值點這三個點中已知的Z座標值算出來,這樣Vector3.Lerp (from : Vector3, to : Vector3, t : float)函數中的三個參數值便都是已知的了,它就可計算出這個插值點的空間座標了,根據前面的設計,這兩個轉折點之間的線段是貼合在路面上的,那麼此插值的座標也就是在路面上了,根據此插值放置的路障也就不會偏離道路,且會隨着道路的左轉而左轉,右轉而右轉,上坡而上坡,下坡而下坡了。
具體設計過程以下。
導入道路模型,假設命名爲forest_1。模型設計時就肯定好了其長度爲3000、座標原點在其終端上了的。導入後咱們將其沿Z軸正方向放置在場景中,讓其Transorm.Position的X、Y值均爲0。咱們能夠導入多段同類型的道路模型,經過控制它們的Z值來把它們拼接成長長的森林道路。
在此道路物體上新建一個空物體做爲它的子物體,命名爲waypoint,再在其下創建多個爲空的孫物體,分別命名爲waypoint_01、waypoint_02……,把它們放在道路的轉折處,並經過放大、旋轉場景圖後細調這些孫物體的座標值,使它們與道路路面貼合,以下圖所示:
說明:圖中的綠色按鈕狀塊就是這些孫物體,因它們是空物體,不能顯示在場景中,是經過屬性面板給它們設置了一個供編輯時顯示使用的圖標標示。
這樣,咱們便把彎彎曲曲的道路分紅了一段一段的直路段,並記錄下來了各段路段兩端的特徵點的座標值。有了這些特徵點,也就有了與道路相近的路線了。這是化曲爲直的方法,把彎曲、起伏的道路化成了與此相近的一段一段的線段。這樣的點越多,其類似程度越高。
在waypionts上建立一個腳本組件waypionts.cs:
using UnityEngine; using System.Collections; public class waypoints : MonoBehaviour { public Transform[] points; void OnDrawGizmos(){ iTween.DrawPath (points); } }
public Transform[] points;該句所定義的points就是存放那些特徵點的數組,因它是public,可在Unity編輯界面中爲其賦值,其操做方法是先在Hierarchy視圖中選中waypoints控件,而後在其Inspector視圖中點擊圖標鎖住其Inspector面板,而後在Hierarchy視圖中全選waypoint_01至waypiont_11後拖到屬性面板上的數組名points上便可完成賦值,以下圖:
接下來,在這個森林道路上創建的Forestcs.cs腳本組件裏添加生成路障的腳本:
using UnityEngine; using System.Collections; public class Forest : MonoBehaviour { public GameObject[] obstacles; //路障物體數組 public float startLength = 50; //路障在道路上出現的開始位置 public float minLength = 100; //路障距上一個路障的最小距離 public float maxLength = 200; //路障距上一個路障的最大距離 private Transform player; //遊戲主人公-奔跑者的Transform組件 private waypoints wayPoints; //與路面相貼合的路線上的腳本組件 void Awake() { player = GameObject.FindGameObjectWithTag(Tags.player).transform; //找到遊戲主人公-奔跑者並得到它的Transform組件 wayPoints = transform.Find("waypoints").GetComponent<waypoints>(); //找到與路面相貼合的路線上的腳本組件 } // Use this for initialization void Start() { GenerateObstacle(); //當森林道路被建立出來時,就會自動調用此Start()方法,從而調用此GenerateObstacle()方法 } // 若是主人公跑完了這段道路,則通知GenerateForest類開始運行產生新的道路,並銷燬已跑完的這條道路 void Update () { if (player.position.z > transform.position.z+100) { Camera.main.SendMessage("GenerateForest"); GameObject.Destroy(this.gameObject); } } void GenerateObstacle(){ float startZ = transform.position.z - 3000; //當前道路在場景中的起始Z座標 float endZ = transform.position.z; //當前道路在場景中的結束Z座標 float z = startZ + startLength; //將要產生的路障的Z座標 while (true) { z += Random.Range(100, 200); //每隔100多米的距離產生一個路障 if (z > endZ) //若是將要產生路障的位置超出了這條道路則退出路障產生循環,不然產生路障 { break; } else { Vector3 position = GetWayPosByz(z); //調用GetWayPosByz()方法計算路障位置座標 int obsIndex = Random.Range(0, obstacles.Length); //產生一個從路障數組裏取路障的隨機序數 GameObject.Instantiate(obstacles[obsIndex], position, Quaternion.identity);//實例化路障 } } } Vector3 GetWayPosByz(float z) { Transform[] points = wayPoints.points; //在道路上設置的轉折點的集合 int index = 0; //轉折點在集合中的序數號 for (int i = 0; i < points.Length-1; i++) { //根據要插入路障的Z值在集合中尋找在哪兩個點之間,找到後記下序數號 if(z<=points[i].position.z && z>= points[i+1].position.z){ index = i; break; } } //使用Lerp函數計算出插入路障處的空間座標值 return Vector3.Lerp(points[index + 1].position, points[index].position, (z - points[index + 1].position.z) / (points[index].position.z - points[index + 1].position.z)); } }