När världar kolliderar Simulerar cirkelcirkelkollisioner

Mest kollisionsdetektering i datorspel görs med AABB-tekniken: helt enkelt, om två rektanglar skärs, så har en kollision inträffat. Det är snabbt, effektivt och otroligt effektivt - för rektangulära föremål. Men vad händer om vi ville krossa cirklarna tillsammans? Hur beräknar vi kollisionspunkten och var går objekten efter? Det är inte så svårt som du kanske tror ...

Notera: Även om denna handledning skrivs med AS3 och Flash, borde du kunna använda samma tekniker och begrepp i nästan vilken spelutvecklingsmiljö som helst.

Förhandsgranska bilden som tas från den här klassiska Psdtuts + handledningen.


Steg 1: Skapa några bollar

Jag kommer att glansa över det här steget, för om du inte kan skapa grundläggande sprites kommer resten av handledningen att vara lite bortom dig.

Det är tillräckligt att säga, vi har en utgångspunkt. Nedan är en mycket rudimentär simulering av bollar studsande runt en skärm. Om de rör på skärmens kant, studsar de tillbaka.

Det är emellertid en viktig sak att notera: ofta, när du skapar sprites, är den övre vänstra delen inställd på ursprunget (0, 0) och längst ner till höger är (bredd höjd). Här har de cirklar vi skapat centreras på spritet.

Detta gör allt väsentligt enklare, eftersom om cirklarna inte är centrerade, för mer eller mindre varje beräkning måste vi kompensera det med radien, utföra beräkningen och sedan återställa den.

Du kan hitta koden upp till denna punkt i v1 mapp på källans nedladdning.


Steg 2: Kontrollera om överlappningar

Det första vi vill göra är att kontrollera om våra bollar är nära varandra. Det finns några sätt att göra detta, men för att vi vill vara bra små programmerare ska vi börja med en AABB-kontroll.

AABB står för axeljusterad avgränsningslåda och hänvisar till en rektangel ritad för att passa tätt runt ett objekt, inriktad så att dess sidor är parallella med axlarna.

En AABB-kollisionskontroll gör det inte kolla om cirklarna överlappar varandra, men det låter oss veta om de är nära varandra. Eftersom vårt spel bara använder fyra objekt, är det inte nödvändigt, men om vi körde en simulering med 10 000 objekt, så skulle den här lilla optimeringen spara oss många CPU-cykler.

Nu kör vi:

 om (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 

Det borde vara ganska enkelt: vi etablerar avgränsande lådor storleken på varje bolls diameter.

Här har en "kollision" inträffat - eller snarare överlappar de två AABB: erna, vilket betyder att cirklarna är nära varandra och potentiellt kolliderar.

När vi vet att bollarna är i närheten kan vi vara lite mer komplexa. Med hjälp av trigonometeri kan vi bestämma avståndet mellan de två punkterna:

 avstånd = Math.sqrt ((firstBall.x - secondBall.x) * (firstBall.x - secondBall.x)) + ((firstBall.y - secondBall.y) * (firstBall.y - secondBall.y))) ; om (avstånd < firstBall.radius + secondBall.radius)  //balls have collided 

Här använder vi Pythagoras teorem, a ^ 2 + b ^ 2 = c ^ 2, att räkna ut avståndet mellan de två cirkelcentren.

Vi känner inte omedelbart längden på en och b, men vi vet koordinaterna för varje boll, så det är trivialt att träna:

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

Vi löser sedan för c med lite algebraisk omläggning: c = Math.sqrt (a ^ 2 + b ^ 2) - därmed denna del av koden:

 avstånd = Math.sqrt ((firstBall.x - secondBall.x) * (firstBall.x - secondBall.x)) + ((firstBall.y - secondBall.y) * (firstBall.y - secondBall.y))) ;

Vi kontrollerar då detta värde mot summan av radierna i de två cirklarna:

 om (avstånd < firstBall.radius + secondBall.radius)  //balls have collided 

Varför kontrollerar vi mot cirklarnas kombinerade radier? Tja, om vi tittar på bilden nedan, så kan vi se det - oavsett vilken vinkel cirklarna berör - om de rör på raden då c är lika med r1 + r2.

Så om c är lika med eller mindre än r1 + r2, då måste cirklarna röra sig. Enkel!

Observera också att för att kunna beräkna kollisioner på rätt sätt kommer du sannolikt att flytta alla dina objekt först och sedan utföra kollisionsdetektering på dem. Annars kan du ha en situation där Ball1 uppdateringar, kontrollerar kollisioner, kolliderar då Ball2 uppdateringar, ligger inte längre i samma område som Ball1, och rapporterar ingen kollision. Eller, i kodvillkor:

 för (n = 0; n 

är mycket bättre än

 för (n = 0; n   

Du kan hitta koden upp till denna punkt i v2 mapp på källans nedladdning.


Steg 3: Beräkna kollisionspoäng

Denna del är inte riktigt nödvändig för bollkollisioner, men det är ganska coolt, så jag kastar in den. Om du bara vill få allt att studsa runt, var god att hoppa över till nästa steg.

Det kan ibland vara praktiskt att utreda den punkt på vilken två bollar har kolliderat. Om du till exempel vill lägga till en partikeleffekt (kanske en liten explosion), eller skapar du någon form av riktlinje för ett snooker-spel, då kan det vara bra att veta kollisionspunkten.

Det finns två sätt att utarbeta detta: rätt sätt och fel väg.

Fel väg, som många tutorials använder, är att medellå de två punkterna:

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

Detta fungerar, men bara om bollarna är lika stora.

Formeln vi vill använda är lite mer komplicerad, men fungerar för bollar av alla storlekar:

 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);

Detta använder ballans radier för att ge oss de sanna x- och y-koordinaterna för kollisionspunkten, representerad av den blå punkten i demo nedan.

Du kan hitta koden upp till denna punkt i v3 mapp på källans nedladdning.


Steg 4: Stoppar Apart

Nu vet vi när våra föremål kolliderar i varandra, och vi känner till deras hastighet och deras x och y platser. Hur tränar vi ut var de reser nästa??

Vi kan göra något som heter elastisk kollision.

Att försöka förklara i ord hur en elastisk kollision fungerar kan vara komplicerad - den följande animerade bilden ska göra saker tydligare.


Bild från http://en.wikipedia.org/wiki/Elastic_collision

Enkelt använder vi fler trianglar.

Nu kan vi träna i vilken riktning varje boll tar, men det kan finnas andra faktorer på jobbet. Spin, friktion, materialet som bollarna är gjorda av, massa och otaliga andra faktorer kan tillämpas för att försöka göra "perfekt" kollision. Vi kommer bara att oroa oss för en av dessa: massa.

I vårt exempel kommer vi att anta att radien hos bollarna vi använder är också deras massa. Om vi ​​strävat efter realism skulle det här vara felaktigt eftersom - eftersom bollarna var alla gjorda av samma material - skulle bollarnas massa vara proportionell mot antingen deras område eller volym beroende på huruvida du ville överväga dem eller ej skivor eller sfärer. Eftersom det här är ett enkelt spel är det dock tillräckligt att använda radierna.

Vi kan använda följande formel för att beräkna förändringen i x-hastigheten för den första bollen:

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

Så, låt oss gå över det här helt enkelt:

  • Antag att båda bollarna har samma massa (vi säger 10).
  • Den första bollen går på 5 enheter / uppdatering (till höger). Den andra bollen flyttar -1 enheter / uppdatering (till vänster).
 NewVelX = (5 * (10-10) + (2 * 10 * -1)) / (10 + 10) = (5 * 0) + (-20) / 20 = -20/20 = -1

I det här fallet, förutsatt att en kollision påbörjas, börjar den första bollen flytta vid -1 enhet / uppdatering. (Till vänster). Eftersom bollarnas massor är lika är kollisionen direkt och ingen energi har gått vilse, bollarna kommer att ha "handlade" hastigheter. Att ändra någon av dessa faktorer kommer givetvis att förändra resultatet.

(Om du använder samma beräkning för att räkna ut den andra bollens nya hastighet, kommer du att upptäcka att den rör sig vid 5 enheter / uppdatering, till höger).

Vi kan använda samma formel för att beräkna x / y-hastigheterna för båda bollarna efter kollisionen:

 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);

Förhoppningsvis är det uppenbart att varje beräkning är densamma, helt enkelt ersätter värdena i enlighet därmed.

När det här är gjort har vi de nya hastigheterna i varje boll. Så är vi färdiga? Inte riktigt.

Tidigare såg vi till att göra alla våra positionsuppdateringar samtidigt, och sedan kolla efter kollisioner. Det betyder att när vi kontrollerar kolliderande bollar är det mycket troligt att en boll kommer att vara "in" en annan - så när kollisionsdetektering heter, kommer både den första bollen och den andra bollen att registrera kollisionen, vilket innebär att våra föremål kan få fast tillsammans.

(Om bollarna går iväg, kommer den första kollisionen att vända sina riktningar - så de rör sig ifrån varandra - och den andra kollisionen kommer att vända sin riktning igen och leda till att de rör sig ihop).

Det finns flera sätt att hantera detta, till exempel genom att implementera en booleska som kontrollerar om bollarna redan har kolliderat den här ramen, men det enklaste sättet är helt enkelt att flytta varje boll med den nya hastigheten. Detta betyder i princip att bollarna ska röra sig ifrån varandra med samma hastighet som de rörde sig ihop - placera dem ett avstånd som är lika med ramen innan de kolliderade.

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

Och det är allt!

Du kan se den slutliga produkten här:

Och källkoden upp till denna del finns tillgänglig i v4 mapp på källans nedladdning.

Tack för att du läser! Om du vill lära dig mer om kollisionsdetekteringsmetoder på egen hand, kolla in den här sessionen. Du kanske också är intresserad av denna handledning om quadtrees och denna handledning om separationsaxeltestet.