Quando i mondi si scontrano simulando le collisioni cerchio-cerchio

La maggior parte del rilevamento delle collisioni nei giochi per computer viene eseguita utilizzando la tecnica AABB: molto semplicemente, se due rettangoli si intersecano, si verifica una collisione. È veloce, efficiente e incredibilmente efficace - per oggetti rettangolari. E se volessimo distruggere cerchi insieme? Come calcoliamo il punto di collisione e dove vanno gli oggetti? Non è così difficile come potresti pensare ...

Nota: Sebbene questo tutorial sia scritto usando AS3 e Flash, dovresti essere in grado di utilizzare le stesse tecniche e concetti in quasi tutti gli ambienti di sviluppo di giochi.

Anteprima immagine presa da questo classico tutorial di Psdtuts +.


Passaggio 1: crea alcune palle

Ho intenzione di sorvolare questo passaggio, perché se non riesci a creare sprite di base, il resto del tutorial sarà un po 'al di là di te.

Basti dire che abbiamo un punto di partenza. Di seguito una simulazione molto rudimentale di palline che rimbalzano su uno schermo. Se toccano il bordo dello schermo, si riprendono.

C'è, tuttavia, una cosa importante da notare: spesso, quando crei sprite, la parte in alto a sinistra è impostata sull'origine (0, 0) e l'in basso a destra è (larghezza altezza). Qui, i cerchi che abbiamo creato sono centrati sullo sprite.

Questo rende tutto molto più semplice, poiché se i cerchi non sono centrati, per più o meno ogni calcolo dobbiamo compensarlo per il raggio, eseguire il calcolo, quindi resettarlo indietro.

Puoi trovare il codice fino a questo punto nel v1 cartella del download sorgente.


Passaggio 2: verifica la presenza di sovrapposizioni

La prima cosa che vogliamo fare è controllare se le nostre palle sono vicine l'una all'altra. Ci sono alcuni modi per farlo, ma poiché vogliamo essere dei bravi programmatori, inizieremo con un controllo AABB.

AABB è l'acronimo di bounding box allineato all'asse e fa riferimento a un rettangolo disegnato per adattarsi saldamente a un oggetto, allineato in modo che i lati siano paralleli agli assi.

Un controllo collisione AABB fa non controlla se i cerchi si sovrappongono, ma ci fa sapere se lo sono vicino l'un l'altro. Poiché il nostro gioco utilizza solo quattro oggetti, questo non è necessario, ma se stessimo eseguendo una simulazione con 10.000 oggetti, fare questa piccola ottimizzazione ci farebbe risparmiare molti cicli della CPU.

Quindi eccoci qui:

 if (firstBall.x + firstBall.radius + secondBall.radius> secondBall.x && firstBall.x < secondBall.x + firstBall.radius + secondBall.radius && firstBall.y + firstBall.radius + secondBall.radius > secondBall.y && firstBall.y < seconBall.y + firstBall.radius + secondBall.radius)  //AABBs are overlapping 

Questo dovrebbe essere abbastanza semplice: stiamo creando caselle di delimitazione delle dimensioni del diametro di ciascuna sfera al quadrato.

Qui si è verificata una "collisione" - o meglio, le due AABB si sovrappongono, il che significa che i cerchi sono vicini l'uno all'altro e potenzialmente in collisione.

Una volta che sappiamo che le palle sono vicine, possiamo essere un po 'più complesse. Usando la trigonometria, possiamo determinare la distanza tra i due punti:

 distance = Math.sqrt (((firstBall.x - secondBall.x) * (firstBall.x - secondBall.x)) + ((firstBall.y - secondBall.y) * (firstBall.y - secondBall.y))) ; se (distanza < firstBall.radius + secondBall.radius)  //balls have collided 

Qui, stiamo usando il teorema di Pitagora, a ^ 2 + b ^ 2 = c ^ 2, per capire la distanza tra i centri dei due cerchi.

Non conosciamo immediatamente la lunghezza di un e B, ma conosciamo le coordinate di ogni palla, quindi è banale capire:

 a = firstBall.x - secondBall.x; b = firstBall.y - secondBall.y;

Quindi risolviamo c con un po 'di riarrangiamento algebrico: c = Math.sqrt (a ^ 2 + b ^ 2) - quindi questa parte del codice:

 distance = Math.sqrt (((firstBall.x - secondBall.x) * (firstBall.x - secondBall.x)) + ((firstBall.y - secondBall.y) * (firstBall.y - secondBall.y))) ;

Quindi controlliamo questo valore rispetto alla somma dei raggi dei due cerchi:

 se (distanza < firstBall.radius + secondBall.radius)  //balls have collided 

Perché controlliamo i raggi combinati dei cerchi? Bene, se guardiamo l'immagine qui sotto, allora possiamo vedere che - non importa in quale angolo i cerchi si toccano - se stanno toccando la linea allora c è uguale a r1 + r2.

Quindi se c è uguale o inferiore a r1 + r2, allora i cerchi devono essere toccanti. Semplice!

Si noti inoltre che, per calcolare correttamente le collisioni, è probabile che si desideri spostare prima tutti gli oggetti, quindi eseguire il rilevamento delle collisioni su di essi. Altrimenti potresti avere una situazione in cui ball1 aggiornamenti, controlli per le collisioni, collisioni, quindi Ball2 aggiornamenti, non è più nella stessa area di ball1, e non segnala alcuna collisione. Oppure, in termini di codice:

 per (n = 0; n 

è molto meglio di

 per (n = 0; n   

Puoi trovare il codice fino a questo punto nel v2 cartella del download sorgente.


Passaggio 3: calcolare i punti di collisione

Questa parte non è realmente necessaria per le collisioni con la palla, ma è piuttosto interessante, quindi la sto lanciando. Se vuoi solo rimbalzare tutto, puoi saltare al prossimo passaggio.

A volte può essere utile calcolare il punto in cui due palle si sono scontrate. Se si desidera, ad esempio, aggiungere un effetto particellare (forse una piccola esplosione), o si sta creando una sorta di linea guida per un gioco di snooker, allora può essere utile conoscere il punto di collisione.

Ci sono due modi per risolverlo: nel modo giusto e nel modo sbagliato.

Il modo sbagliato, utilizzato da molti tutorial, è di fare una media dei due punti:

 collisionPointX = (firstBall.x + secondBall.x) / 2 collisionPointY = (firstBall.y + secondBall.y) / 2

Funziona, ma solo se le palle hanno le stesse dimensioni.

La formula che vogliamo utilizzare è leggermente più complicata, ma funziona per palloni di tutte le dimensioni:

 collisionPointX = ((firstBall.x * secondBall.radius) + (secondBall.x * firstBall.radius)) / (firstBall.radius + secondBall.radius); collisionPointY = ((firstBall.y * secondBall.radius) + (secondBall.y * firstBall.radius)) / (firstBall.radius + secondBall.radius);

Questo usa i raggi delle sfere per darci le coordinate xey del punto di collisione, rappresentate dal punto blu nella demo qui sotto.

Puoi trovare il codice fino a questo punto nel v3 cartella del download sorgente.


Fase 4: rimbalzare a parte

Ora, sappiamo quando i nostri oggetti si scontrano l'un l'altro e conosciamo la loro velocità e le loro posizioni x e y. Come lavoriamo su dove viaggiano dopo?

Possiamo fare qualcosa chiamato collisione elastica.

Cercare di spiegare a parole come funziona una collisione elastica può essere complicato: la seguente immagine animata dovrebbe chiarire le cose.


Immagine da http://en.wikipedia.org/wiki/Collezione elastica

Semplicemente, stiamo usando più triangoli.

Ora, possiamo calcolare la direzione di ogni palla, ma potrebbero esserci altri fattori al lavoro. Spin, attrito, il materiale di cui sono fatte le palle, massa e innumerevoli altri fattori possono essere applicati cercando di fare la collisione "perfetta". Ci preoccuperemo solo di uno di questi: la massa.

Nel nostro esempio, assumeremo che il raggio delle palline che stiamo usando sia anche la loro massa. Se ci sforzassimo per il realismo, allora questo sarebbe inaccurato poiché - supponendo che le sfere fossero tutte realizzate con lo stesso materiale - la massa delle sfere sarebbe proporzionale alla loro area o volume, a seconda che volessi o meno prenderle in considerazione dischi o sfere. Tuttavia, poiché si tratta di un gioco semplice, sarà sufficiente utilizzare i loro raggi.

Possiamo usare la seguente formula per calcolare la variazione della velocità x della prima palla:

 newVelX = (firstBall.speed.x * (firstBall.mass - secondBall.mass) + (2 * secondBall.mass * secondBall.speed.x)) / (firstBall.mass + secondBall.mass);

Quindi, andiamo oltre questo semplicemente:

  • Supponiamo che entrambe le palle abbiano la stessa massa (diremo 10).
  • La prima palla si muove a 5 unità / aggiornamento (a destra). La seconda palla si sposta -1 unità / aggiornamento (a sinistra).
 NewVelX = (5 * (10-10) + (2 * 10 * -1)) / (10 + 10) = (5 * 0) + (-20) / 20 = -20/20 = -1

In questo caso, ipotizzando una collisione frontale, la prima pallina inizierà a muoversi a -1 unità / aggiornamento. (A sinistra). Poiché le masse delle palline sono uguali, la collisione è diretta e nessuna energia è stata persa, le sfere avranno velocità "scambiate". Cambiare uno qualsiasi di questi fattori cambierà ovviamente il risultato.

(Se usi lo stesso calcolo per capire la nuova velocità della seconda palla, scoprirai che si sta muovendo a 5 unità / aggiornamento, a destra).

Possiamo usare questa stessa formula per calcolare le velocità x / y di entrambe le sfere dopo la collisione:

 newVelX1 = (firstBall.speed.x * (firstBall.mass - secondBall.mass) + (2 * secondBall.mass * secondBall.speed.x)) / (firstBall.mass + secondBall.mass); newVelY1 = (firstBall.speed.y * (firstBall.mass - secondBall.mass) + (2 * secondBall.mass * secondBall.speed.y)) / (firstBall.mass + secondBall.mass); newVelX2 = (secondBall.speed.x * (secondBall.mass - firstBall.mass) + (2 * firstBall.mass * firstBall.speed.x)) / (firstBall.mass + secondBall.mass); newVelY2 = (secondBall.speed.y * (secondBall.mass - firstBall.mass) + (2 * firstBall.mass * firstBall.speed.y)) / (firstBall.mass + secondBall.mass);

Speriamo sia evidente che ogni calcolo è lo stesso, semplicemente sostituendo i valori di conseguenza.

Una volta fatto, abbiamo le nuove velocità di ogni palla. Quindi abbiamo finito? Non proprio.

In precedenza, ci siamo assicurati di fare tutti i nostri aggiornamenti di posizione in una volta, e poi verificare la presenza di collisioni. Ciò significa che quando controlliamo le palle in collisione, è molto probabile che una palla sia "in" un'altra - così quando viene chiamato il rilevamento di collisione, sia la prima palla che la seconda palla registreranno quella collisione, il che significa che i nostri oggetti potrebbero ottenere bloccati insieme.

(Se le palle si stanno dirigendo insieme, la prima collisione invertirà le loro direzioni - così si allontaneranno - e la seconda collisione invertirà di nuovo la loro direzione, facendoli muovere insieme).

Ci sono diversi modi per affrontarlo, come l'implementazione di un booleano che controlla se le palline hanno già fatto collisione con questo fotogramma, ma il modo più semplice è semplicemente spostare ciascuna palla per la nuova velocità. Ciò significa, in linea di principio, che le sfere dovrebbero spostarsi a parte della stessa quantità di velocità che hanno spostato insieme - ponendole a una distanza uguale al telaio prima che entrassero in collisione.

 firstBall.x = firstBall.x + newVelX1; firstBall.y = firstBall.y + newVelY1; secondBall.x = secondBall.x + newVelX2; secondBall.y = secondBall.y + newVelY2;

E questo è tutto!

Puoi vedere il prodotto finale qui:

E il codice sorgente fino a questa parte è disponibile in v4 cartella del download sorgente.

Grazie per aver letto! Se desideri saperne di più sui metodi di rilevamento delle collisioni, consulta questa sessione. Potresti anche essere interessato a questo tutorial su quadtrees e questo tutorial sul test dell'asse di separazione.