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

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

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

-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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.