天天看點

Unity3D真實跟蹤的無腦解決方案

最近在寫一個空戰類遊戲,遊戲中需要用到3D場景中的真實跟蹤飛彈模拟。      
找了好多相關的代碼和demo,但不是有缺陷就是邏輯涉及大量三角函數運算讓我這個數學渣無法忍受。
    正在我苦于無法解決的時候一篇關于飛彈技術的文章給了我靈感。
    具體過程我就不說了,還是直接說思路與代碼實作。
    首先,我們要知道,飛彈的跟蹤實際上是由兩部分組成的,即推進力與扭力。
    推進力我們可以通過移動模拟,是以讓大部分人困擾最多的實際上是扭力。
    而真實的飛彈常會因為扭力不足無法命中目标,這就是真實飛彈模拟的關鍵所在。      
我們知道,UNITY為我們提供了非常友善的lookat函數,但lookat隻能實時的瞄準對方,這會使飛彈一定能命中。      
那麼關鍵點來了。我們不用飛彈的彈體lookat目标,而是在飛彈中建立一個子物體,讓子物體lookat目标。我把這個子物體稱為目      
标指向器(僞陀螺儀)。      
子物體朝向目标後會給我們提供旋轉角度的資料,而我們隻需要用MoveTowards以一定的速度讓彈體的旋轉角度不斷迫近這個角度      
就行了。      
當然這會出現一個問題,歐拉角在超過360時會報錯,還有就是當目标指向器的角度從360變到1時會出現bug,但這都很好解決,      
隻是簡單的追擊問題罷了。      
這個問題解決後,我又開始考慮各角度的扭力配置設定,以保證飛彈已最圓滑的曲線追擊目标,最後完成的代碼大概是這樣。      
/// <summary>
/// 2017.07.07
/// zcl1001zcl
/// 蓮風
/// </summary>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class TrackingControl : MonoBehaviour
{
    public float Torsion = 30.0f;
    public float Speed = 5.0f;
    public GameObject m_Gyroscope;          //目标指向器(僞陀螺儀)
    public GameObject Target;               //目标


    // Use this for initialization
    void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
		
	}


    void FixedUpdate()
    {
        //(解釋)讓指向器指向目标
        m_Gyroscope.transform.LookAt(Target.transform) ;
        //(解釋)獲得相關指向器歐拉角資料和自身的歐拉角資料
        Vector3 TrackingMissileEuler = transform.eulerAngles;        
        Vector3 GyroscopeEuler = m_Gyroscope.transform.eulerAngles;
        Vector3 NewTrackingMissileEuler = Vector3.zero;
        Vector3 NewGyroscopeEuler = Vector3.zero;
        Detection(TrackingMissileEuler, GyroscopeEuler, out NewTrackingMissileEuler, out NewGyroscopeEuler);
        float TorsionX = 0;
        float TorsionY = 0;
        float TorsionZ = 0;
        TorqueDecomposition(NewTrackingMissileEuler, NewGyroscopeEuler,  out TorsionX, out TorsionY, out TorsionZ);


        float eulerX = Mathf.MoveTowards(NewTrackingMissileEuler.x, NewGyroscopeEuler.x , TorsionX);
        float eulerY = Mathf.MoveTowards(NewTrackingMissileEuler.y, NewGyroscopeEuler.y , TorsionY);
        float eulerZ = Mathf.MoveTowards(NewTrackingMissileEuler.z, NewGyroscopeEuler.z , TorsionZ);
        //(解釋)%360,防止超範圍
        transform.localEulerAngles = new Vector3(eulerX%360, eulerY%360, eulerZ%360);
        //(解釋)角度調整結束後,讓飛彈延自身的z軸移動
        transform.Translate(new Vector3( 0, 0, Speed * Time.fixedDeltaTime));
    }


    //(解釋)對3對歐拉角做計算,得出3個角之間所占比例
    void TorqueDecomposition(Vector3 TrackingMissileEuler,Vector3 GyroscopeEuler ,out float TorsionX ,out float TorsionY, out float TorsionZ)
    {
        //(解釋)實際上在lookat的過程中,z軸是無變化的,是以這裡的Z軸隻是用來保險用的
        //(解釋)另外,如果需要對Z軸做特殊處理(比如轉彎時彈體傾斜)等也應該在這裡進行,隻不過需要自己重寫z軸相關的代碼
        float eulerX = Mathf.Abs(GyroscopeEuler.x - TrackingMissileEuler.x);
        float eulerY = Mathf.Abs(GyroscopeEuler.y - TrackingMissileEuler.y);
        float eulerZ = Mathf.Abs(GyroscopeEuler.z - TrackingMissileEuler.z);
        if ((eulerX + eulerY + eulerZ) > 0)
        {
            TorsionX = (eulerX / (eulerX + eulerY + eulerZ)) * Torsion * Time.fixedDeltaTime;
            TorsionY = (eulerY / (eulerX + eulerY + eulerZ)) * Torsion * Time.fixedDeltaTime;
            TorsionZ = (eulerZ / (eulerX + eulerY + eulerZ)) * Torsion * Time.fixedDeltaTime;
        }
        else
        {
            //(解釋)這裡實際上也隻是個保險而已,因為如果eulerX + eulerY + eulerZ <=0(實際上不可能出現<0的情況)的話
            //(解釋)代表以完成方向調整,那扭力配置設定就沒有意義了。
            //(解釋)另外,因為在追擊過程中z軸不參與扭力,是以實際上這裡應該是
            //(解釋)TorsionX = Torsion / 2.0f;
            //(解釋)TorsionY = Torsion / 2.0f;
            //(解釋)TorsionZ = 0;
            //(解釋)不過反正應該也對結果沒任何影響,是以就懶得改了
            TorsionX = Torsion / 3.0f;
            TorsionY = Torsion / 3.0f;
            TorsionZ = Torsion / 3.0f;
        }
    }


    //(解釋)該函數檢測是否被套圈(即差>180度)
    //(解釋)如果差>180,那我們需要在小的那個數上+360
    void Detection(Vector3 TrackingMissileEuler, Vector3 GyroscopeEuler, out Vector3 NewTrackingMissileEuler, out Vector3 NewGyroscopeEuler)
    {
        NewTrackingMissileEuler = Vector3.zero;
        NewGyroscopeEuler = Vector3.zero;
        Compare(TrackingMissileEuler.x, GyroscopeEuler.x, out NewTrackingMissileEuler.x, out NewGyroscopeEuler.x);
        Compare(TrackingMissileEuler.y, GyroscopeEuler.y, out NewTrackingMissileEuler.y, out NewGyroscopeEuler.y);
        Compare(TrackingMissileEuler.z, GyroscopeEuler.z, out NewTrackingMissileEuler.z, out NewGyroscopeEuler.z);
            }
    //(解釋)該函數的作用為,傳入TrackingMissileEuler與GyroscopeEuler,如果TrackingMissileEuler-GyroscopeEuler的絕對值>180
    //(解釋)則将2者中小的那個+360以後輸出回來
    //(解釋)否則值不變傳回
    void Compare(float TrackingMissileEuler, float GyroscopeEuler, out float NewTrackingMissileEuler, out float NewGyroscopeEuler)
    {
        if (Mathf.Abs(GyroscopeEuler - TrackingMissileEuler) > 180)
        {
            if (GyroscopeEuler < TrackingMissileEuler)
            {
                GyroscopeEuler += 360;
            }
            else
            {
                TrackingMissileEuler += 360;
            }
        }
        NewTrackingMissileEuler = TrackingMissileEuler;
        NewGyroscopeEuler = GyroscopeEuler;
    }
}
      
另外,本人之前是用cocos的,最近一兩個月才開始接觸unity,是以代碼上肯定做不到完美,而且還會有很多C艹的影子,忘大家      
見諒吧。我也隻是給與我一樣數學渣的同胞們奉獻一份不需要複雜數學計算的跟蹤解決方案而已,大家重在領會精神就好,以上。      
項目下載下傳:http://pan.baidu.com/s/1kVHWWkf