rigidbodyの軌道予測線を作成してみた(空気抵抗あり)

rigidbodyの軌道予測線を作成してみた(空気抵抗あり)

 空気抵抗がある場合のrigidbodyの軌道予測線を作成しました。放物線運動の公式によって作成した起動予測線はrigidbodyの軌道と一致していなかったため、新たに式を導出しrigidbodyの軌道と一致するかを確認しました。

※空気抵抗がない場合の軌道予測線はこちら

放物線運動の公式をもとに作成

放物線運動の公式

 原点より初速\(v_{0}\)で発射された空気抵抗を受ける物体の時刻\(t\)における位置\(x,y\)は

$$ \begin{align} &x=\frac{mv_{0}}{c_{k}}(1-e^{-\frac{c_{k}}{m}}t)cos\theta\\ &y=\frac{m}{c_{k}}\biggl\{(v_{0}sin\theta+\frac{m}{c_{k}}g)(1-e^{-\frac{c_{k}}{m}}t)-gt\biggl\} \end{align} $$

と表せます。ここで、 \(m\)は物体の質量、\(c_{k}\)は空気抵抗係数です。
この式とLineRendererを使用して軌道予測線を作成しました。

Script

 公式を使用した軌道予測線を描画するためのScriptは以下の通りです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DrawLine : MonoBehaviour {

    public GameObject preLineRenderer;
    public GameObject preBullet;
    private GameObject objLineRenderer;
    private LineRenderer lineRenderer;
    private int count = 0;
    private float mass = 1f, drag = 1f, x = 0f, y = 0f, initVel = 10f, angle = 0f, step = 0.05f, time = 0;
    private float gravity = 9.80665f;
    private Vector3 vel;

	// Use this for initialization
	void Start () {
        angle = 30f * Mathf.Deg2Rad;
        vel = new Vector3(initVel * Mathf.Cos(angle), initVel * Mathf.Sin(angle), 0);

        objLineRenderer = Instantiate(preLineRenderer, gameObject.transform);
        objLineRenderer.transform.localPosition = Vector3.zero;
        lineRenderer = objLineRenderer.GetComponent();

        Time.timeScale = 0.2f;
    }

    void Drag()
    {
        if(y >= 0)
        {
            float a = initVel * mass / drag;
            float t = 0;
            if(x != 0) t = -1f * mass / drag * Mathf.Log(1f - x / (a * Mathf.Cos(angle)), Mathf.Exp(1f));
            y = mass / drag * ((initVel * Mathf.Sin(angle) + mass / drag * gravity) * (1f - Mathf.Exp(-1f * drag / mass * t)) - gravity * t);
            lineRenderer.positionCount = count + 1;
            lineRenderer.SetPosition(count, new Vector3(x, y, 0f));
            count++;
            x += step;
        }
    }

    // Update is called once per frame
    void Update () {
        Drag();
    }
}

実行結果

 作成した軌道予測線が実際の挙動とあっているか否かを確認しました。結果は以下の通りです。空気抵抗がない場合と同様に、投射されたオブジェクトが軌道予測線から徐々にずれていくことがわかりました。

Unity

 空気抵抗がある場合、どのように計算されているか調べました。UnityのForum(ここ)より、以下の式で計算することで速度が求められることがわかりました。

$$ v(t+dt)=v(t)(1-c_{d}dt) $$

ここで、\(c_{d}\)はrigidbodyのdragである。この式をもとに、位置\(x,y\)の式を求め予測線を作成しました。

式の修正

・位置\(x\)の導出

 \(v_{0x}=v_{0}sin\theta\)とおくと、x方向の速度\(v_{x}\)は

$$ \begin{align} &v_{x}(0)=v_{0x}\\ &v_{x}(dt)=v_{0x}(1-c_{d}dt)\\ &v_{x}(2dt)=v_{x}(dt)(1-c_{d}dt)=v_{0x}(1-c_{d}dt)^{2} \end{align} $$

と表せます。よって

$$ v(kdt)=v_{0x}(1-c_{d}dt)^{k} $$

となります。これより、\(dt\)秒間の\(x\)方向の移動距離\(dx\)は

$$ dx=v(kdt)dt=v_{0x}(1-c_{d}dt)^{k}dt $$

です。ここで、\(r=(1-c_{d}dt)\)と置くと、位置\(x\)は

$$ x=\sum_{k=0}^{n}v_{0x}r^{k}dt $$

となります。ここで

$$ \sum_{k=1}^{n}ar^{k}=\frac{ar(1-r^{n})}{1-r} $$

なので、位置\(x\)は

$$ \begin{align} x&=\sum_{k=0}^{n}v_{0x}r^{k}dt\\ &=\frac{v_{0x}r(1-r^{n})}{1-r} \end{align} $$

となります。

・位置\(y\)の導出

 \(v_{0y}=v_{0}cos\theta\)とおくと、y方向の速度\(v_{y}\)は

$$ \begin{align} &v_{y}(0)=v_{0y}\\ &v_{y}(dt)=(v_{0y}-gdt)r\\ &v_{y}(2dt)=(v_{y}(dt)-gdt)r\\ &=v_{0y}r^{2}-gdt(r^{2}+r)\\ &v_{y}(2dt)=(v_{y}(2dt)-gdt)r\\ &=v_{0y}r^{3}-gdt(r^{3}+r^{2}+r) \end{align} $$

ただし

$$ r=(1-c_{d}dt) $$

です。これより、\(dt\)秒間の\(y\)方向の移動距離\(dy\)は

$$ \begin{align} v_{y}(kdt)&=v_{0y}r^{n}-gdt\sum_{k=1}^{n}r^{k}\\ &=v_{0y}r^{n}-gdt\frac{r(1-r^{n})}{1-r} \end{align} $$

となります。よって、\(dt\)秒間の\(y\)方向の移動距離\(dy\)は

$$ \begin{align} dy&=v_{0y}r^{n}dt-gdt^{2}\frac{r(1-r^{n})}{1-r}\\ &=v_{0y}r^{n}dt-\frac{rgdt^{2}}{1-r}(1-r^{n}) \end{align} $$

と求まります。これより、位置\(y\)は

$$ \begin{align} y&=v_{0y}dt\sum_{k=1}^{n}r^{k}-\frac{rgdt^{2}}{1-r}\sum_{k=1}^{n}(1-r^{n})\\ &=v_{0y}dt\frac{r(1-r^{n})}{1-r}-\frac{rgdt^{2}}{1-r}\biggl\{n-\frac{r(1-r^{n})}{1-r}\biggl\} \end{align} $$

となります。

・導出した式

 位置\(x,y\)は以下の通りです。

$$ \begin{align} &x=\frac{v_{0x}r(1-r^{n})}{1-r}\\ &y=v_{0y}dt\frac{r(1-r^{n})}{1-r}-\frac{rgdt^{2}}{1-r}\biggl\{n-\frac{r(1-r^{n})}{1-r}\biggl\}\\ &r=(1-c_{d}dt) \end{align} $$

Script

 導出した式をもちいた軌道予測線を描画するためのScriptは以下の通りです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DrawLine : MonoBehaviour {

    public GameObject preLineRenderer;
    public GameObject preBullet;
    private GameObject objLineRenderer;
    private LineRenderer lineRenderer;
    private int count = 0;
    private float mass = 1f, drag = 1f, x = 0f, y = 0f, initVel = 10f, angle = 0f, step = 0.05f, time = 0;
    private float gravity = 9.80665f;
    private Vector3 vel;

	// Use this for initialization
	void Start () {
        angle = 30f * Mathf.Deg2Rad;
        vel = new Vector3(initVel * Mathf.Cos(angle), initVel * Mathf.Sin(angle), 0);

        objLineRenderer = Instantiate(preLineRenderer, gameObject.transform);
        objLineRenderer.transform.localPosition = Vector3.zero;
        lineRenderer = objLineRenderer.GetComponent();

        Time.timeScale = 0.2f;
    }

    void DisDrag()
    {
        if(y >= 0)
        {
            float r =  Mathf.Clamp01(1 - drag * Time.fixedDeltaTime);
            float n = time / Time.fixedDeltaTime;
            x = vel.x * Time.fixedDeltaTime * r * (1 - Mathf.Pow(r, n)) / (1 - r);
            y = vel.y * Time.fixedDeltaTime * r * (1 - Mathf.Pow(r, n)) / (1 - r);
            y = y - r * gravity * Time.fixedDeltaTime * Time.fixedDeltaTime / (1 - r) * (n - r * (1 - Mathf.Pow(r, n)) / (1 - r));
            lineRenderer.positionCount = count + 1;
            lineRenderer.SetPosition(count, new Vector3(x, y, 0f));
            count++;
            time += step;
        }
    }

    // Update is called once per frame
    void Update () {
        DisDrag();
    }
}

実行結果

 修正した軌道予測線があっているか否かを確認しました。結果は以下の通りです。この結果より、導出した式により、正確な軌道予測線が描画できることがわかりました。

参考文献

斜方投射

Unity公式Forum(Physics drag formula?)