Implementare un filo avvolgente (come la corda Ninja di Worms) in un motore fisico 2D


34

Recentemente ho provato un po 'di fisica della corda e ho scoperto che la soluzione "standard" - fare una corda da una serie di oggetti infilati insieme a molle o giunti - non è soddisfacente. Soprattutto quando l'oscillazione della corda è rilevante per il gameplay. Non mi interessa davvero la capacità di una corda di avvolgere o abbassarsi (questo può comunque essere simulato per la grafica).

Per il gameplay, ciò che è importante è la capacità della corda di avvolgere l'ambiente e successivamente di svolgersi. Non deve nemmeno comportarsi come una corda: un "filo" fatto di segmenti di linea retta farebbe. Ecco un'illustrazione:

Corda ninja, avvolgendo ostacoli

Questo è molto simile al "Ninja Rope" del gioco Worms.

Poiché sto usando un motore fisico 2D - il mio ambiente è costituito da poligoni convessi 2D. (In particolare sto usando SAT in Farseer.)

Quindi la mia domanda è questa: come implementeresti l'effetto "avvolgimento"?

Sembra abbastanza ovvio che il filo sarà costituito da una serie di segmenti di linea che "si dividono" e "uniscono". E il segmento (attivo) finale di quella linea, dove si collega l'oggetto in movimento, sarà un giunto a lunghezza fissa.

Ma che cosa comporta la matematica / algoritmo per determinare quando e dove il segmento di linea attiva deve essere diviso? E quando deve essere unito al segmento precedente?

(In precedenza questa domanda chiedeva anche di farlo in un ambiente dinamico - ho deciso di dividerlo in altre domande.)

Risposte:


18

Per determinare quando dividere la corda, è necessario guardare l'area in cui la corda copre ogni telaio. Quello che fai è fare un controllo delle collisioni con l'area coperta e la geometria del tuo livello. L'area coperta da un'altalena dovrebbe essere un arco. In caso di collisione, è necessario creare un nuovo segmento sulla fune. Verificare la presenza di angoli che si scontrano con l'arco oscillante. Se ci sono più angoli che si scontrano con l'arco di oscillazione, dovresti scegliere quello in cui l'angolo tra la corda durante il telaio precedente e il punto di collisione è il più piccolo.

Diagramma utile della situazione della corda ninja

Il modo in cui si esegue il rilevamento delle collisioni è quello per la radice del segmento di corda corrente, O, la posizione finale della corda sul telaio precedente, A, la posizione finale della corda sul telaio corrente, B e ogni punto d'angolo P in una poligonale geometria di livello, si calcola (OA x OP), (OP x OB) e (OA x OB), dove "x" rappresenta la presa della coordinata Z del prodotto incrociato tra i due vettori. Se tutti e tre i risultati hanno lo stesso segno, negativo o positivo e la lunghezza di OP è inferiore alla lunghezza del segmento della fune, il punto P si trova nell'area coperta dall'oscillazione e si dovrebbe dividere la fune. Se hai più punti d'angolo in collisione, ti consigliamo di utilizzare il primo che colpisce la corda, ovvero quello in cui l'angolo tra OA e OP è il più piccolo. Utilizzare il prodotto punto per determinare l'angolo.

Per quanto riguarda l'unione di segmenti, fai un confronto tra il segmento precedente e l'arco del segmento corrente. Se il segmento corrente si è spostato dal lato sinistro al lato destro o viceversa, è necessario unire i segmenti.

Per la matematica per unire i segmenti useremo il punto di attacco del precedente segmento di corda, Q, così come quelli che avevamo per il caso di divisione. Quindi ora, ti consigliamo di confrontare i vettori QO, OA e OB. Se il segno di (QO x OA) è diverso dal segno di (QO x OB), la fune ha attraversato da sinistra a destra o viceversa e dovresti unire i segmenti. Questo ovviamente può accadere anche se la corda oscilla di 180 gradi, quindi se vuoi che la corda sia in grado di avvolgere un singolo punto nello spazio anziché una forma poligonale, potresti voler aggiungere un caso speciale per quello.

Questo metodo ovviamente presuppone che tu stia facendo un rilevamento delle collisioni per l'oggetto che pende dalla corda, in modo che non finisca all'interno della geometria del livello.


1
Il problema con questo approccio è che gli errori di precisione in virgola mobile consentono alla fune di "attraversare" un punto.
Andrew Russell,

16

È da un po 'che non gioco a Worms, ma da quello che ricordo: quando la corda avvolge le cose, c'è solo una sezione (dritta) di corda che si muove in qualsiasi momento. Il resto della corda diventa statico

Quindi c'è pochissima fisica reale coinvolta. La sezione attiva può essere modellata come una singola molla rigida con una massa all'estremità

Il bit interessante sarà la logica per dividere / unire sezioni inattive della fune alla / dalla sezione attiva.

Immagino che ci sarebbero 2 operazioni principali:

'Split' - La corda ha intersecato qualcosa. Dividilo all'intersezione in una sezione inattiva e nella nuova sezione più corta e attiva

"Unisci": la fune attiva si è spostata in una posizione in cui l'intersezione più vicina non esiste più (potrebbe trattarsi di un semplice test del prodotto ad angolo / punto?). Ricongiungere 2 sezioni, creando una nuova sezione più lunga e attiva

In una scena costruita con poligoni 2D, tutti i punti di divisione devono trovarsi in corrispondenza di un vertice sulla mesh di collisione. Il rilevamento delle collisioni può semplificare fino a qualcosa sulla falsariga di "Se la fune passa sopra un vertice in questo aggiornamento, dividere / unire la fune in quel vertice?


2
Questo ragazzo era sul posto ... In realtà, non è una molla "rigida", basta solo ruotare una linea retta intorno ...
Speeder

La tua risposta è tecnicamente corretta. Ma ho supposto che avere segmenti di linea, dividere e unirli fosse ovvio. Sono interessato all'algoritmo / alla matematica attuali per farlo. Ho reso la mia domanda più specifica.
Andrew Russell,

3

Scopri come è stata implementata la corda ninja a Gusanos :

  • La corda si comporta come una particella fino a quando non si attacca a qualcosa.
  • Una volta attaccata, la corda applica solo una forza sul verme.
  • Il collegamento ad oggetti dinamici (come altri worm) è ancora un TODO: in questo codice.
  • Non ricordo se è supportato il wrapping intorno a oggetti / angoli ...

Estratto rilevante da 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;
            }
        }
    }
}

1
Uhm ... questo non sembra rispondere alla mia domanda. L'intero punto della mia domanda è avvolgere una corda attorno a un mondo fatto di poligoni. Gusanos sembra non avere un avvolgimento e un mondo bitmap.
Andrew Russell,

1

Temo di non poterti dare un algoritmo concreto dalla parte superiore della mia testa, ma mi viene in mente che ci sono solo due cose che contano per rilevare una collisione per la corda del ninja: qualsiasi vertice potenzialmente in collisione su ostacoli entro un raggio dell'ultima "divisione" uguale alla lunghezza rimanente del segmento; e la direzione corrente di oscillazione (in senso orario o antiorario). Se hai creato un elenco temporaneo di angoli dal vertice "diviso" a ciascuno dei vertici vicini, il tuo algoritmo dovrebbe solo preoccuparsi se il tuo segmento stava per oscillare oltre quell'angolo per un dato vertice. In tal caso, devi eseguire un'operazione di divisione, che è facile come una torta: è solo una linea dall'ultimo vertice di divisione alla nuova divisione, quindi viene calcolato un nuovo resto.

Penso che contino solo i vertici. Se sei in pericolo di colpire un segmento tra i vertici su un ostacolo, allora il tuo normale rilevamento di collisioni per il ragazzo che pende all'estremità della fune dovrebbe entrare. In altre parole, la tua fune si "aggancerà" sempre angoli comunque, quindi i segmenti tra non importeranno.

Mi dispiace non avere nulla di concreto, ma spero che ti porti dove devi essere, concettualmente, per farlo accadere. :)


1

Ecco un post che contiene collegamenti a documenti su tipi simili di simulazioni (in contesti ingegneristici / accademici piuttosto che per i giochi): https://gamedev.stackexchange.com/a/10350/6398

Ho provato almeno due approcci diversi al rilevamento delle collisioni + risposta per questo tipo di simulazione "wire" (come visto nel gioco Umihara Kawase); almeno, penso che questo sia ciò che stai cercando: non sembra esserci un termine specifico per questo tipo di simulazione, tendo solo a chiamarlo "filo" piuttosto che "corda" perché sembra che la maggior parte delle persone considera "corda" sinonimo di "una catena di particelle". E, se vuoi il comportamento stick-ish della corda ninja (cioè può spingere E tirare), questo è più simile a un filo rigido che a una corda. Comunque..

La risposta di Pekuja è buona, è possibile implementare il rilevamento continuo delle collisioni risolvendo per il momento in cui l'area firmata dei tre punti è 0.

(Non riesco a ricordare completamente OTOH ma puoi avvicinarti come segue: trova il tempo t quando il punto a è contenuto nella linea che passa attraverso b, c, (penso di averlo fatto risolvendo per quando punto (ab, cb) = 0 per trovare i valori di t), e quindi dato un tempo valido 0 <= t <1, trova la posizione parametrica s di a sul segmento bc, ovvero a = (1-s) b + s c e se a è compreso tra bec (cioè se 0 <= s <= 1) è una collisione valida.

AFAICR puoi affrontarlo anche al contrario (cioè risolvi per s e poi collegalo per trovare t) ma è molto meno intuitivo. (Mi dispiace se questo non ha alcun senso, non ho tempo di scavare i miei appunti ed è passato qualche anno!))

Quindi, ora è possibile calcolare tutte le volte in cui si verificano eventi (ad esempio, i nodi della corda devono essere inseriti o rimossi); elaborare il primo evento (inserire o rimuovere un nodo) e quindi ripetere / ricorrere fino a quando non ci sono più eventi tra t = 0 e t = 1.

Un avvertimento su questo approccio: se gli oggetti che la corda può avvolgere sono dinamici (specialmente se li stai simulando E i loro effetti sulla corda, e viceversa), ci possono essere problemi se quegli oggetti si agganciano / passano attraverso ciascuno altro: il filo può impigliarsi. E sarà sicuramente difficile impedire questo tipo di interazione / movimento (gli angoli degli oggetti che scivolano l'uno nell'altro) in una simulazione fisica in stile box2d. Piccole quantità di penetrazione tra oggetti sono comportamenti normali in quel contesto.

(Almeno .. questo è stato un problema con una delle mie implementazioni di "wire".)

Una soluzione diversa, che è molto più stabile ma che manca alcune collisioni in determinate condizioni è quella di utilizzare solo i test statici (cioè non preoccuparti di ordinare in base al tempo, basta suddividere ricorsivamente ogni segmento in collisione quando li trovi), che può essere molto più robusto - il filo non si impiglia negli angoli e piccole quantità di penetrazione andranno bene.

Penso che l'approccio di Pekuja funzioni anche per questo, tuttavia ci sono approcci alternativi. Un approccio che ho usato è quello di aggiungere dati di collisione ausiliari: ad ogni vertice convesso v nel mondo (cioè gli angoli delle forme che la corda può avvolgere), aggiungi un punto u che forma il segmento di linea diretta uv, dove sei punto "dentro l'angolo" (cioè dentro il mondo, "dietro" v; per calcolare u puoi lanciare un raggio verso l'interno da v lungo la sua normale interpolata e fermarti a una certa distanza dopo v o prima che il raggio si intersechi con un bordo del mondo e esce dalla regione solida oppure puoi semplicemente dipingere manualmente i segmenti nel mondo usando uno strumento visivo / editor di livelli).

Ad ogni modo, ora hai una serie di "corner linesegs" uv; per ogni uv e ogni segmento ab nel filo, verificare se ab e uv si intersecano (ovvero query di intersezione statica, booleana lineeg-lineseg); in tal caso, ricorrere (dividere la lineeg ab in av e vb, ovvero inserire v), registrando in quale direzione la fune piegata a v. Quindi per ciascuna coppia di lineeg vicine ab, bc nel filo, verificare se l'attuale direzione di piega a b è lo stesso di quando è stato generato b (tutti questi test di "direzione di piega" sono solo test di area con segno); in caso contrario, unire i due segmenti in ac (cioè rimuovere b).

O forse mi sono unito e poi diviso, dimentico - ma funziona sicuramente in almeno uno dei due possibili ordini! :)

Dati tutti i segmenti di filo calcolati per il fotogramma corrente, puoi quindi simulare un vincolo di distanza tra i due punti terminali del filo (e puoi anche coinvolgere i punti interni, cioè i punti di contatto tra il filo e il mondo, ma è un po 'più coinvolto ).

Comunque, si spera che questo possa essere di qualche utilità ... anche i documenti nel post che ho collegato dovrebbero darvi alcune idee.


0

Un approccio a questo è di modellare la corda come particelle collidabili, collegate da molle. (abbastanza rigidi, forse anche solo come un osso). Le particelle si scontrano con l'ambiente, assicurandosi che la corda avvolga gli oggetti.

Ecco una demo con fonte: http://www.ewjordan.com/rgbDemo/

(Spostati a destra al primo livello, c'è una corda rossa con cui puoi interagire)


2
Uh - questo è specificamente ciò che non voglio (vedi la domanda).
Andrew Russell,

Ah. Non era chiaro dalla domanda originale. Grazie per il tempo dedicato a chiarirlo così tanto. (Ottimo diagramma!) Continuerei con una serie di giunti fissi molto piccoli, invece di eseguire le divisioni dinamiche - a meno che non sia un grosso problema di prestazioni nel tuo ambiente, è molto più facile da programmare.
Rachel Blum,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.