Wie kann ich ein GameObject an einem Ziel starten, wenn mir bis auf den Startwinkel alles gegeben wird?

11

Ich versuche, ein Objekt auf ein Ziel zu starten, angesichts seiner Position, seiner Zielposition, der Startgeschwindigkeit und der Schwerkraft. Ich folge dieser Formel aus Wikipedia :

θ=einrcteinn(v2±v4- -G(Gx2+2yv2)Gx)

Ich habe den Code nach besten Kräften vereinfacht, kann das Ziel aber immer noch nicht konsequent treffen. Ich betrachte nur die größere Flugbahn der beiden, die aus der + - Auswahl in der Formel verfügbar sind.

Weiß jemand was ich falsch mache?

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
quelle
Zwei Dinge fallen mir auf. -Physics2D.gravity.y und angle = Mathf.Atan ((Mathf.Pow (v, 2) + Mathf.Sqrt (valueToBeSquareRooted)) / g * x); die Formel erwartet, dass die Schwerkraft ein positiver Wert wie 9,81 ist , der zweite ist der Nenner gx, so wie Sie ihn haben, dividieren Sie durch g und multiplizieren dann die Zeit x. Sie sollten den Nenner (g * x) haben, damit die Multiplikation vor der Division erfolgt.
Mike White

Antworten:

14

Ich bin etwas skeptisch gegenüber atan hier, da das Tangentenverhältnis in bestimmten Winkeln ins Unendliche abschießt und zu numerischen Fehlern führen kann (auch außerhalb des undefinierten / durch Null dividierten Falls für direktes Auf / Ab-Schießen).

Mit den in dieser Antwort erarbeiteten Formeln können wir dies anhand der (anfangs unbekannten) Zeit bis zum Aufprall Tanhand der Initiale speeddes Projektils parametrisieren :

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

Sie können entweder T_min oder T_max wählen (oder etwas dazwischen, wenn Sie mit Geschwindigkeiten bis zu einem Maximum feuern möchten, das jedoch nicht unbedingt einem Maximum entspricht).

Beispieltrajektorien

( T_minist die flache rote Flugbahn unten undT_max Ist die hohe grüne. Jede Flugbahn zwischen ihnen ist mit einer möglichen Geschwindigkeit realisierbar. Wenn die beiden in die gelbe Flugbahn übergehen, befindet sich das Objekt außerhalb der Reichweite.)

Nachdem wir einen Wert für berechnet haben T, ist der Rest einfach:

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

velocity = (vx, vy);

Sie können diese Geschwindigkeit direkt verwenden (sie hat eine Länge, die speedder Konstruktion entspricht), oder wenn Sie den Winkel wirklich kennen müssen, können Sie sie verwendenatan2(vy, vx)


Bearbeiten: Um dies auf weitere Fälle anzuwenden, ist hier eine 3D-Version:

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
quelle
Richtig, ich habe Lösungen gefunden, indem ich die Zeit als bekannt eingestuft habe, aber ich möchte, dass die Kraft die bekannte ist.
Evorlor
1
Ja, Jost Petrie und @DMGregory sind die Gewinne in diesem Forum. :) kein Zweifel
Hamza Hasan
1
Aw, shucks, danke euch beiden! :) @Evorlor discRootist die Quadratwurzel der Diskriminante , dh der Teil, der in der quadratischen Formel unter dem Quadratwurzelzeichen erscheint . bist das -1-fache der Variablen b in der quadratischen Formel. Leider kenne ich keinen aussagekräftigeren Namen dafür. (Ich habe es mit -1 multipliziert, als ich die späteren Schritte zugewiesen habe, da das führende Minus bereits eingebrannt ist und die Quadrierung nicht beeinflusst.) Siehe die andere Antwort für eine vollständige Ableitung, obwohl ein paar Quadrate fehlen (wird in Kürze
behoben
1
Was bedeuten die blaue und gelbe Kurve?
Slipp D. Thompson
3
@ SlippD.Thompson ist die gelbe Kurve die effizienteste Flugbahn (geringste benötigte Startgeschwindigkeit) und die blaue Kurve die höchste Flugbahn innerhalb einer festen Decke (nützlich, wenn Sie Spielfeldgrenzen oder Lichtbögen außerhalb des Bildschirms vermeiden müssen). Gleichungen für diese Zeitwerte sind in der verknüpften Antwort
DMGregory
3

Dank DMGregory habe ich jetzt ein C # -Erweiterungsskript, das dafür verwendet werden kann. Die neueste Version finden Sie auf 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
quelle
-6

Persönlich würde ich mir nicht einmal die Mühe machen, irgendeine komplizierte Formel zu verwenden.

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

Es schießt es einfach in Richtung des Ziels. Wenn Sie die Schwerkraft, die Entfernung usw. kompensieren möchten, stellen Sie someSortOfMultiplier()eine Funktion ein, die einen Float zurückgibt, der bei Multiplikation mit dem obigen Code kompensiert.

jonathanhuo11
quelle