起動角度以外のすべてを与えられた場合、ターゲットでGameObjectを起動するにはどうすればよいですか?


11

位置、目標位置、発射速度、重力を考慮して、目標で物体を発射しようとしています。私はウィキペディアからこの式に従っています:

θ=arctav2±v4ggバツ2+2yv2gバツ

私はできる限りコードを簡略化しましたが、それでも目標を達成することができません。計算式の+-の選択肢から利用できる2つのうち、より長い軌道のみを考慮しています。

誰かが私が間違っていることを知っていますか?

using UnityEngine;

public class Launcher : MonoBehaviour
{
    public float speed = 10.0f;

    void Start()
    {
        Launch(GameObject.Find("Target").transform);
    }

    public void Launch(Transform target)
    {
        float angle = GetAngle(transform.position, target.position, speed, -Physics2D.gravity.y);
        var forceToAdd = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * speed;
        GetComponent<Rigidbody2D>().AddForce(forceToAdd, ForceMode2D.Impulse);
    }

    private float GetAngle(Vector2 origin, Vector2 destination, float speed, float gravity)
    {
        float angle = 0.0f;

        //Labeling variables to match formula
        float x = Mathf.Abs(destination.x - origin.x);
        float y = Mathf.Abs(destination.y - origin.y);
        float v = speed;
        float g = gravity;

        //Formula seen above
        float valueToBeSquareRooted = Mathf.Pow(v, 4) - g * (g * Mathf.Pow(x, 2) + 2 * y * Mathf.Pow(v, 2));
        if (valueToBeSquareRooted >= 0)
        {
            angle = Mathf.Atan((Mathf.Pow(v, 2) + Mathf.Sqrt(valueToBeSquareRooted)) / g * x);
        }
        else
        {
            //Destination is out of range
        }

        return angle;
    }
}

私には2つのことが際立っています。-Physics2D.gravity.y、およびangle = Mathf.Atan((Mathf.Pow(v、2)+ Mathf.Sqrt(valueToBeSquareRooted))/ g * x);、式は重力が9.81などの正の値であることを期待しています、2番目は分母gxです。つまり、gで除算し、時間xを乗算します。分母の前に乗算が発生するように分母(g * x)が必要です。
マイクホワイト

回答:


14

atan接線比が特定の角度で無限大になり、数値エラーにつながる可能性があるため、ここでの使用には少し懐疑的です(数値のエラーにつながる可能性があります(まっすぐに撮影する場合、定義されていない/ゼロで除算されている場合でも))。

この回答で算出されを使用して、発射体のTイニシャルspeedを使用して、(最初は不明)が影響するまでの時間に関してこれをパラメーター化できます。

// assuming x, y are the horizontal & vertical offsets from source to target,
// and g is the (positive) gravitational acceleration downwards
// and speed is the (maximum) launch speed of the projectile...

b = speed*speed - y * g
discriminant = b*b - g*g * (x*x + y*y)

if(discriminant < 0)
  return CANNOT_REACH_TARGET; // Out of range, need higher shot velocity.

discRoot = sqrt(discriminant);

// Impact time for the most direct shot that hits.
T_min = sqrt((b - discRoot) * 2 / (g * g));

// Impact time for the highest shot that hits.
T_max = sqrt((b + discRoot) * 2 / (g * g));

T_minまたはT_maxのいずれかを選択できます(または、ある最大までの速度で発射する必要がある場合は、その中間の何か)

軌跡の例

T_minは下部の浅い赤い軌道で、T_max背の高い緑色の軌道です。それらの間の軌道は、実行可能な速度で実行可能です。2つが黄色の軌道に合流すると、オブジェクトは範囲外になります。)

の値を計算したTので、残りは簡単です。

vx = x/T;
vy = y/T + T*g/2;

velocity = (vx, vy);

この速度を直接使用することができます(長speedさは構造上等しい)、または角度を本当に知る必要がある場合は、atan2(vy, vx)


編集:これをより多くのケースに適用できるようにするには、次の3Dバージョンを使用します。

Vector3 toTarget = target.position - transform.position;

// Set up the terms we need to solve the quadratic equations.
float gSquared = Physics.gravity.sqrMagnitude;
float b = speed * speed + Vector3.Dot(toTarget, Physics.gravity);    
float discriminant = b * b - gSquared * toTarget.sqrMagnitude;

// Check whether the target is reachable at max speed or less.
if(discriminant < 0) {
    // Target is too far away to hit at this speed.
    // Abort, or fire at max speed in its general direction?
}

float discRoot = Mathf.Sqrt(discriminant);

// Highest shot with the given max speed:
float T_max = Mathf.Sqrt((b + discRoot) * 2f / gSquared);

// Most direct shot with the given max speed:
float T_min = Mathf.Sqrt((b - discRoot) * 2f / gSquared);

// Lowest-speed arc available:
float T_lowEnergy = Mathf.Sqrt(Mathf.Sqrt(toTarget.sqrMagnitude * 4f/gSquared));

float T = // choose T_max, T_min, or some T in-between like T_lowEnergy

// Convert from time-to-hit to a launch velocity:
Vector3 velocity = toTarget / T - Physics.gravity * T / 2f;

// Apply the calculated velocity (do not use force, acceleration, or impulse modes)
projectileBody.AddForce(velocity, ForceMode.VelocityChange);

そうです、既知のプラグインを使って解決策を見つけましたが、既知の力にしたいのです。
Evorlor 2016年

1
そう、Jost Petrieと@DMGregoryがこのフォーラムの利益を得ています。:)間違いありません
Hamza Hasan

1
ああ、すごい、両方ありがとう!:) @Evorlor discRootは、判別式の平方根です。これは、2次式の平方根記号の下に表示される部分です。二次式のb変数bの -1倍です。残念ながら、それを説明する名前はわかりません。(前のマイナスはすでに焼き込まれていて、二乗に影響を与えないため、後のステップをアニーリングするために割り当てるときは、-1を掛けました)。完全な導出については、他の回答を参照してください。ただし、いくつかの正方形が欠落しています(まもなく修正されます)
DMGregory

1
青と黄色の曲線は何を表していますか?
Slipp D. Thompson

3
@ SlippD.Thompson黄色の曲線は最も効率的な軌道(最低の発射速度が必要)であり、青い曲線は固定天井内で最も高い軌道です(プレイフィールドの境界や画面外でのアークを避ける必要がある場合に役立ちます)。これらの時間値の方程式は、リンクされた回答にあります
DMGregory

3

DMGregoryのおかげで、これに使用できるC#拡張スクリプトができました。最新バージョンはGitHubにあります

using UnityEngine;

public static class Rigidbody2DExtensions
{
    /// <summary>
    /// Applies the force to the Rigidbody2D such that it will land, if unobstructed, at the target position.  The arch [0, 1] determines the percent of arch to provide between the minimum and maximum arch.  If target is out of range, it will fail to launch and return false; otherwise, it will launch and return true.  This only takes the Y gravity into account, and X gravity will not affect the trajectory.
    /// </summary>
    public static bool SetTrajectory(this Rigidbody2D rigidbody2D, Vector2 target, float force, float arch = 0.5f)
    {
        Mathf.Clamp(arch, 0, 1);
        var origin = rigidbody2D.position;
        float x = target.x - origin.x;
        float y = target.y - origin.y;
        float gravity = -Physics2D.gravity.y;
        float b = force * force - y * gravity;
        float discriminant = b * b - gravity * gravity * (x * x + y * y);
        if (discriminant < 0)
        {
            return false;
        }
        float discriminantSquareRoot = Mathf.Sqrt(discriminant);
        float minTime = Mathf.Sqrt((b - discriminantSquareRoot) * 2) / Mathf.Abs(gravity);
        float maxTime = Mathf.Sqrt((b + discriminantSquareRoot) * 2) / Mathf.Abs(gravity);
        float time = (maxTime - minTime) * arch + minTime;
        float vx = x / time;
        float vy = y / time + time * gravity / 2;
        var trajectory = new Vector2(vx, vy);
        rigidbody2D.AddForce(trajectory, ForceMode2D.Impulse);
        return true;
    }
}

-6

個人的には、どんな種類の複雑な数式を使用することすらしません。

GetComponent<Rigidbody2D>.AddForce((target.transform.position - transform.position) * someSortOfMultiplier());

ターゲットの方向に発射するだけです。また、重力や距離などsomeSortOfMultiplier()を補正する場合は、上記のコードを乗算すると補正される浮動小数点数を返す関数に設定します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.