Wie man ein sich bewegendes Ziel von einem sich bewegenden Schützen führt

7

Ich sah diese Frage: Vorhersage der feindlichen Position, damit ein Objekt sein Ziel führt . Meine Situation ist allerdings etwas anders.

Mein Ziel bewegt sich und der Schütze bewegt sich. Außerdem wird die Geschwindigkeit des Schützen zu den Geschwindigkeiten der Kugeln addiert, dh Kugeln, die beim Gleiten nach rechts abgefeuert werden, haben eine größere Geschwindigkeit nach rechts.

Ich versuche, den Feind dazu zu bringen, zu bestimmen, wo er schießen muss, um den Spieler zu treffen. Bei Verwendung der verknüpften SO-Lösung führt der Geschwindigkeitsunterschied zu einem Fehlschlag, sofern der Spieler und der Feind nicht stationär sind. Wie kann ich das verhindern?


Hier ist die Lösung aus der Stapelüberlaufantwort. Es läuft darauf hinaus, eine quadratische Gleichung der Form zu lösen:

a * sqr(x) + b * x + c == 0

Beachten Sie, dass sqrich damit Quadrat im Gegensatz zur Quadratwurzel meine. Verwenden Sie die folgenden Werte:

a := sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed)
b := 2 * (target.velocityX * (target.startX - cannon.X)
      + target.velocityY * (target.startY - cannon.Y))
c := sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y)

Jetzt können wir uns die Diskriminante ansehen, um festzustellen, ob wir eine mögliche Lösung haben.

disc := sqr(b) - 4 * a * c

Wenn die Diskriminante kleiner als 0 ist, vergessen Sie, Ihr Ziel zu treffen - Ihr Projektil kann niemals rechtzeitig dort ankommen. Ansonsten schauen Sie sich zwei mögliche Lösungen an:

t1 := (-b + sqrt(disc)) / (2 * a)
t2 := (-b - sqrt(disc)) / (2 * a)

Beachten Sie, dass wenn disc == 0 ist, t1 und t2 gleich sind.

Azaral
quelle
3
Können Sie bitte versuchen, Ihre Frage zu verdichten? Ich weiß, dass du denkst, du musst wirklich detaillierte Erklärungen geben, aber ich bin wirklich zur Hälfte
durchgekommen
1
Können Sie bitte etwas wie sq()anstelle von verwenden sqr()? Es macht es wirklich verwirrend zu lesen.
Sam Hocevar
@ gardian06 Der Code ohne Erklärung befindet sich am Ende der Frage.
Azaral
@ Sam Hocevar Ich habe alle meine Variablen * durch std :: pow (Variable, 2) ersetzt.
Azaral
Wenn es funktioniert, wenn Sie stationär sind und sich das Ziel bewegt, aber nicht, wenn Sie sich beide bewegen, subtrahieren Sie Ihre Geschwindigkeit von der des Ziels, um die Relativgeschwindigkeit zu erhalten und diese zu verwenden.
George Duckett

Antworten:

5

Okay, lassen Sie uns etwas Vernunft in diese Sache stecken. Ich fürchte, Sie machen es überhaupt nicht einfach, Ihr Code wird nicht kompiliert, ist in Bezug auf Variablennamen inkonsistent ( playerVelocityXwird playerXvelocitynach ein paar Zeilen? Was ist xVelocity?) Und ist zu ausführlich. Es ist im Grunde unmöglich zu debuggen, damit Sie nicht erhebliche Anstrengungen unternehmen.

Also, hier sind die Dinge zu beheben:

Geschossgeschwindigkeit

Die Geschossgeschwindigkeit muss 30Punkt sein. Die von Ihnen durchgeführten Berechnungen sind nicht erforderlich: Die Änderung des Referenzrahmens ist genau dort, um die Komplexität zu vermeiden. Sie addieren die Geschwindigkeit des Feindes erst, nachdem Sie eine Lösung gefunden haben, wenn Sie zum Hauptreferenzrahmen zurückkehren.

Gültigkeit der Lösung

Sie überprüfen nicht, ob die timeLösung positiv ist.

Zahlreiche Codierungsfehler

Sie testen time1und verwenden time2aber immer time1die Ergebnisse.

Sie tun, playerXvelocity - yVelocitywas inkonsistent ist.

Du machst / 2 * astatt / (2.f * a). Dies ist der schlimmste Fehler und deshalb läuft alles schief.

Sie berechnen shootxund shootyals Endposition der Kugel, während Sie nach der Geschwindigkeit der Kugel suchen .

Code behoben

float const bulletSpeed = 30.f;
/* Relative player position */
float const dx = playerX - enemyX;
float const dy = playerY - enemyY;
/* Relative player velocity */
float const vx = playerVelocityX - enemyVelocityX;
float const vy = playerVelocityY - enemyVelocityY;

float const a = vx * vx + vy * vy - bulletSpeed * bulletSpeed;
float const b = 2.f * (vx * dx + vy * dy);
float const c = dx * dx + dy * dy;
float const disc = b * b - 4.f * a * c;

shouldShoot = false;

if (disc >= 0.f)
{
    float t0 = (-b - std::sqrt(disc)) / (2.f * a);
    float t1 = (-b + std::sqrt(disc)) / (2.f * a);
    /* If t0 is negative, or t1 is a better solution, use t1 */
    if (t0 < 0.f || (t1 < t0 && t1 >= 0.f))
        t0 = t1;
    if (t0 >= 0.f)
    {
        /* Compute the ship's heading */
        shootx = vx + dx / t0;
        shooty = vy + dy / t0;
        heading = std::atan2(shooty, shootx) * RAD2DEGREE;
        /* Compute the bullet's velocity by adding the enemy's velocity */
        bulletVelocityX = shootx + enemyVelocityX;
        bulletVelocityY = shooty + enemyVelocityY;

        shouldShoot = true;
    }
}
Sam Hocevar
quelle
Ja, ich habe meinen Code erneut eingegeben und festgestellt, dass ich ein paar Dinge durcheinander gebracht habe. Es ist nicht so inkonsistent in meinem eigentlichen Code, das verspreche ich lol! Ich habe das "Du machst / 2 * a anstelle von / (2.f * a)" bemerkt. letzte Nacht und das behoben. Ich werde Ihren Code ausprobieren. Ich kam hierher, nachdem ich ungefähr vier Stunden lang versucht hatte, dies herauszufinden, und war ziemlich irritiert. Ich denke, deshalb habe ich den Code so schrecklich eingegeben.
Azaral
Ich habe dies in einer einfachen Konsolenausgabe versucht und es funktioniert nicht. Ich habe es kopiert und in ein neues Projekt eingefügt. Ich habe die Spieler- und Feindvariablen hinzugefügt, die nicht aus Ihrem Code deklariert sind. Innerhalb des if (t0> = 0.f) -Bereichs habe ich die Berechnung von Spieler x, y und Kugel x, y hinzugefügt, nachdem die Linie schießen sollte = true; playerX += playerVelocityX*t0; playerY += playerVelocityY*t0; float bulletX = ((cos(heading * DEGREE2RAD)*bulletSpeed + enemyVelocityX) * t0) + enemyX; float bulletY = ((sin(heading * DEGREE2RAD) * bulletSpeed + enemyVelocityY) * t0) + enemyY; Ausgabe ist P = (84.8069.93,0386) E = (88.3793.105.132)
Azaral
An diesem Punkt wäre ich nicht überrascht, wenn ich nur ein Idiot wäre. Ich kann das akzeptieren.
Azaral
Im obigen Beispiel lauten die Originaldaten: Spieler X = 50, Spieler Y = 100, Spieler Geschwindigkeit X = 20, Spieler Geschwindigkeit Y = -4, Feind X = 145, Feind Y = 67, Feind Geschwindigkeit X = -5, Feind Geschwindigkeit = 10.
Azaral
bulletSpeedAn dieser Stelle nicht verwenden . Ersetzen cos(heading * DEGREE2RAD) * bulletSpeeddurch shootx. Gleiches gilt für shooty. Der Grund ist, dass die Geschwindigkeit des Feindes zur Geschwindigkeit des Geschosses addiert wird und seine Geschwindigkeit nicht mehr genau ist bulletSpeed. Tatsächlich benötigen Sie die headingVariable überhaupt nicht, sie ist nur zum Debuggen hilfreich.
Sam Hocevar
2

Ein sich bewegender Schütze ist identisch mit einem stationären Schützen. Subtrahieren Sie einfach den Bewegungsvektor des Schützen vom Bewegungsvektor des Ziels.

Target [-5,0]
Shooter [4,1]
Target - Shooter = [-5,0] - [4,1] = [-9,-1]

Berechnen Sie den Schussvektor / Anfangswinkel und fügen Sie dann den Bewegungsvektor des Ziels wie gewohnt zur Kugel hinzu.

Daniel Carlsson
quelle
Wenn Sie meine Frage lesen und sich den Code ansehen, werden Sie sehen, dass ich das bereits mache.
Azaral
@azaral ja und nein. Sie tun dies, um einen Ihrer Werte zu erhalten, und dann
Dinge
@ gardian06 kannst du genauer sein?
Azaral
@ Azaral Ich hatte den Eindruck, dass Sie einen funktionierenden Algorithmus hatten, als der Schütze stationär war, wie in dem von Ihnen angegebenen Link beschrieben. Wenn es in diesem Fall nicht funktioniert, gibt es einen Fehler in diesem Algorithmus, der sortiert werden muss, bevor Sie sich um einen sich bewegenden Schützen sorgen müssen (nicht, dass der Unterschied groß ist, aber einfacher ist einfacher zu debuggen)
Daniel Carlsson
@ DanielCarlsson Du weißt, das ist ein guter Punkt, an den ich nie gedacht habe. Ich habe nur angenommen, dass der Algorithmus funktioniert.
Azaral
1

Hier ist ein Beispiel, in dem ich eine Lösung für das Problem des Predictive Targeting mithilfe eines rekursiven Algorithmus entwickelt und implementiert habe: http://www.newarteest.com/flash/targeting.html (Ich hatte einen stationären Shooter, aber der gleiche Ansatz würde für a funktionieren beweglicher Schütze)

Ich muss einige der anderen vorgestellten Lösungen ausprobieren, da es effizienter erscheint, sie in einem Schritt zu berechnen. Die Lösung bestand jedoch darin, die Zielposition zu schätzen und das Ergebnis in den Algorithmus zurückzugeben, um eine neue zu erstellen genauere Schätzung, mehrmals wiederholt.

Für die erste Schätzung "feuere" ich auf die aktuelle Position des Ziels und benutze dann Trigonometrie, um zu bestimmen, wo sich das Ziel befindet, wenn der Schuss die Position erreicht, auf die geschossen wird. Dann "feuere" ich in der nächsten Iteration an dieser neuen Position und bestimme, wo das Ziel diesmal sein wird. Nach ungefähr 4 Wiederholungen komme ich auf ein Pixel Genauigkeit.

jhocking
quelle
1
Ein Vorteil dieser Antwort ist, dass der Schütze auch dann um die Position des Spielers feuert, wenn es keine genaue Lösung gibt. +1 dafür.
Sam Hocevar
huh ich hatte nicht mal darüber nachgedacht, guter Punkt
jhocking
0

Da Sie nur mit 2D-Physik arbeiten (keine Z-Geschwindigkeit), kann dieses Problem erheblich vereinfacht werden. Der einfache Weg, dies zu tun, besteht darin, nicht mehr daran zu denken, dass sich Quelle und Ziel relativ zu den Weltkoordinaten bewegen, sondern nur noch daran, dass sich das Ziel relativ zur Quelle bewegt (und die Quelle stationär zu halten).

Vector TargetInitialPosition = new Vector ( target.X - source.X ,
                                            target.Y - source.Y );
Vector TargetApparentVelocity = new Vector( target.velocityX - source.velocityX ,
                                            target.velocityY - source.velocityY );

Normalerweise ist die Geschwindigkeit einer Kugel viel höher als die Geschwindigkeit des Schützen, daher wird normalerweise angenommen, dass die Kugel unabhängig ist, aber es gibt Fälle, in denen dies nicht zutrifft, z. B. das Abfeuern eines Hubschraubers oder eines Kampfjets.

Dann müssen wir die Geschossgeschwindigkeit berechnen:

// Your directional vector MUST be normalized...
Vector BulletVelocity = new Vector( source.directionX * Bullet::StaticSpeed + source.velocityX ,
                                     source.directionY * Bullet::StaticSpeed + source.velocityY );

Das Problem, das Sie haben, ist, dass sich das Ziel bewegt hat, bis die Kugel es erreicht.

TargetPosition = TargetInitialPosition + TargetApparentVelocity * t
BulletPosition = BulletInitialPosition + BulletVelocity * t
               = BulletVelocity * t

und löse nach TargetPosition == BulletPosition, weil dann die Kugel das Ziel getroffen hätte. Jetzt haben Sie drei Unbekannte und nur zwei Gleichungen. Wir können 't' entfernen, indem wir die Ableitung erster Ordnung nehmen:

TargetInitialPosition + ( TargetApparentVelocity - BulletVelocity ) * t == 0
dV / dt = TargetApparentVelocity - BulletVelocity

Nun, um das Ziel zu treffen, möchten Sie dV/dt == -TargetInitialPosition * k. Die Konstante muss in den X- und Y-Koordinaten gleich sein und gibt die Anzahl der Sekunden an, die die Kugel benötigt, um das Ziel zu treffen.

TargetApparentVelocity.X - BulletVelocity.X == k * -TargetInitialPosition.X
k = ( BulletVelocity.X - TargetApparentVelocity.X ) / TargetInitialPosition.X
----------------------
TargetApparentVelocity.Y - BulletVelocity.Y == k * -TargetInitialPosition.Y
k = ( BulletVelocity.Y - TargetApparentVelocity.Y ) / TargetInitialPosition.Y

mach sie gleich:

( BulletVelocity.X - TargetApparentVelocity.X ) / TargetInitialPosition.X
= ( BulletVelocity.Y - TargetApparentVelocity.Y ) / TargetInitialPosition.Y

oder um die Variablen zu erweitern:

( source.directionX * Bullet::StaticSpeed + source.velocityX - target.velocityX + source.velocityX ) / ( target.X - source.X )
 == ( source.directionY * Bullet::StaticSpeed + source.velocityY - target.velocityY + source.velocityY ) / ( target.Y - source.Y )

Dann gibt Ihnen die Algebra Ihre endgültige Gleichung:

source.directionY = ( target.velocityY * ( source.X - target.X ) - 2 * source.velocityY * ( source.X - target.X ) + ( Bullet::Speed * source.directionX + 2 * source.velocityX - target.velocityX ) * ( source.Y - target.Y ) ) / ( Bullet::Speed * ( source.X - target.X ) )

Der nächste Teil ist chaotisch und es liegt an Ihnen, wie Sie ihn in Ihren Code implementieren möchten, aber wir ersetzen ihn einfach in und normalisieren den Vektor.

sqrt( source.directionX ^ 2 + source.directionY ^ 2 ) == 1

Am Ende erhalten Sie eine Gleichung mit nur einem Unbekannten (source.directionX), und Sie können sie für directionX lösen und dann wieder einsetzen, um directionY zu erhalten.

Ich habe keinen dieser Codes getestet und kann auf methodische Fehler hinweisen, die ich gemacht habe, aber die Theorie sollte stichhaltig sein :).

Viel Glück.

tyler.daniels
quelle
Ja, es ist nur 2D. Lese jetzt.
Azaral
Warum addieren Sie die Quellgeschwindigkeit zur Geschwindigkeit des Geschosses?
Sam Hocevar
@ SamHocevar So funktioniert Physik. Eine von einer Waffe abgefeuerte Kugel beginnt mit einer Geschwindigkeit, die der Waffe entspricht, die sie abgefeuert hat. Anschließend wird der durch die Zündaktion erzeugte Vektor zu diesem Vektor hinzugefügt. Die Aktion findet im Weltraum statt, sodass keine Luft vorhanden ist, um die Geschwindigkeit des Geschosses zu ändern. Wenn Sie eine Waffe im Weltraum abfeuern, während Sie sich nach rechts bewegen, befindet sie sich immer vor Ihnen, bis Sie Ihre Geschwindigkeit ändern.
Azaral
1
Entschuldigung, es funktioniert mit Beispielen, es ist nur schwer, es auf den allgemeinen Fall zu abstrahieren. Alles bis auf (dV / dt = TargetApparentVelocity - BulletVelocity) ist Theorie und der einzige Code, den Sie benötigen, sind diese letzten paar Felder. Die Algebra, die ich mir ausgedacht habe, ist viel zu kompliziert, aber Mathematica konnte sie lösen. Der Grund, warum wir das Derivat erster Ordnung nehmen, ist, dass es Ihnen egal ist, wie lange es dauert, bis die Kugel trifft, Sie möchten nur, dass sie trifft.
tyler.daniels
1
@ Azaral so funktioniert Physik nicht . In einem sich gleichmäßig bewegenden Referenzrahmen müssen Sie die Quellgeschwindigkeit von allen Geschwindigkeiten, einschließlich der Geschosse , subtrahieren . Wenn Sie die scheinbare Geschwindigkeit für das Ziel verwenden, müssen Sie auch die scheinbare Geschwindigkeit für das Geschoss verwenden.
Sam Hocevar
0

Dies ist nur ein 3D-Geometrieproblem.

Zuerst benötigen Sie die relative Position und die relative Geschwindigkeit des Schützen und des Ziels:

Pos = TargetPos - ShooterPos
Vel = TargetVel - ShooterVel

Dann müssen Sie die Gleichung lösen:

Pos + t * Vel = t * FireSpeed * [x , +-sqrt(1-x^2)]

Zum t und x.

Das macht:

x = ( PosX + t * VelX ) / ( t * FireSpeed )

( PosY + t * VelY ) / ( t * FireSpeed ) = +-sqrt(1 - (( PosX + t * VelX ) / ( t * FireSpeed ))^2 )
=>
( PosY + t * VelY )^2 / ( t * FireSpeed )^2 = 1 - (( PosX + t * VelX ) / ( t * FireSpeed ))^2
=>
( PosY + t * VelY )^2 + ( PosX + t * VelX )^2 = ( t * FireSpeed )^2
<=>
( Dot(Vel,Vel) - FireSpeed^2 ) * t^2 + 2 * Dot(Vel,Pos) * t + Dot(Pos,Pos) = 0

Die letzte dieser Gleichungen ist eine einfache quadratische Gleichung, die Sie lösen sollten. Für jedes positive Ergebnis fügen Sie das Ergebnis in die Gleichung ein:

FireVector = Vel + Pos / t

Dies sollte Ihnen jeden möglichen Feuervektor geben, wo t die Zeit ist, die der Schuss dauern wird.

Beachten Sie, dass es nur eine Lösung gibt , wenn diese FireSpeedgrößer als die Größe ist. VelWenn sie FireSpeedjedoch kleiner ist, gibt es möglicherweise zwei oder gar keine Lösungen oder in besonderen Fällen nur eine einzige Doppellösung.

Bearbeiten: Besser die Mathematik richtig machen oder diese Antwort wird nicht viel gut sein.

aaaaaaaaaaaa
quelle