Bagaimana saya bisa meluncurkan GameObject pada target jika saya diberikan segalanya kecuali untuk sudut peluncurannya?

11

Saya mencoba meluncurkan objek pada target, mengingat posisinya, posisi targetnya, kecepatan peluncuran, dan gravitasi. Saya mengikuti formula ini dari Wikipedia :

θ=SebuahrctSebuahn(v2±v4-g(gx2+2yv2)gx)

Saya telah menyederhanakan kode dengan kemampuan terbaik saya, tetapi saya masih tidak dapat secara konsisten mencapai target. Saya hanya mempertimbangkan lintasan yang lebih tinggi, dari dua yang tersedia dari + - pilihan dalam rumus.

Adakah yang tahu apa yang saya lakukan salah?

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;
    }
}
Evorlor
sumber
Ada dua hal yang menonjol bagi saya. -Physics2D.gravity.y, dan angle = Mathf.Atan ((Mathf.Pow (v, 2) + Mathf.Sqrt (valueToBeSquareRooted)) / g * x) ;, rumus mengharapkan gravitasi menjadi nilai positif seperti 9,81 , yang kedua adalah penyebut gx, cara Anda memilikinya, Anda membaginya dengan g, lalu gandakan waktu x, Anda harus memiliki penyebutnya (g * x) sehingga penggandaannya terjadi sebelum pembagian.
Mike White

Jawaban:

14

Saya agak skeptis menggunakan di atansini, karena rasio singgung melesat hingga tak terbatas pada sudut tertentu, dan dapat menyebabkan kesalahan numerik (bahkan di luar undefined / bagi dengan nol case untuk pemotretan lurus ke atas / bawah).

Menggunakan rumus yang dikerjakan dalam jawaban ini , kita dapat menentukan ini dalam hal waktu (awalnya tidak diketahui) untuk berdampak T,, menggunakan inisial speeddari proyektil:

// 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));

Anda dapat memilih T_min atau T_max (atau sesuatu di antaranya jika Anda ingin menjalankan dengan kecepatan hingga tetapi tidak harus sama dengan beberapa maksimum)

Contoh lintasan

( T_minAdalah lintasan merah dangkal di bagian bawah, dan T_maxmerupakan lintasan hijau tinggi. Setiap lintasan di antara mereka layak pada beberapa kecepatan yang layak. Ketika keduanya bergabung ke lintasan kuning, objek berada di luar jangkauan.)

Sekarang kami telah menghitung nilai untuk T, sisanya mudah:

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

velocity = (vx, vy);

Anda dapat menggunakan kecepatan ini secara langsung (memiliki panjang yang sama speeddengan konstruksi), atau jika Anda benar-benar perlu mengetahui sudutnya, Anda dapat menggunakanatan2(vy, vx)


Edit: untuk menjadikan ini berlaku untuk lebih banyak kasus, inilah versi 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);
DMGregory
sumber
Benar, saya telah menemukan solusi dengan menghubungkan waktu sebagai yang diketahui, tetapi saya ingin kekuatan untuk diketahui.
Evorlor
1
Ya, Jost Petrie dan @DMGregory adalah forum di forum ini. :) tidak diragukan lagi
Hamza Hasan
1
Aduh, brengsek, terima kasih semuanya! :) @Evorlor discRootadalah akar kuadrat dari diskriminan , yang merupakan bagian yang muncul di bawah tanda akar kuadrat dalam rumus kuadratik . badalah -1 kali variabel b dalam rumus kuadratik. Sayangnya saya tidak tahu nama yang lebih deskriptif untuk itu. (Saya mengalikannya dengan -1 saat menetapkan untuk menghapus langkah-langkah selanjutnya, karena minus utama sudah dipanggang dan tidak memengaruhi kuadrat). Lihat jawaban lain untuk derivasi penuh, meskipun beberapa kotak hilang (akan segera diperbaiki)
DMGregory
1
Apa yang dilambangkan oleh kurva biru dan kuning?
Slipp D. Thompson
3
@ SlippD. Thompson kurva kuning adalah lintasan yang paling efisien (paling tidak diperlukan kecepatan luncuran), dan kurva biru adalah lintasan tertinggi di langit-langit tetap (berguna jika Anda perlu menghindari batas playfield atau melengkung dari layar). Persamaan untuk nilai waktu ini ada dalam jawaban yang ditautkan
DMGregory
3

Berkat DMGregory, saya sekarang memiliki skrip ekstensi C # yang dapat digunakan untuk ini. Versi terbaru dapat ditemukan di 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;
    }
}
Evorlor
sumber
-6

Secara pribadi, saya bahkan tidak akan repot menggunakan segala jenis formula rumit.

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

Itu hanya menembak ke arah target. Dan jika Anda ingin mengkompensasi gravitasi, jarak, dll, setel someSortOfMultiplier()menjadi fungsi yang mengembalikan float yang akan mengkompensasi ketika dikalikan dengan kode di atas.

jonathanhuo11
sumber