¿Cómo puedo lanzar un GameObject en un objetivo si me dan todo excepto su ángulo de lanzamiento?

11

Estoy tratando de lanzar un objeto a un objetivo, dada su posición, su posición de destino, la velocidad de lanzamiento y la gravedad. Estoy siguiendo esta fórmula de Wikipedia :

θ=arctan(v2±v4g(gx2+2yv2)gx)

Simplifiqué el código lo mejor que pude, pero todavía no puedo alcanzar el objetivo. Solo estoy considerando la trayectoria más alta, de los dos disponibles de la opción + - en la fórmula.

¿Alguien sabe lo que estoy haciendo mal?

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
fuente
Dos cosas me destacan. -Physics2D.gravity.y, y angle = Mathf.Atan ((Mathf.Pow (v, 2) + Mathf.Sqrt (valueToBeSquareRooted)) / g * x) ;, la fórmula espera que la gravedad sea un valor positivo como 9.81 , el segundo es el denominador gx, la forma en que lo tiene, divide por g, luego multiplica el tiempo x, debe tener el denominador (g * x) para que la multiplicación ocurra antes de la división.
Mike White

Respuestas:

14

Soy un poco escéptico de usar atanaquí, porque la relación tangente se dispara al infinito en ciertos ángulos, y puede conducir a errores numéricos (incluso fuera del caso indefinido / dividir por cero para disparar hacia arriba / abajo).

Usando las fórmulas elaboradas en esta respuesta , podemos parametrizar esto en términos del tiempo (inicialmente desconocido) para impactar T, usando la inicial speeddel proyectil:

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

Puedes elegir T_min o T_max (o algo intermedio si quieres disparar con velocidades de hasta un máximo, pero no necesariamente iguales )

Ejemplos de trayectorias

( T_mines la trayectoria roja poco profunda en la parte inferior, y T_maxes la verde alta. Cualquier trayectoria entre ellos es viable a una velocidad factible. Cuando los dos se funden en la trayectoria amarilla, el objeto está fuera de alcance).

Ahora que hemos calculado un valor para T, el resto es sencillo:

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

velocity = (vx, vy);

Puede usar esta velocidad directamente (tiene una longitud igual a speedpor construcción), o si realmente necesita conocer el ángulo, puede usaratan2(vy, vx)


Editar: para que esto sea aplicable a más casos, aquí hay una versión 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
fuente
Bien, he encontrado soluciones conectando el tiempo como un conocido, pero quiero que la fuerza sea lo conocido.
Evorlor
1
Sí, Jost Petrie y @DMGregory son los gaints en este foro. :) sin duda
Hamza Hasan
1
Aw, shucks, gracias a los dos! :) @Evorlor discRootes la raíz cuadrada del discriminante , que es la parte que aparece debajo del signo de raíz cuadrada en la fórmula cuadrática . bes -1 veces la variable b en la fórmula cuadrática. Lamentablemente no sé un nombre más descriptivo para ello. (Lo multipliqué por -1 cuando asigné a Neaten los pasos posteriores, ya que el menos inicial ya está integrado y no afecta la cuadratura). Vea la otra respuesta para obtener una derivación completa, aunque faltan un par de cuadrados (se solucionará en breve)
DMGregory
1
¿Qué representan la curva azul y amarilla?
Slipp D. Thompson
3
@ SlippD.Thompson la curva amarilla es la trayectoria más eficiente (se necesita la menor velocidad de lanzamiento), y la curva azul es la trayectoria más alta dentro de un techo fijo (útil si necesita evitar los límites del campo de juego o el arco fuera de la pantalla). Las ecuaciones para estos valores de tiempo están en la respuesta vinculada
DMGregory
3

Gracias a DMGregory, ahora tengo un script de extensión C # que se puede usar para esto. La versión más reciente se puede encontrar en 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
fuente
-6

Personalmente, ni siquiera me molestaría en usar ningún tipo de fórmula complicada.

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

Simplemente lo dispara en la dirección del objetivo. Y si desea compensar la gravedad, la distancia, etc., configure someSortOfMultiplier()una función que devuelva un flotador que compensará cuando se multiplique con el código anterior.

jonathanhuo11
fuente