Kann ich von A nach B springen?

10

Ich mache eine rudimentäre KI für meinen Side-Scroller und muss wissen, ob eine KI-Einheit Punkt B von Punkt A aus erreichen kann, indem sie einfach einen Sprung macht.

Die Flugbahn meiner Charaktere ist etwas ungewöhnlich, da sie in der Luft Kraft ausüben können (wie zum Beispiel in Jazz Jackrabbit 2), also im Gegensatz zur klassischen Flugbahn eines Projektils, bei der es um ...

Weg, den ein geworfenes oder abgefeuertes Projektil (...) ohne Antrieb nimmt.

... Ich nehme an, mein Problem betrifft eher ein Projektil mit Antrieb (z. B. Rakete).

Um dies zu veranschaulichen, sieht die Flugkurve für meinen Charakter so aus, wenn ich springe und ständig den "linken Knopf" drücke (am linken Ende sieht es anders aus, hier habe ich einige Manöver in der Luft gemacht): Geben Sie hier die Bildbeschreibung ein

Die während des Fluges ausgeübte Kraft ist immer parallel zur X-Achse, also ist es F = (-f, 0), wenn ich "links" halte, und es ist F = (f, 0), wenn ich "rechts" halte.

Er kann sich sehr wie ein Skispringer bewegen:

Geben Sie hier die Bildbeschreibung ein

Es unterscheidet sich also stark von der klassischen Flugbahn, die einfach eine Parabel ist (Quelle: Wikipedia ):

Geben Sie hier die Bildbeschreibung ein

Um es schwieriger zu machen, simuliere ich einen einfachen Luftwiderstand, damit meine Charaktere nur bis zu einem Höchstgeschwindigkeitswert beschleunigen können.

Dies geschieht durch Aufbringen einer kleinen Kraft in die entgegengesetzte Fahrtrichtung :

b2Vec2 vel = body->GetLinearVelocity();
float speed = vel.Normalize(); //normalizes vector and returns length
body->ApplyForce( AIR_RESISTANCE_MULT * speed * speed * -vel, body->GetWorldCenter() );

AIR_RESISTANCE_MULT ist eine Konstante, die in meinem Fall gleich 0,1 ist.

Nehmen wir an, mein Charakter ist ein unendlich kleiner Punkt.

Und ich berücksichtige KEINE Hindernisse, also geht meine Frage so ...

Wie man bei gegebener Anfangsgeschwindigkeit V einen Impuls J = (0, -j) bestimmt (zumindest zuverlässig errät) , den ich beim Sprung auf das Zeichen anwende, Schwerkraft G = (0, g) , Kraft F = (+ -f , 0) wird während des Fluges und AIR_RESISTANCE_MULT kontinuierlich angewendet, wenn wir uns wirklich dazu entschließen, den Luftwiderstand zu berücksichtigen (dies ist optional) , ob ein Punkt unter der Kurve liegt, die durch den Weg gezogen wird, den mein Charakter nehmen wird?

Ich habe buchstäblich keine Ahnung, wo ich mit den Berechnungen anfangen soll, und tatsächlich bin ich nicht unbedingt an einer genauen Antwort interessiert. Ein gut funktionierender Hack / Approximation wäre großartig, da die KI keineswegs perfekt handeln muss.

edit: Ich habe beschlossen, dieses Problem mithilfe einer Simulation zu lösen, wie Jason es vorschlägt, aber wie soll ich mit einem solchen Fall umgehen? Geben Sie hier die Bildbeschreibung ein

Soll ich ein Segment von C nach D zeichnen und prüfen, ob der gewünschte Punkt unter diesem Segment liegt?

Oder sollte ich die Zeitschritte zwischen C und D binär durchsuchen , um nach dem Punkt zu suchen, der in horizontaler Entfernung zum gewünschten Punkt nahe genug ist, und erst dann den vertikalen Unterschied überprüfen? (scheint mir ein bisschen übertrieben)

Patryk Czachurski
quelle
Ich glaube, ich habe eine Lösung für den Fall gefunden, dass wir den Luftwiderstand nicht berücksichtigen: gamedev.stackexchange.com/questions/37916/…
Patryk Czachurski

Antworten:

4

Wie Sie sagen, ist die beste Wahl die Annäherung, in diesem Fall unter Verwendung eines numerischen Schemas. Teilen Sie die Zeit in große Zeitschritte (z. B. 100-300 ms) und verwenden Sie die parabolische Näherung für jeden Zeitschritt. Die Kräfte sind bis auf den Luftwiderstand überall gleich. Der parabolische Pfad dient im Wesentlichen der konstanten Beschleunigung, aber mit dem Luftwiderstand ändert sich die Beschleunigung, da die Kraft von der Geschwindigkeit abhängt. Eine vernünftige Annäherung besteht darin, den Luftwiderstand über jeden Zeitschritt als konstant zu behandeln. Die Verwendung einer quadratischen (dh parabolischen) Näherung bei der Integration ermöglicht es Ihnen jedoch, viel größere Zeitschritte zu verarbeiten. Dann berechnen Sie einfach, bis eine Parabel den gewünschten Punkt in horizontaler Richtung kreuzt, und vergleichen dann die Höhen.

EDIT: Ein bisschen mehr Details zum Vergleich. Sie wissen, dass der Spieler über den Zeitschritt (der in Spielrahmen viele sein kann) das Ziel überquert <targetx,targety>. Ihr Weg wird durch die Position beschrieben, an der <ax*t^2 + bx*t + cx, ay*t^2 + by*t + cy>:

ax = 1/2 * accel.x
bx = velocity.x
cx = position.x

tist die Zeit durch den Zeitschritt ( 0 <= t <= dt) und ähnlich für y. Wenn sich t=0der Charakter also an der vorherigen Position befindet und wann t=dt, befinden sie sich an der nächsten Position. Beachten Sie, dass dies im Grunde das Euler-Update ist, das durch dtersetzt wird, tdamit wir überall entlang der Flugbahn berechnen können. Jetzt wissen wir, dass die x-Position eine quadratische Funktion ist, sodass wir während des Schritts, in dem sich das Zeichen direkt über oder unter dem Ziel befindet, zwei Mal lösen ax*t^2 + bx*t + cx = targetx und (bis zu) erhalten können. Dann werfen wir alle Lösungen aus, die nicht im Bereich [0,dt], da diese nicht im aktuellen Zeitschritt sind. (Fügen Sie aus Gründen der Robustheit den Enden des Bereichs eine kleine Konstante hinzu, damit Sie keine Rundungsprobleme haben.) Jetzt konnten wir keine Lösungen finden (nach dem Filtern). In diesem Fall treffen wir das Ziel in diesem Zeitschritt nicht. Ansonsten bewerten wir ay*t^2 + by*t + cydie Lösungen und vergleichen diese mit targety. Beachten Sie, dass Sie sich an einem Punkt Ihrer Flugbahn über dem Ziel und später darunter befinden können (oder umgekehrt). Sie müssen solche Situationen so interpretieren, wie Sie es möchten.

Das Betrachten einer Reihe von Zeitschritten ist viel einfacher als das Finden einer analytischen Lösung für das ursprüngliche Problem und weitaus flexibler, da Sie das Bewegungsmodell ändern können und dies dennoch ungefähr funktioniert.

Bonuspunkte für die Verwendung variabler Schritte, z. B. 100 ms für die erste Sekunde (zehn Punkte), 200 ms für die nächsten zwei (zehn weitere Punkte), 400 ms über 4 Sekunden usw. Wenn sich Ihr Charakter der Endgeschwindigkeit nähert, ändert sich die Geschwindigkeit in Der Widerstand nimmt ab und Sie benötigen sowieso keine größeren Zeitschritte. Auf diese Weise können Sie wirklich lange Sprünge ohne zu viel Verarbeitung verarbeiten, da die Komplexität für T Sekunden eher O (log T) als O (T) ist.

Sie können auch simulieren, was passiert, wenn der Charakter während seines Sprunges nicht mehr oder in die andere Richtung verstärkt. Mit dem obigen Trick ist die Komplexität O ((log T) ^ 2), was nicht schlecht ist.


quelle
+1, gute Antwort! Wie konnte ich die eigentliche Simulation nicht berücksichtigen. Könnten Sie bitte die "parabolische Approximation" näher erläutern (ich verstehe das nicht ganz)? Meinen Sie nur die Methode zur Integration von Geschwindigkeiten, wie zum Beispiel RK4 und Euler? Wenn ja, könnten Sie es erklären oder zumindest auf einige Informationen zur Durchführung verweisen?
Patryk Czachurski
1
Normalerweise tust du das x'= x + v*dt. Verwenden Sie stattdessen x' = x + v*dt + 1/2*a*dt*dt. Wenn dtes klein ist, dt^2ist es winzig, so dass es bei der traditionellen Euler-Integration in Spielen im Allgemeinen weggelassen wird. Hier dtist nicht klein, also brauchen Sie den Beschleunigungsterm. Da dtauf die zweite Potenz angehoben wird, ist dies eine quadratische Integration, und der Pfad ist eine Parabel, daher eine parabolische Approximation. RK4 berechnet im Wesentlichen höhere Ableitungen und könnte so kubische, quartische, quintische usw. Näherungen ergeben. RK4 ist dafür höchstwahrscheinlich übertrieben, da Stabilität nicht wichtig ist.
und ich nehme an, die Geschwindigkeit selbst sollte wie im traditionellen Euler integriert werden? v' = v + a*dt
Patryk Czachurski
1
Ja. Sie haben keinen Ruck, Sie gehen davon aus, dass es Null ist.
Bitte werfen Sie einen Blick auf die Bearbeitung.
Patryk Czachurski
4

Yay! Ich habe es gemacht!

Ich verwende eine einfache Simulation, bei der die erste Position hinter der vertikalen Achse des Zielpunkts landet. Von dort aus nehme ich die vorherige simulierte Position und erstelle ein Segment. Jetzt überprüfe ich, ob der Zielpunkt unter diesem Segment liegt. Wenn ja, können wir dorthin springen.

Geben Sie hier die Bildbeschreibung ein

Es ist ein vom Spieler kontrollierter Charakter im GIF. Pink ist der vorhergesagte Pfad, gelbe Segmente werden nachfolgende Schrittpositionen vorhergesagt, und das letzte Segment wird weiß, wenn der Zielpunkt darunter liegt, andernfalls rot. Die rote Kurve ist die tatsächliche Flugbahn. Es gibt einige leichte Ungenauigkeiten aufgrund der aktivierten Interpolation des Physikzustands.

Die Berechnungen erwiesen sich als überraschend einfach, aber meine Umgebung so zu machen, wie diese reinen Berechnungen ... war ein massiver Schmerz im Hintern. Zumindest habe ich da draußen einige schwerwiegende Fehler behoben, also war es doch eine nützliche Übung.

Hier ist der vollständige Code in Lua, der zur Lösung des ursprünglichen Problems verwendet wird (der Code setzt voraus, dass Sie über eine eigene Routine "debug_draw" und eine eigene Vektorklasse mit grundlegenden Methoden wie "length_sq" (Länge im Quadrat), "normalize" oder Operatoren +, * verfügen ::

function simple_integration(p, dt)
    local new_p = {}

    new_p.acc = p.acc
    new_p.vel = p.vel + p.acc * dt 
    new_p.pos = p.pos + new_p.vel * dt
    -- uncomment this if you want to use quadratic integration
    -- but with small timesteps even this is an overkill since Box2D itself uses traditional Euler
    -- and I found that for calculations to be accurate I either way must keep the timesteps very low at the beginning of the jump
     --+ p.acc * dt * dt * 0.5

    return new_p
end

function point_below_segment(a, b, p)
    -- make sure a is to the left
    if a.x > b.x then a,b = b,a end

    return ((b.x - a.x)*(p.y - a.y) - (b.y - a.y)*(p.x - a.x)) < 0
end

-- returns true or false
function can_point_be_reached_by_jump
(
gravity, -- vector (meters per seconds^2)
movement_force, -- vector (meters per seconds^2)
air_resistance_mult, -- scalar
queried_point, -- vector (meters)
starting_position, -- vector (meters)
starting_velocity, -- vector (meters per seconds)
jump_impulse, -- vector (meters per seconds)
mass -- scalar (kilogrammes)
)

    local my_point = {
        pos = starting_position,
        vel = starting_velocity + jump_impulse/mass
    }

    local direction_left = movement_force.x < 0
    local step = 1/60

    while true do           
        -- calculate resultant force
        my_point.acc = 
        -- air resistance (multiplier * squared length of the velocity * opposite normalized velocity)
        (vec2(my_point.vel):normalize() * -1 * air_resistance_mult * my_point.vel:length_sq()) / mass
        -- remaining forces
        + gravity + movement_force/mass

        -- I discard any timestep optimizations at the moment as they are very context specific
        local new_p = simple_integration(my_point, step)

        debug_draw(my_point.pos, new_p.pos, 255, 0, 255, 255)
        debug_draw(new_p.pos, new_p.pos+vec2(0, -1), 255, 255, 0, 255)

        if (direction_left and new_p.pos.x < queried_point.x) or (not direction_left and new_p.pos.x > queried_point.x) then
            if point_below_segment(new_p.pos, my_point.pos, queried_point) then
                debug_draw(new_p.pos, my_point.pos, 255, 0, 0, 255)
                return true
            else
                debug_draw(new_p.pos, my_point.pos, 255, 255, 255, 255)
                return false
            end
        else 
            my_point = new_p
        end
    end

    return false
end

Akzeptieren geht an Jason, der mich in die richtige Richtung gebracht hat! Vielen Dank!

Patryk Czachurski
quelle
2

Vielleicht möchten Sie die Antwort "nur berechnen", aber ich bin sicher, dass Sie sie aufgrund der hochgradig interaktiven Natur Ihrer "freien Fall" -Physik als unzureichend empfinden, wenn Sie sie erhalten haben.

Ziehen Sie einen anderen Ansatz in Betracht: Suchen. So geht's für Super Mario AI: http://aigamedev.com/open/interview/mario-ai/

Die Suche nach möglichen Pfaden von A nach B ermöglicht unbegrenzte Interaktivität in der Luft und ist dennoch rechnerisch effizient.

Jonas Bötel
quelle
1
Das ist nur für bestimmte Welten praktisch. Insbesondere begrenzt Mario die Größe des Suchgraphen, indem er ungefähr linear ist, eine begrenzte Anzahl von Geschwindigkeiten aufweist und eine ausgezeichnete Heuristik aufweist. Je nach Spiel ist dies möglicherweise nicht der Fall. Auch rechnerisch effizient ist relativ, da diese KI wahrscheinlich für mehr als einen Charakter / Feind arbeiten müsste, während in Mario nur einer zu kontrollieren ist.