Implementierung eines Wickeldrahtes (wie das Worms Ninja Rope) in einer 2D-Physik-Engine

34

Ich habe in letzter Zeit ein wenig Seilphysik ausprobiert und festgestellt, dass die "Standard" -Lösung, ein Seil aus einer Reihe von Gegenständen herzustellen, die mit Federn oder Gelenken aneinandergereiht sind, unbefriedigend ist. Besonders wenn Seilschwingen für das Gameplay relevant ist. Die Fähigkeit eines Seils, sich einzuwickeln oder durchzubiegen, interessiert mich nicht wirklich (dies kann ohnehin für visuelle Zwecke vorgetäuscht werden).

Für das Gameplay ist es wichtig, dass sich das Seil um die Umgebung wickelt und anschließend abwickelt. Es muss sich nicht einmal wie ein Seil verhalten - ein "Draht" aus geraden Liniensegmenten würde ausreichen. Hier ist eine Illustration:

Ninja-Seil, um Hindernisse gewickelt

Dies ist dem "Ninja Rope" aus dem Spiel Worms sehr ähnlich.

Da ich eine 2D-Physik-Engine verwende, besteht meine Umgebung aus konvexen 2D-Polygonen. (Insbesondere benutze ich SAT in Farseer.)

Meine Frage lautet also: Wie würden Sie den "Wrapping" -Effekt implementieren?

Es scheint ziemlich offensichtlich, dass der Draht aus einer Reihe von Liniensegmenten bestehen wird, die sich "teilen" und "verbinden". Das letzte (aktive) Segment dieser Linie, an dem das sich bewegende Objekt anhaftet, ist eine Verbindung mit fester Länge.

Aber was ist die Mathematik / der Algorithmus, um zu bestimmen, wann und wo das aktive Liniensegment aufgeteilt werden muss? Und wann muss es mit dem vorherigen Segment verbunden werden?

(Zuvor wurde diese Frage auch dazu gestellt, dies für eine dynamische Umgebung zu tun. Ich habe beschlossen, dies in andere Fragen aufzuteilen.)

Andrew Russell
quelle

Antworten:

18

Um festzustellen, wann das Seil geteilt werden muss, müssen Sie den Bereich betrachten, in dem das Seil jeden Rahmen bedeckt. Sie führen eine Kollisionsprüfung mit dem abgedeckten Bereich und Ihrer Ebenengeometrie durch. Der Bereich, den eine Schaukel abdeckt, sollte ein Bogen sein. Wenn es zu einer Kollision kommt, müssen Sie ein neues Segment für das Seil erstellen. Überprüfen Sie, ob Ecken mit dem Schwenkbogen kollidieren. Wenn mehrere Ecken mit dem Schwenkbogen kollidieren, sollten Sie diejenige auswählen, bei der der Winkel zwischen dem Seil während des vorherigen Rahmens und dem Kollisionspunkt am kleinsten ist.

Hilfreiche Darstellung der Ninja-Seilsituation

Bei der Kollisionserkennung werden für die Wurzel des aktuellen Seilsegments O, die Endposition des Seils im vorherigen Frame, A, die Endposition des Seils im aktuellen Frame, B und jeder Eckpunkt P in einem Polygon verwendet Ebenengeometrie berechnen Sie (OA x OP), (OP x OB) und (OA x OB), wobei "x" die Z-Koordinate des Kreuzprodukts zwischen den beiden Vektoren darstellt. Wenn alle drei Ergebnisse das gleiche Vorzeichen haben, negativ oder positiv, und die Länge von OP kleiner als die Länge des Seilsegments ist, befindet sich der Punkt P innerhalb des Bereichs, der von der Schaukel abgedeckt wird, und Sie sollten das Seil teilen. Wenn Sie mehrere kollidierende Eckpunkte haben, sollten Sie den ersten verwenden, der auf das Seil trifft, dh denjenigen, bei dem der Winkel zwischen OA und OP am kleinsten ist. Verwenden Sie das Skalarprodukt, um den Winkel zu bestimmen.

Vergleichen Sie beim Verbinden von Segmenten das vorherige Segment mit dem Bogen Ihres aktuellen Segments. Wenn das aktuelle Segment von links nach rechts geschwungen ist oder umgekehrt, sollten Sie die Segmente verbinden.

Für die Berechnung zum Verbinden von Segmenten verwenden wir den Befestigungspunkt des vorherigen Seilsegments Q sowie denjenigen, den wir für den Aufteilungsfall hatten. Nun wollen Sie die Vektoren QO, OA und OB vergleichen. Wenn sich das Vorzeichen von (QO x OA) vom Vorzeichen von (QO x OB) unterscheidet, hat sich das Seil von links nach rechts oder umgekehrt gekreuzt, und Sie sollten die Segmente verbinden. Dies kann natürlich auch passieren, wenn sich das Seil um 180 Grad dreht. Wenn Sie also möchten, dass sich das Seil anstelle einer polygonalen Form um einen einzelnen Punkt im Raum wickelt, möchten Sie möglicherweise einen Sonderfall hinzufügen.

Diese Methode setzt natürlich voraus, dass Sie eine Kollisionserkennung für das am Seil hängende Objekt durchführen, damit es nicht in der Ebenengeometrie endet.

Pekuja
quelle
1
Das Problem bei diesem Ansatz ist, dass Gleitkomma-Präzisionsfehler es dem Seil ermöglichen, "durch" einen Punkt zu laufen.
Andrew Russell
16

Es ist eine Weile her, dass ich Worms gespielt habe, aber soweit ich mich erinnere, gibt es immer nur einen (geraden) Seilabschnitt, der sich gleichzeitig bewegt, wenn sich das Seil um Dinge wickelt. Der Rest des Seils wird statisch

Es ist also sehr wenig Physik beteiligt. Der aktive Abschnitt kann als einzelne steife Feder mit einer Masse am Ende modelliert werden

Das interessante Bit wird die Logik zum Teilen / Verbinden inaktiver Abschnitte des Seils mit / von dem aktiven Abschnitt sein.

Ich würde mir vorstellen, dass es zwei Hauptoperationen geben würde:

'Split' - Das Seil hat etwas gekreuzt. Teilen Sie es an der Kreuzung in einen inaktiven Abschnitt und den neuen, kürzeren, aktiven Abschnitt auf

'Verbinden' - Das aktive Seil hat eine Position erreicht, an der der nächste Schnittpunkt nicht mehr vorhanden ist (dies kann nur ein einfacher Winkel- / Punkt-Produkttest sein?). Verbinden Sie 2 Abschnitte erneut und erstellen Sie einen neuen, längeren, aktiven Abschnitt

In einer aus 2D-Polygonen aufgebauten Szene sollten sich alle Teilungspunkte an einem Scheitelpunkt auf dem Kollisionsnetz befinden. Die Kollisionserkennung kann sich vereinfachen, wenn das Seil bei dieser Aktualisierung einen Scheitelpunkt überquert, das Seil an diesem Scheitelpunkt teilen / verbinden.

Bluescrn
quelle
2
Dieser Typ war genau auf der Stelle ... Eigentlich ist es kein "steifer" Frühling, Sie drehen nur eine gerade Linie um ...
Speeder
Ihre Antwort ist technisch korrekt. Aber ich ging davon aus, dass es naheliegend war, Liniensegmente zu haben und diese zu teilen und zu verbinden. Ich interessiere mich für den eigentlichen Algorithmus / die Mathematik, um das zu tun. Ich habe meine Frage präzisiert.
Andrew Russell
3

Sehen Sie sich an, wie das Ninja-Seil in Gusanos implementiert wurde:

  • Das Seil wirkt wie ein Partikel, bis es an etwas haftet.
  • Einmal angebracht, übt das Seil nur eine Kraft auf die Schnecke aus.
  • Das Anhängen an dynamische Objekte (wie bei anderen Würmern) ist nach wie vor ein TODO: in diesem Code.
  • Ich kann mich nicht erinnern, ob das Umbrechen von Objekten / Ecken unterstützt wird ...

Relevanter Auszug aus ninjarope.cpp :


void NinjaRope::think()
{
    if ( m_length > game.options.ninja_rope_maxLength )
        m_length = game.options.ninja_rope_maxLength;

    if (!active)
        return;

    if ( justCreated && m_type->creation )
    {
        m_type->creation->run(this);
        justCreated = false;
    }

    for ( int i = 0; !deleteMe && i < m_type->repeat; ++i)
    {
        pos += spd;

        BaseVec<long> ipos(pos);

        // TODO: Try to attach to worms/objects

        Vec diff(m_worm->pos, pos);
        float curLen = diff.length();
        Vec force(diff * game.options.ninja_rope_pullForce);

        if(!game.level.getMaterial( ipos.x, ipos.y ).particle_pass)
        {
            if(!attached)
            {
                m_length = 450.f / 16.f - 1.0f;
                attached = true;
                spd.zero();
                if ( m_type->groundCollision  )
                    m_type->groundCollision->run(this);
            }
        }
        else
            attached = false;

        if(attached)
        {
            if(curLen > m_length)
            {
                m_worm->addSpeed(force / curLen);
            }
        }
        else
        {
            spd.y += m_type->gravity;

            if(curLen > m_length)
            {
                spd -= force / curLen;
            }
        }
    }
}
Leftium
quelle
1
Ähm ... das scheint meine Frage überhaupt nicht zu beantworten. Der ganze Punkt meiner Frage ist, ein Seil um eine Welt aus Polygonen zu wickeln. Gusanos scheint keine Umhüllung und eine Bitmap-Welt zu haben.
Andrew Russell
1

Ich fürchte, ich kann Ihnen keinen konkreten Algorithmus auf den Kopf stellen, aber mir fällt auf, dass es nur zwei Dinge gibt, die für die Erkennung einer Kollision mit dem Ninja-Seil von Bedeutung sind: alle potenziell kollidierenden Scheitelpunkte auf Hindernissen innerhalb eines Radius von der letzten "Teilung" gleich der verbleibenden Länge des Segments; und die aktuelle Schwenkrichtung (im oder gegen den Uhrzeigersinn). Wenn Sie eine temporäre Liste von Winkeln vom "geteilten" Scheitelpunkt zu jedem der nahe gelegenen Scheitelpunkte erstellt haben, muss sich Ihr Algorithmus nur darum kümmern, ob Ihr Segment für einen bestimmten Scheitelpunkt über diesen Winkel hinausschwingen würde. Wenn dies der Fall ist, müssen Sie eine geteilte Operation ausführen, die kinderleicht ist. Es ist nur eine Linie vom letzten geteilten Scheitelpunkt zum neuen geteilten Punkt, und dann wird ein neuer Rest berechnet.

Ich denke nur die Eckpunkte sind wichtig. Wenn Sie Gefahr laufen, auf einem Hindernis ein Segment zwischen den Eckpunkten zu treffen, sollte Ihre normale Kollisionserkennung für den am Ende des Seils hängenden Kerl aktiviert werden. Mit anderen Worten, Ihr Seil wird immer nur "hängen bleiben" Ecken sowieso, so dass die Segmente dazwischen keine Rolle spielen.

Tut mir leid, dass ich nichts Konkretes habe, aber hoffentlich bringt dich das dahin, wo du konzeptionell sein musst, um dies zu verwirklichen. :)

Pi-3
quelle
1

Hier ist ein Beitrag mit Links zu Artikeln über ähnliche Arten von Simulationen (eher im technischen / akademischen Kontext als für Spiele): https://gamedev.stackexchange.com/a/10350/6398

Ich habe mindestens zwei verschiedene Ansätze zur Kollisionserkennung + Reaktion für diese Art von "Draht" -Simulation ausprobiert (wie im Spiel Umihara Kawase zu sehen); Zumindest denke ich, das ist es, wonach Sie suchen - es scheint keinen bestimmten Begriff für diese Art von Simulation zu geben, ich neige dazu, es eher als "Draht" als als "Seil" zu bezeichnen, weil es den meisten Menschen so vorkommt Betrachten Sie "Seil" als Synonym für "eine Kette von Partikeln". Und wenn Sie das klebrige Verhalten eines Ninja-Seils wollen (dh es kann drücken UND ziehen), ähnelt dies eher einem starren Draht als einem Seil. Sowieso..

Die Antwort von Pekuja ist gut. Sie können eine kontinuierliche Kollisionserkennung implementieren, indem Sie die Zeit berechnen, in der der vorzeichenbehaftete Bereich der drei Punkte 0 ist.

(Ich kann mich nicht vollständig an OTOH erinnern, aber Sie können es wie folgt angehen: Ermitteln Sie die Zeit t, wenn der Punkt a in der durch b, c verlaufenden Linie enthalten ist 0, um Werte von t) zu finden, und dann, wenn eine gültige Zeit 0 <= t <1 gegeben ist, finde die parametrische Position s von a auf dem Segment bc, dh a = (1-s) b + s c und wenn a zwischen liegt b und c (dh wenn 0 <= s <= 1) ist es eine gültige Kollision.

AFAICR Sie können auch umgekehrt vorgehen (dh nach s auflösen und dieses dann einstecken, um t zu finden), aber es ist viel weniger intuitiv. (Es tut mir leid, wenn dies keinen Sinn ergibt, ich habe keine Zeit, meine Notizen auszugraben, und es sind ein paar Jahre vergangen!)

So können Sie jetzt alle Zeiten berechnen, zu denen Ereignisse auftreten (dh Seilknoten sollten eingefügt oder entfernt werden). Verarbeiten Sie das früheste Ereignis (Einfügen oder Entfernen eines Knotens) und wiederholen / rekursieren Sie es, bis zwischen t = 0 und t = 1 keine Ereignisse mehr vorhanden sind.

Eine Warnung zu diesem Ansatz: Wenn die Objekte, um die sich das Seil wickeln kann, dynamisch sind (insbesondere, wenn Sie sie und ihre Auswirkungen auf das Seil simulieren und umgekehrt), kann es zu Problemen kommen, wenn diese Objekte jeweils durchtrennen Anderes - der Draht kann sich verwickeln. Und es wird definitiv eine Herausforderung sein, diese Art von Interaktion / Bewegung (die Ecken von Objekten rutschen durcheinander) in einer Box2D-ähnlichen Physiksimulation zu verhindern. Geringe Durchdringungswerte zwischen Objekten sind in diesem Kontext normal.

(Zumindest .. das war ein Problem mit einer meiner Implementierungen von "wire".)

Eine andere Lösung, die viel stabiler ist, aber unter bestimmten Bedingungen einige Kollisionen auslässt, besteht darin, nur statische Tests zu verwenden (dh sich keine Gedanken über die zeitliche Reihenfolge zu machen, sondern jedes Segment rekursiv in Kollisionen zu unterteilen, wie Sie es finden) viel robuster - der Draht verwickelt sich nicht in Ecken und kleine Mengen an Eindringen sind in Ordnung.

Ich denke, Pekujas Ansatz funktioniert auch hier, es gibt jedoch alternative Ansätze. Ein Ansatz, den ich verwendet habe, besteht darin, zusätzliche Kollisionsdaten hinzuzufügen: Fügen Sie an jedem konvexen Scheitelpunkt v der Welt (dh an den Ecken von Formen, um die sich das Seil wickeln kann) einen Punkt u hinzu, der das gerichtete Liniensegment uv bildet, wobei u einiges ist Punkt "innerhalb der Ecke" (dh innerhalb der Welt, "hinter" v; um u zu berechnen, können Sie einen Strahl von v aus entlang seiner interpolierten Normalen nach innen werfen und eine Strecke nach v anhalten oder bevor der Strahl einen Rand der Welt schneidet und Sie können die Segmente auch manuell mit einem visuellen Werkzeug / Ebenen-Editor in die Welt malen.

Wie auch immer, Sie haben jetzt eine Reihe von "Ecklinien" UV; Überprüfen Sie für jedes UV und jedes Segment ab in der Leitung, ob sich ab und uv überschneiden (dh statische, boolesche Lineseg-Lineseg-Schnittabfrage). Wenn ja, rekursieren Sie (teilen Sie das Lineseg ab in av und vb, dh fügen Sie v ein) und notieren Sie, in welche Richtung das Seil bei v gebogen ist. Prüfen Sie dann für jedes Paar benachbarter Linesegs ab, bc im Draht, ob die aktuelle Biegerichtung bei b vorliegt ist dasselbe wie bei der Erzeugung von b (alle diese "Biegerichtungstests" sind nur Tests mit vorzeichenbehafteten Bereichen); Wenn nicht, füge die beiden Segmente zu ac zusammen (dh entferne b).

Oder vielleicht habe ich fusioniert und dann geteilt, vergesse ich - aber es funktioniert definitiv in mindestens einer der beiden möglichen Bestellungen! :)

Wenn Sie alle für den aktuellen Frame berechneten Drahtsegmente vorausgesetzt haben, können Sie eine Abstandsbeschränkung zwischen den beiden Drahtendpunkten simulieren (und Sie können sogar die inneren Punkte einbeziehen, dh die Kontaktpunkte zwischen Draht und Welt, aber das ist etwas komplizierter ).

Wie auch immer, hoffentlich wird dies von Nutzen sein ... die Artikel in dem Beitrag, den ich auch verlinkt habe, sollten Ihnen auch einige Ideen geben.

Raigan
quelle
0

Ein Ansatz besteht darin, das Seil als kollidierbare Partikel zu modellieren, die durch Federn verbunden sind. (ziemlich steif, möglicherweise sogar nur als Knochen). Die Partikel kollidieren mit der Umgebung und sorgen dafür, dass sich das Seil um Gegenstände wickelt.

Hier ist eine Demo mit Quelle: http://www.ewjordan.com/rgbDemo/

(Gehen Sie in der ersten Ebene nach rechts, es gibt ein rotes Seil, mit dem Sie interagieren können.)

Rachel Blum
quelle
2
Äh - das ist genau das, was ich nicht will (siehe die Frage).
Andrew Russell
Ah. Das war aus der ursprünglichen Frage nicht klar. Vielen Dank, dass Sie sich die Zeit genommen haben, dies zu klären. (Tolles Diagramm!) Ich würde immer noch mit einer Reihe sehr kleiner fester Gelenke arbeiten, anstatt die dynamischen Teilungen durchzuführen - es sei denn, es handelt sich um ein großes Leistungsproblem in Ihrer Umgebung, das Codieren ist viel einfacher.
Rachel Blum