Unity - 製作2D拋物線(Parabola)物件1 - 完美拋物線

  拋物線很容易從字面上理解,就是物體拋出去後移動的軌跡,但是實際的運作卻參雜了許多公式,例如空氣可視為某種流體,那是不是就又牽扯了流體的運作,同時物體如果不是正圓形的狀況,情況就又更複雜了。

  不過在遊戲中通常也用不到這麼精細的計算,這邊使用的公式也都是以點為主,而完美拋物線也只需要考慮重力的作用即可。




  如果只是單純地要在遊戲投射出物體,像是炸彈、手榴彈等等,其實直接使用Unity內部的物理運算即可,也比較輕鬆,不使用Unity的物理系統自己手動運算移動軌跡根本就是自找麻煩,不過從這邊讓我也對拋物線有更深一點的了解。

  首先就要來看拋射物的公式,請參考WIKI。
http://en.wikipedia.org/wiki/Trajectory_of_a_projectile
  • g: the gravitational acceleration—usually taken to be 9.81 m/s2 near the Earth's surface
  • θ: the angle at which the projectile is launched
  • v: the velocity at which the projectile is launched
  • y0: the initial height of the projectile
  • d: the total horizontal distance traveled by the projectile

--
  這邊有幾個重要參數,做成Component之後,有些可以直接在Inspector中設定,有些可以經由計算取得:

  g重力,直接設定。

  θ角度可以由兩種方式給,一種就是直接設定角度,另一種就是藉由發射點座標跟發射目標的座標求得向量,再由這向量計算取得θ角。

  V初速度直接設定。

  Y0發射點座標,這邊我若從原點發射,設定座標是Vector3(0, 0, 0),這個會影響物體飛行時間,因為這邊假設y=0的位置為水平面,物體y<0算落地。

  d水平飛行的距離,先由Vy這個Y軸初速度加上g的參數,計算出飛行至落地時間,再由飛行時間乘上X軸的初速度Vx,即可求得飛行距離。


--
  有了基本數值,接著就可以來設定物體的座標了。
transform.position.x 為物體當前的X座標
transform.position.y 當前的Y座標

nextX 物體下一個frame的X座標,這邊使用Time.deltaTime來預測下個位置
nextX= (transform.position.x - startPos.x) + Time.deltaTime;

nextY 物體下一個frame的Y座標
這邊Y座標的計算同樣參考上面的WIKI,根據X座標來計算Y座標位置的公式
nextY= y0 + nextX * Mathf.Tan(theta) - (g * Mathf.Pow(x,2))/(2 * Mathf.Pow(v * cos(theta),2) );


--
實作測試,Speed調整物件移動的速度,V0調整初速度,投射的目標可以用滑鼠拖拉



--
拋物線移動物件Component
public class ParabolaObject : MonoBehaviour {

    public GameObject target; //初始投射的目標方向,用gameObject只是方便在editor中方便拖拉編輯

    public float g = 9.81f;  //g = 9.81 m/s^2
    public float speed = 0.8f; //物體transform在拋物線路徑上移動的速度,此速度不影響拋物線的形狀
    public float V0 = 8.0f; //初速度
    public float ground = 0f;

    void Start () {
        if(target != null)
            StartCoroutine("Move", target.transform.position);
    }

    IEnumerator Move(Vector3 target)
    {
        ground = 0;
        if (target.y < ground)
            ground = target.y;

        Vector3 startPos = transform.position; //初始座標
        Vector3 dir = (target - transform.position).normalized; //先取得初始投射方向的向量
        if (dir.x == 0) dir.x = 0.001f; //避免x=0無法移動
        float theta = Mathf.Atan2(dir.y, dir.x); //再由向量求得初始角度

        //運算求得基本數值,飛行時間t(受到V0、theta、Y0的影響),飛行距離(受到V0、t的影響),最高點位置h
        //這邊幾個數值,在下面物體移動的計算上目前其實用不到,但計算出來如果有需要就可以使用
        //float Y0 = startPos.y; //把Y0獨立出來看得比較清楚,其實可以直接使用startPos
        //float t = (V0 * Mathf.Sin(theta) + Mathf.Sqrt(Mathf.Pow(V0 * Mathf.Sin(theta), 2) + 2 * g * Y0)) / g;
        //float distance = t * V0 * Mathf.Cos(theta);
        //float h = transform.position.y + (distance / 2) * Mathf.Tan(theta) - (g * (distance / 2) * (distance / 2)) / (2 * (V0 * Mathf.Cos(theta)) * (V0 * Mathf.Cos(theta)));

        while (true)
        {
            float nextX = (transform.position.x - startPos.x) + speed * Time.deltaTime;

            //使用公式 y = y0 + x * tan(theta) - (g * x^2)/(2 * (v * cos(theta))^2); 來取得y座標
            float nextY = startPos.y + nextX * Mathf.Tan(theta) - (g * Mathf.Pow(nextX, 2)) / (2 * Mathf.Pow(V0 * Mathf.Cos(theta), 2));

            //移動物體,這邊就直接設定座標了
            transform.position = new Vector3(nextX, nextY, 0);

            //如果y座標小於地板座標就停止移動
            if (transform.position.y < ground)
                break;
            yield return null;
        }
    }
}

No comments:

Post a Comment