Comment lancer un GameObject sur une cible si on me donne tout sauf son angle de lancement?

11

J'essaie de lancer un objet sur une cible, étant donné sa position, sa position cible, la vitesse de lancement et la gravité. Je suis cette formule de Wikipedia :

θ=unerctunen(v2±v4-g(gX2+2yv2)gX)

J'ai simplifié le code au mieux de mes capacités, mais je ne parviens toujours pas à atteindre la cible de manière cohérente. Je ne considère que la trajectoire plus haute, des deux disponibles à partir du choix + - dans la formule.

Est-ce que quelqu'un sait ce que je fais 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
la source
Deux choses me viennent à l'esprit. -Physics2D.gravity.y, et angle = Mathf.Atan ((Mathf.Pow (v, 2) + Mathf.Sqrt (valueToBeSquareRooted)) / g * x) ;, la formule s'attend à ce que la gravité soit une valeur positive telle que 9,81 , le second est le dénominateur gx, la façon dont vous l'avez, vous divisez par g, puis multipliez le temps x, vous devriez avoir le dénominateur (g * x) pour que la multiplication se produise avant la division.
Mike White

Réponses:

14

Je suis un peu sceptique quant à l'utilisation atanici, car le rapport de tangence se déclenche à l'infini sous certains angles, et peut conduire à des erreurs numériques (même en dehors de l'indéfini / diviser par zéro pour la prise de vue droite / haute).

En utilisant les formules élaborées dans cette réponse , nous pouvons paramétrer cela en termes de temps (initialement inconnu) pour l'impact, Ten utilisant l'initiale speeddu projectile:

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

Vous pouvez choisir T_min ou T_max (ou quelque chose entre les deux si vous souhaitez tirer avec des vitesses allant jusqu'à mais pas nécessairement égales à un certain maximum)

Exemples de trajectoires

( T_minest la trajectoire rouge peu profonde en bas, et T_maxest la grande verte. Toute trajectoire entre eux est viable à une vitesse réalisable. Lorsque les deux fusionnent dans la trajectoire jaune, l'objet est hors de portée.)

Maintenant que nous avons calculé une valeur pour T, le reste est simple:

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

velocity = (vx, vy);

Vous pouvez utiliser cette vitesse directement (elle a une longueur égale à speedpar construction), ou si vous avez vraiment besoin de connaître l'angle, vous pouvez utiliseratan2(vy, vx)


Edit: pour rendre cela applicable à plus de cas, voici une version 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
la source
Bon, j'ai trouvé des solutions en branchant le temps en tant que connu, mais je veux que la force soit connue.
Evorlor
1
Oui, Jost Petrie et @DMGregory sont les gagnants de ce forum. :) sans aucun doute
Hamza Hasan
1
Oh, shucks, merci à vous deux! :) @Evorlor discRootest la racine carrée du discriminant , qui est la partie qui apparaît sous le signe racine carrée dans la formule quadratique . best -1 fois la variable b dans la formule quadratique. Malheureusement, je ne connais pas de nom plus descriptif. (Je l'ai multiplié par -1 lors de l'affectation pour nettoyer les étapes ultérieures, car le moins en tête est déjà intégré et n'affecte pas la quadrature). Voir l'autre réponse pour une dérivation complète, bien qu'il manque quelques carrés (corrigera sous peu)
DMGregory
1
Que représentent les courbes bleue et jaune?
Slipp D. Thompson
3
@ SlippD.Thompson, la courbe jaune est la trajectoire la plus efficace (la vitesse de lancement la plus faible nécessaire) et la courbe bleue est la trajectoire la plus élevée dans un plafond fixe (utile si vous devez éviter les limites du terrain de jeu ou les arcs hors écran). Les équations pour ces valeurs de temps sont dans la réponse liée
DMGregory
3

Grâce à DMGregory, j'ai maintenant un script d'extension C # qui peut être utilisé pour cela. La version la plus récente peut être trouvée sur 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
la source
-6

Personnellement, je ne prendrais même pas la peine d'utiliser n'importe quel type de formule compliquée.

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

Il tire simplement dans la direction de la cible. Et si vous souhaitez compenser la gravité, la distance, etc., définissez someSortOfMultiplier()une fonction qui renvoie un flotteur qui compensera une fois multiplié avec le code ci-dessus.

jonathanhuo11
la source