實現(xiàn)子彈追蹤目標有很多種方法,首先是一開始就選定了目標的位置,然后按照曲線運動軌跡的方式,持續(xù)運動到目標點,不過如果目標移動了,就得將對應的軌跡重新計算一次,另外如果需要設(shè)置范圍的話更不好做。另一種是銳角追蹤,就是在目標進入識別范圍后,將子彈的旋轉(zhuǎn)方向朝向目標,但是這會使得子彈的拐彎看起來非常的突兀,舉個列子就是如果敵人時在子彈后進入了識別范圍,那么就會導致子彈突然來個180度大轉(zhuǎn)彎,很不美觀,還有一種就是給子彈施加一個朝向目標力,問題是會帶來子彈的速度大小改變,繼而還要標準化速度,而且追蹤的定位可能不準,因為子彈有初速度。
我所用的追蹤是在銳角轉(zhuǎn)彎上的思路改進,就是將修改子彈的旋轉(zhuǎn)朝向向目標位置但是每次只更改一點角度,直到子彈方向指向目標后將不再旋轉(zhuǎn)。
把大概的效果圖放一下先~

考慮到追蹤的范圍識別問題,有兩個解決方法,一種是用collider,但是當子彈非常多的時候性能開銷可能會很大,導致掉幀。另一種是代碼替代,這個之后詳細再講,不過我的相比碰撞也不會節(jié)省多少......
先來講講使用碰撞箱的這種較為容易理解的邏輯
我用上的屬性:
? ? [Range(0, 100)]
[SerializeField]
private int hurt;//傷害
[Range(0, 100)]
[SerializeField]
private float speed;//速度
private GameObject enemy;//敵人對象
[Range(0, 10)]
[SerializeField]
private float angle_differ;//允許的差異角度
[Range(0, 2)]
[SerializeField]
private float angle_fix;//每次修正的角度
[Range(0, 100)]
[SerializeField]
private float dis_time;//消失時間
想要獲取到要追蹤的物體,就需要一個添加一個圓形碰撞箱,然后勾選其是觸發(fā)器選項,這里的盒體碰撞是為了檢測子彈和敵人發(fā)生碰撞時執(zhí)行傷害事件才使用的,這里不做說明。
當這物體進入碰撞箱后,就將這個物體賦值給enemy,具體代碼如下:
//因為存在不足,我沒用使用這個方法
private void OnTriggerEnter2D(Collider2D collision)
{
? ? ? ? //嘗試過進行獲取多個敵人對象然后選擇對象
? ? ? ? //AI_Move[] enemys = collision.GetComponentsInChildren<AI_Move>();
? ? ? ?
? ? ? ? //判斷是否有敵人組件,否則enemy為null,無法賦值給this.enemy
AI_Move enemy = collision.gameObject.GetComponent<AI_Move>();
if (enemy != null)
{
? ? ? ? ? ? //指定敵人目標
this.enemy = collision.gameObject;
}
}
只要當敵人進入圓形碰撞箱后,就會執(zhí)行這個函數(shù),然后將這個敵人對象賦給enemy,這樣就相當于告訴知道子彈應該追蹤那個物體了。
接下來就是重點,子彈已經(jīng)知道應該去追蹤誰了,那么如何將使得子彈轉(zhuǎn)向敵人的位置呢?首先必須使得子彈在旋轉(zhuǎn)不會影響自身的速度大小。我這里使用的是
transform.Translate(speed * Time.deltaTime, 0, 0);
這個方法來控制子彈的移動。這段代碼是使得子彈沿著自身x軸方向運動,當它的世界旋轉(zhuǎn)方向改變時,x軸的軸向也會變動,這樣使得無論如何改變它的角度也不會改變他的速度大小。
然后執(zhí)行如下代碼
? ? ? ? //檢測到敵人時則執(zhí)行追蹤
if (enemy != null)
{
//對需要追蹤物體和自身間的角度求解運算
Vector2 row = (enemy.transform.position - transform.position).normalized;
//獲取兩物體間夾角
float angle1 = Vector3.SignedAngle(Vector3.up, row, Vector3.forward);
//將夾角坐標和世界坐標的取值和范圍對齊
angle1 = (angle1 + 270) % 360;
//獲取彈幕自身世界坐標
float angle2 = transform.eulerAngles.z % 360;
//獲取兩個角度間的差異值并標準化
float angle3 = ((angle1 - angle2) + 360) % 360;
//對物體的角度做修正,使得物體x軸指向需要追蹤的目標
//朝向需要追蹤對象的方向調(diào)整角度,按照設(shè)定的值進行調(diào)整
if (angle3 < 180 - angle_differ)
{
Quaternion reAngle = Quaternion.Euler(0, 0, transform.eulerAngles.z - angle_fix);
transform.rotation = reAngle;
}
else if (angle3 > 180 + angle_differ)
{
Quaternion reAngle = Quaternion.Euler(0, 0, transform.eulerAngles.z + angle_fix);
transform.rotation = reAngle;
}
}
計算角度使用unity自帶的SignedAngle方法就可以算出兩個物體間的夾角,接下來但是問題在于,夾角使用的取值范圍和世界坐標的旋轉(zhuǎn)的標準不一樣,夾角計算出的結(jié)果返回的是一個-180到180的區(qū)間,而世界坐標則是是0~360的一個區(qū)間而且兩者的起點0位置不同。如果你不清楚這個差異會導致的問題,你可以看看接下來的圖解和不這樣寫子彈的運行軌跡。

上面的A1 A2分別代表angle1和angle2,可以看出,在第二第三第四象限,同樣位置的角度在A1坐標下減去A2,得到的都是-270,但是唯獨在第一坐標下的值為90。同樣的我們看看不對angle1和angle3進行模運算修正的結(jié)果,此時指向角的差值不為180而是-90。我們來看看此時子彈指向物體會出現(xiàn)什么問題。

可以看到,在位于敵人左上方時,指向就發(fā)生了混亂,就是我所說的坐標系標準問題。
有人就會覺得只要簡單的對angle3做360的模運算就可以了,實際上我就這么認為過,然后......

很明顯實際上沒有這樣簡單,子彈仍然在某些位置上會發(fā)生奇怪的偏轉(zhuǎn),需要考慮到16種情況,每一個坐標系上都會有四種。(朝向物體但角度偏大,偏小,背向物體的兩個偏向,因為你總不可能只采用一個方向的角度修正吧,不然明明能往左轉(zhuǎn)就能到非要向右繞一圈回來)
總之,大概就是這樣,如果你仍然不能理解后果,可以自己試一試按照我的方式寫一下會不會出現(xiàn)這種問題,反正當時解決這個問題把我整慘了。
將兩者的坐標的值都對齊后,如何使得子彈的旋轉(zhuǎn)指向物體呢?就像是我們要面對面看著你,相互錯位180度就可以了。如果想要控制子彈的追蹤距離限制,可以設(shè)置圓形碰撞器的大小,修改angle_fix使得每次旋轉(zhuǎn)的角度變化更大或更小,這樣轉(zhuǎn)彎也會越明顯或不明顯,angle_differ其實沒有太大的意義,只是設(shè)置讓它指向差錯角度大小的調(diào)整,可以不使用讓它只鎖定中心。
接下來是第二種方法
之所以我棄用了使用碰撞器的方法,一是性能開銷問題,當發(fā)射大量子彈后,使用圓形碰撞判斷范圍內(nèi)是否有敵人會消耗很大性能,二是我使用的unity版本的圓形碰撞器有些bug,導致失去enemy對象(明明物體還在范圍內(nèi)結(jié)果判定出了碰撞,要不是debug了不然我還不信)三是如果按照我寫的方式,每次單個檢測,一但當有第二個敵人進入碰撞,那么就會覆蓋子彈的enemy就會被覆蓋并指向下一個敵人,條件苛刻點甚至能出現(xiàn)子彈從一堆敵人中繞過去,當然要改代碼也是能解決這個問題的,但是相比這個方法屬實不劃算。
另一種方式來設(shè)置enemy
private void OnArea()
{
//相對上面的方式能夠減少性能開銷,而且能指定選擇的對象
GameObject[] games = GameObject.FindGameObjectsWithTag("Enemy");
//設(shè)置當前最近需要追蹤對象的距離,如果結(jié)束后這個值沒有變說明范圍內(nèi)沒有敵人
float distance = this.distance;
foreach(GameObject game in games)
{
//這句其實是多余的,因為目前測試場景中可被追蹤的敵人都是這個標簽,但是也可以保留
//可以在此基礎(chǔ)上發(fā)展追蹤優(yōu)先級,的比如同距離優(yōu)先鎖定BOSS,或者不能被追蹤的敵人對象
if (game.GetComponent<AI_Move>() != null)
{
//找到場景中指定為敵人的對象,進行距離求值運算
float dis = Vector2.Distance(new(game.transform.position.x, game.transform.position.y),
new(gameObject.transform.position.x, gameObject.transform.position.y));
if (distance > dis)
{
//將最小距離的GameObject對象賦值給需要追蹤的對象enemy,會不斷的循環(huán)更替,最終結(jié)束循環(huán)的時候
//篩選出來的enemy就是距離這個彈幕最近的敵人
distance = dis;
enemy = game;
}
}
}
if (distance == this.distance)
{
//如果沒有找到或者飛行過程中脫離了最大追蹤距離,刪除追蹤對象
enemy = null;
}
}
}
接下來只需要在Update()方法執(zhí)行這個方法就可以了。
這種方式能解決以上的所有問題,而且可以通過設(shè)置time固定時間調(diào)用一次使得不必每幀執(zhí)行這個方法,繼續(xù)減少開銷(......),由于獲取了一個數(shù)組的敵人,可以通過Tag值區(qū)分敵人類型,那些不能被追蹤那些能,還可以在給distance除一個數(shù)分層追蹤的優(yōu)先級,比如同距離優(yōu)先鎖定BOSS等等擴展方法,相對于使用碰撞使用這個實現(xiàn)更容易。
不過子彈追蹤的方式還有很多種,肯定有更好的方法來實現(xiàn)子彈追蹤,不過大概我寫的比較容易理解適合萌新吧,畢竟我也在這個層級......文章來源:http://www.zghlxwxcb.cn/news/detail-459457.html
把追蹤腳本掛載在了一個分裂體子彈上,效果看起來不錯,也很流暢。文章來源地址http://www.zghlxwxcb.cn/news/detail-459457.html

到了這里,關(guān)于Unity2D實現(xiàn)子彈追蹤目標的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!