Come posso lanciare un GameObject su un bersaglio se mi viene dato tutto tranne che per l'angolo di lancio?


11

Sto cercando di lanciare un oggetto su un bersaglio, data la sua posizione, la sua posizione di destinazione, la velocità di lancio e la gravità. Sto seguendo questa formula da Wikipedia :

θ=un'rctun'n(v2±v4-g(gX2+2yv2)gX)

Ho semplificato il codice al meglio delle mie possibilità, ma non riesco ancora a raggiungere l'obiettivo in modo coerente. Sto solo considerando la traiettoria più alta, delle due disponibili dalla scelta + nella formula.

Qualcuno sa cosa sto facendo di sbagliato?

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;
    }
}

Due cose si distinguono per me. -Physics2D.gravity.y e angle = Mathf.Atan ((Mathf.Pow (v, 2) + Mathf.Sqrt (valueToBeSquareRooted)) / g * x); la formula prevede che la gravità sia un valore positivo come 9,81 , il secondo è il denominatore gx, il modo in cui lo hai, dividi per g, quindi moltiplica il tempo x, dovresti avere il denominatore (g * x) in modo che il multiplo avvenga prima della divisione.
Mike White,

Risposte:


14

Sono un po 'scettico nell'utilizzare atanqui, perché il rapporto tangente si spegne all'infinito a determinati angoli e può portare a errori numerici (anche al di fuori del caso indefinito / diviso per zero per lo scatto dritto su / giù).

Usando le formule elaborate in questa risposta , possiamo parametrizzare questo in termini di tempo (inizialmente sconosciuto) di impatto T, usando l'iniziale speeddel proiettile:

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

Puoi scegliere T_min o T_max (o qualcosa nel mezzo se vuoi sparare con velocità fino a ma non necessariamente pari a un massimo)

Traiettorie di esempio

( T_minè la traiettoria rossa poco profonda nella parte inferiore, ed T_maxè quella verde alta. Qualsiasi traiettoria tra loro è praticabile a una certa velocità possibile. Quando i due si fondono nella traiettoria gialla, l'oggetto è fuori portata.)

Ora che abbiamo calcolato un valore per T, il resto è semplice:

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

velocity = (vx, vy);

Puoi usare questa velocità direttamente (ha una lunghezza uguale a speedper costruzione), o se hai davvero bisogno di conoscere l'angolo, puoi usareatan2(vy, vx)


Modifica: per renderlo applicabile a più casi, ecco una versione 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);

Bene, ho trovato soluzioni collegando il tempo come noto, ma voglio che la forza sia il noto.
Evorlor

1
Sì, Jost Petrie e @DMGregory sono i guadagni in questo forum. :) senza dubbio
Hamza Hasan,

1
Oh, grazie, grazie ad entrambi! :) @Evorlor discRootè la radice quadrata del discriminante , ovvero la porzione che appare sotto il segno della radice quadrata nella formula quadratica . bè -1 volte la variabile b nella formula quadratica. Sfortunatamente non conosco un nome più descrittivo per questo. (L'ho moltiplicato per -1 durante l'assegnazione per perfezionare i passaggi successivi, poiché il meno principale è già cotto e non influisce sulla quadratura). Vedi l'altra risposta per una derivazione completa, anche se mancano un paio di quadrati (risolveranno a breve)
DMGregory

1
Cosa rappresentano la curva blu e gialla?
Slipp D. Thompson,

3
@ SlippD. Thompson la curva gialla è la traiettoria più efficiente (è necessaria la minima velocità di lancio) e la curva blu è la traiettoria più alta all'interno di un soffitto fisso (utile se è necessario evitare i limiti del campo di gioco o arcuarsi fuori dallo schermo). Le equazioni per questi valori temporali sono nella risposta collegata
DMGregory

3

Grazie a DMGregory, ora ho uno script di estensione C # che può essere utilizzato per questo. La versione più recente è disponibile su 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

Personalmente, non mi preoccuperei nemmeno di usare qualsiasi tipo di formula complicata.

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

Lo spara semplicemente nella direzione del bersaglio. E se si desidera compensare gravità, distanza, ecc., Impostare someSortOfMultiplier()una funzione che restituisca un galleggiante che compenserà quando moltiplicato per il codice sopra.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.