轉自:http://blog.csdn.net/zhangxiao13627093203/article/details/47658673
上一篇講到了追蹤算法的比較簡單的形式,看上去比較假,因為AI控制的對象過於精確地跟蹤目標。一種更自然的追蹤方式可以這樣做,使得跟蹤者的方向矢量與從跟蹤目標的中心到跟蹤者的中心所定義的方向矢量靠攏。如圖所示:


這個算法的基本思路是這樣的:假設AI控制的對象即追蹤者有如下屬性
1、Position:(tracker.x,tracker.y)
2、Velocity:(tracker.vx,tracker.vy)
追蹤目標有如下屬性:
1、Postion:(target.x,target.y)
2、Velocity:(target.vx,target.vy)
接下來就是調整追蹤者的速度向量的常用邏輯:
1、計算從跟蹤者到跟蹤目標的向量:TV=(target.x-tracker.x,target.y-tracker.y)=(tvx,tvy),歸一化TV,這樣就可以得到一個單位向量,從而方便計算它與坐標軸的角度。歸一化也即是sqrt(x^2+y^2).
2、調整追蹤者當前的速度向量,加上一個按rate比例縮放過的TV*
tracker.x+=rate*tvx;
traker.y+=rate*tvy;
注意這一步才是關鍵,它使得導彈的追蹤不在是從前的直接緊密追蹤而是會有一個變軌跡的過程,另外當rate等於1的時候,跟蹤向量會合的更快,跟蹤算法對目標跟蹤的根據緊密,並更快地的修正目標的運動。
3、跟蹤者的速度向量修改過后,有可能向量的速度會溢出最大值。換言之,跟蹤者一旦鎖定了目標的方向就會繼續沿着該方向加速。所以需要設置一個上限,讓追蹤者的速度從某處慢下來
在Unity5.1.1實現的效果圖如圖所示:
在這里我還調整了導彈的追蹤方向rotation的變化,其實如果是3D空間就可以直接使用lookAt方法來使得導彈的運動方向始終朝向目標,但是在2D平面上就沒有這么好的方法供我們調用了,所以我自己寫了一個算法。如果不加這個方向修正算法的結果如圖所示:
導彈的運行是不是顯得非常的生硬,運動的軌跡和導彈頭的朝向並不一致,這一段的修正導彈頭的方向的代碼如下:
void LookAtTarget()
{
float zAngles;
if(moveVy==0)
{
zAngles = moveVx >= 0 ? -90 : 90;
}
zAngles = Mathf.Atan(moveVx / moveVy) * (-180 / Mathf.PI);
if(moveVy<0)
{
zAngles = zAngles - 180;
}
Vector3 tempAngles = new Vector3(0, 0, zAngles);
Quaternion tempQua = this.transform.rotation;
tempQua.eulerAngles = tempAngles;
this.transform.rotation = tempQua;
}
算法的計算思路是:
注意:這個平面上的角度主要是Z軸的角度變化,而Z軸的角度是導彈頭方向直線與y軸的夾角,這點比較蛋疼。另外坐標的頂點是位於屏幕的左上角。
1、根據導彈的運動速度矢量來調整導彈頭的方向
2、導彈的速度矢量為x和y方向的矢量和,根據反三角函數來計算出導彈與屏幕坐標y軸的夾角

3、要特別注意當moveVy為0的情況,不考慮這個會導致計算反三角的時候分母為零而因溢出而報錯,以及moveVy小於0的情況,不考慮這個會使得方向剛好會想法。
最終的代碼為:
sing UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class AITrackAdvanced : MonoBehaviour {
public Image target;
public float target_moveSpeed;
public float MIN_trackingRate;//最小的追蹤向量改變率
public float MIN_TrackingDis;
public float MAX_trackingVel;
public float moveVx;//x方向的速度
public float moveVy;//y方向的速度
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
Debug.Log((Mathf.Atan(moveVx / moveVy) * (-180 / Mathf.PI)));
// LookAtTarget();
// this.transform.position += new Vector3(moveVx * Time.deltaTime, moveVy * Time.deltaTime, 0);
MoveTarget();
Track_AIAdvanced();
CheckMoveBoundary();
}
void LookAtTarget()
{
float zAngles;
if(moveVy==0)
{
zAngles = moveVx >= 0 ? -90 : 90;
}
zAngles = Mathf.Atan(moveVx / moveVy) * (-180 / Mathf.PI);
if(moveVy<0)
{
zAngles = zAngles - 180;
}
Vector3 tempAngles = new Vector3(0, 0, zAngles);
Quaternion tempQua = this.transform.rotation;
tempQua.eulerAngles = tempAngles;
this.transform.rotation = tempQua;
}
/// <summary>
/// 通過鍵盤來控制移動目標
/// </summary>
void MoveTarget()
{
float x = Input.GetAxis("Horizontal") * 100;
float y = Input.GetAxis("Vertical") * 100;
//如果超出屏幕范圍則讓它出現在另一面
target.transform.Translate(x * Time.deltaTime * target_moveSpeed, y * Time.deltaTime * target_moveSpeed, 0);
if (target.transform.position.x >= Screen.width)
{
//使用了Image的target.rectTransform.lossyScale.x來表示顯示的圖片寬度
target.transform.position = new Vector3(-target.rectTransform.lossyScale.x, target.transform.position.y, 0);
}
else if (target.transform.position.x < -target.rectTransform.lossyScale.x)
{
target.transform.position = new Vector3(Screen.width, target.transform.position.y, 0);
}
if (target.transform.position.y >= Screen.height)
{
target.transform.position = new Vector3(target.transform.position.x, -target.rectTransform.lossyScale.y, 0);
}
else if (target.transform.position.y < -target.rectTransform.lossyScale.y)
{
target.transform.position = new Vector3(target.transform.position.x, Screen.height, 0);
}
}
/// <summary>
/// 追蹤算法
/// </summary>
void Track_AIAdvanced()
{
//計算與追蹤目標的方向向量
float vx = target.transform.position.x - this.transform.position.x;
float vy = target.transform.position.y - this.transform.position.y;
float length = PointDistance_2D(vx, vy);
//如果達到距離就追蹤
if(length<MIN_TrackingDis)
{
vx = MIN_trackingRate * vx / length;
vy = MIN_trackingRate * vy / length;
moveVx += vx;
moveVy += vy;
//增加一點擾動
if(Random.Range(1,10)==1)
{
vx = Random.Range(-1, 1);
vy = Random.Range(-1, 1);
moveVx += vx;
moveVy += vy;
}
length = PointDistance_2D(moveVx,moveVy);
//如果導彈飛的速度太快就讓它慢下來
if(length>MAX_trackingVel)
{
//讓它慢下來
moveVx *= 0.75f;
moveVy *= 0.75f;
}
}
//如果不在追蹤范圍內,隨機運動
else
{
if(Random.Range(1,10)==1)
{
vx= Random.Range(-2, 2);
vy = Random.Range(-2, 2);
moveVx += vx;
moveVy += vy;
}
length = PointDistance_2D(moveVx, moveVy);
//如果導彈飛的速度太快就讓它慢下來
if (length > MAX_trackingVel)
{
//讓它慢下來
moveVx *= 0.75f;
moveVy *= 0.75f;
}
}
this.transform.position += new Vector3(moveVx * Time.deltaTime, moveVy * Time.deltaTime, 0);
}
/// <summary>
/// 計算從零點到這個點的距離
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
float PointDistance_2D(float x,float y)
{
//使用了泰勒展開式來計算,有3.5%的誤差,直接使用開方計算會比較慢,但是測試了我的電腦好像沒有什么變化可能是數據量不大體現不出來
/*x = Mathf.Abs(x);
y = Mathf.Abs(y);
float mn = Mathf.Min(x, y);//獲取x,y中最小的數
float result = x + y - (mn / 2) - (mn / 4) + (mn / 8);*/
float result = Mathf.Sqrt(x * x + y * y);
return result;
}
void CheckMoveBoundary()
{
//檢測是否超出了邊界
if (this.transform.position.x >= Screen.width)
{
this.transform.position = new Vector3(-this.GetComponent<Image>().rectTransform.lossyScale.x, 0, 0);
}
else if (this.transform.position.x < -this.GetComponent<Image>().rectTransform.lossyScale.x)
{
this.transform.position = new Vector3(Screen.width, this.transform.position.y, 0);
}
if (this.transform.position.y >= Screen.height)
{
this.transform.position = new Vector3(this.transform.position.x, -this.GetComponent<Image>().rectTransform.lossyScale.y, 0);
}
else if (this.transform.position.y < -this.GetComponent<Image>().rectTransform.lossyScale.y)
{
this.transform.position = new Vector3(this.transform.position.x, Screen.height, 0);
}
}
}
最后附上工程的下載地址,里面是我用Unity5.1.1寫的如圖的演示程序,還包括之前兩篇文章中的演示程序。點擊打開鏈接
