Como posso iniciar um GameObject em um destino se receber tudo, exceto o ângulo de inicialização?

11

Estou tentando lançar um objeto em um alvo, dada a sua posição, a sua posição alvo, a velocidade de lançamento e a gravidade. Eu estou seguindo esta fórmula da Wikipedia :

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

Simplifiquei o código da melhor maneira possível, mas ainda não consigo atingir o alvo de forma consistente. Estou apenas considerando a trajetória mais alta, das duas disponíveis na opção + - na fórmula.

Alguém sabe o que estou fazendo de errado?

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
fonte
Duas coisas se destacam para mim. -Physics2D.gravity.y, e angle = Mathf.Atan ((Mathf.Pow (v, 2) + Mathf.Sqrt (valueToBeSquareRooted)) / g * x) ;, a fórmula espera que a gravidade seja um valor positivo, como 9,81 , o segundo é o denominador gx, da maneira que você o tem, divide por g e multiplica o tempo x, você deve ter o denominador (g * x) para que a multiplicação ocorra antes da divisão.
Mike White

Respostas:

14

Sou um pouco cético em usar atanaqui, porque a razão tangente dispara até o infinito em certos ângulos e pode levar a erros numéricos (mesmo fora do caso indefinido / dividido por zero para disparar para cima / para baixo).

Usando as fórmulas elaboradas nesta resposta , podemos parametrizar isso em termos do tempo (inicialmente desconhecido) para impactar T, usando a inicial speeddo projétil:

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

Você pode escolher T_min ou T_max (ou algo intermediário, se desejar disparar com velocidades de até, mas não necessariamente iguais a, no máximo)

Trajetórias de exemplo

( T_miné a trajetória rasa em vermelho na parte inferior e T_maxé a alta em verde. Qualquer trajetória entre eles é viável a uma velocidade possível. Quando os dois se fundem na trajetória em amarelo, o objeto está fora de alcance.)

Agora que calculamos um valor para T, o resto é direto:

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

velocity = (vx, vy);

Você pode usar essa velocidade diretamente (ela tem um comprimento igual à speedconstrução) ou, se você realmente precisa conhecer o ângulo, pode usaratan2(vy, vx)


Editar: para tornar isso aplicável a mais casos, aqui está uma versão 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
fonte
Certo, encontrei soluções conectando o tempo como conhecido, mas quero que a força seja conhecida.
Evorlor 9/01/16
11
Sim, Jost Petrie e @DMGregory são os ganhos neste fórum. :) sem dúvida
Hamza Hasan
11
Ah, droga, obrigado a vocês! :) @Evorlor discRooté a raiz quadrada do discriminante , que é a parte que aparece sob o sinal de raiz quadrada na fórmula quadrática . bé -1 vezes a variável b na fórmula quadrática. Infelizmente, não conheço um nome mais descritivo para ele. (Eu o multipliquei por -1 ao atribuir a ordem de aperfeiçoar as etapas posteriores, pois o sinal de menos inicial já está inserido e não afeta o quadrado). Veja a outra resposta para obter uma derivação completa, embora alguns quadrados estejam faltando (serão corrigidos em breve).
DMGregory
11
O que representam a curva azul e amarela?
Slipp D. Thompson
3
@ SlippD.Thompson, a curva amarela é a trajetória mais eficiente (menor velocidade de lançamento necessária) e a curva azul é a trajetória mais alta dentro de um teto fixo (útil se você precisar evitar limites do campo de jogo ou arcos fora da tela). As equações para esses valores de tempo estão na resposta vinculada
DMGregory
3

Graças ao DMGregory, agora tenho um script de extensão C # que pode ser usado para isso. A versão mais recente pode ser encontrada no 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
fonte
-6

Pessoalmente, eu nem me incomodaria em usar qualquer tipo de fórmula complicada.

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

Apenas atira na direção do alvo. E se você deseja compensar a gravidade, a distância etc., defina someSortOfMultiplier()como uma função que retorne um flutuador que compensará quando multiplicado pelo código acima.

jonathanhuo11
fonte