Når Worlds Collide Simulerer Circle-Circle Collisions

Mest kollisjonsdeteksjon i dataspill er gjort ved hjelp av AABB-teknikken: ganske enkelt, hvis to rektangler krysser, så har det skjedd en kollisjon. Det er raskt, effektivt og utrolig effektivt - for rektangulære gjenstander. Men hva om vi ønsket å knuse sirkler sammen? Hvordan beregner vi kollisjonspunktet, og hvor går objektene etter? Det er ikke så vanskelig som du kanskje tror ...

Merk: Selv om denne opplæringen er skrevet med AS3 og Flash, bør du kunne bruke de samme teknikkene og konseptene i nesten hvilket som helst spillutviklingsmiljø.

Forhåndsvis bilde tatt fra denne klassiske Psdtuts + opplæringen.


Trinn 1: Opprett noen baller

Jeg kommer til å glosse over dette trinnet, fordi hvis du ikke kan lage grunnleggende sprites, vil resten av opplæringen være litt over deg.

Tilstrekkelig å si, vi har utgangspunkt. Nedenfor er en veldig rudimentær simulering av baller som hopper rundt en skjerm. Hvis de berører kanten av skjermen, spretter de tilbake.

Det er imidlertid en viktig ting å merke seg: ofte, når du lager sprites, er den øverste venstre delen satt til opprinnelsen (0, 0) og nederst til høyre er (bredde høyde). Her sirkler vi har opprettet er sentrert på sprite.

Dette gjør alt betydelig enklere, siden om kretsene ikke er sentrert, for mer eller mindre hver beregning må vi kompensere den av radius, utfør beregningen, og tilbakestill den deretter tilbake.

Du kan finne koden opp til dette punktet i v1 mappe av kilde nedlasting.


Trinn 2: Sjekk om overlapper

Det første vi vil gjøre er å sjekke om våre baller er i nærheten av hverandre. Det er noen måter å gjøre dette på, men fordi vi vil være gode, små programmerere, skal vi starte med en AABB-sjekk.

AABB står for aksejustert begrensingsboks, og refererer til et rektangel tegnet for å passe tett rundt et objekt, justert slik at sidene er parallelle med aksene.

En AABB-kollisjonskontroll gjør det ikke sjekk om sirklerne overlapper hverandre, men det gir oss beskjed om de er nær hverandre. Siden vårt spill bare bruker fire objekter, er dette ikke nødvendig, men hvis vi kjørte en simulering med 10.000 objekter, ville da denne lille optimaliseringen spare oss mange CPU-sykluser.

Så her går vi:

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

Dette burde være ganske greit: vi etablerer avgrensende bokser størrelsen på hver ballens diameter kvadratet.

Her har det oppstått en "kollisjon" - eller de to AABBene overlapper hverandre, noe som betyr at sirklene er nær hverandre og potensielt kolliderer.

Når vi vet at ballene er i nærheten, kan vi være litt mer komplekse. Ved hjelp av trigonometeri kan vi avgjøre avstanden mellom de to punktene:

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

Her bruker vi Pythagoras teorem, a ^ 2 + b ^ 2 = c ^ 2, å finne ut avstanden mellom de to sirkelsentrene.

Vi kjenner ikke umiddelbart lengden på en og b, men vi kjenner koordinatene til hver ball, så det er trivielt å trene:

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

Vi løser da for c med litt algebraisk omlegging: c = Math.sqrt (a ^ 2 + b ^ 2) - dermed denne delen av koden:

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

Vi sjekker deretter denne verdien mot summen av radien til de to sirkler:

 hvis (avstand < firstBall.radius + secondBall.radius)  //balls have collided 

Hvorfor sjekker vi mot kretsens kombinerte radier? Vel, hvis vi ser på bildet nedenfor, så kan vi se det - uansett i hvilken vinkel sirklene berører - hvis de berører linjen da c er lik r1 + r2.

Så hvis c er lik eller mindre enn r1 + r2, så må sirklene røre. Enkel!

Vær også oppmerksom på at, for å kunne beregne kollisjoner på riktig måte, vil du sannsynligvis ønske å flytte alle objektene dine først, og deretter utføre kollisjonsdeteksjon på dem. Ellers kan du ha en situasjon der Ball1 oppdateringer, sjekker for kollisjoner, kolliderer, da Ball2 oppdateringer, er ikke lenger i samme område som Ball1, og rapporterer ingen kollisjon. Eller, med kodenavn:

 for (n = 0; n 

er mye bedre enn

 for (n = 0; n   

Du kan finne koden opp til dette punktet i v2 mappe av kilde nedlasting.


Trinn 3: Beregn kollisjonspunkter

Denne delen er egentlig ikke nødvendig for kollisjoner, men det er ganske kult, så jeg kaster det inn. Hvis du bare vil få alt til å hoppe rundt, kan du ikke hoppe over til neste trinn.

Det kan noen ganger være nyttig å trene det punktet hvor to baller har kollidert. Hvis du for eksempel vil legge til en partikkel-effekt (kanskje en liten eksplosjon), eller du lager en slags retningslinje for et snooker-spill, så kan det være nyttig å kjenne kollisjonspunktet.

Det er to måter å utarbeide dette ut: den riktige måten, og feil vei.

Feil måte, som mange opplæringsprogrammer bruker, er å gjennomsnittlig de to poengene:

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

Dette fungerer, men bare hvis ballene har samme størrelse.

Formelen vi vil bruke er litt mer komplisert, men fungerer for baller i alle størrelser:

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

Dette bruker radiusene til ballene for å gi oss de ekte x- og y-koordinatene for kollisjonspunktet, representert ved den blå prikken i demoen under.

Du kan finne koden opp til dette punktet i v3 mappe av kilde nedlasting.


Trinn 4: Spretter Apart

Nå vet vi når våre objekter kolliderer til hverandre, og vi kjenner deres hastighet og deres x og y steder. Hvordan trener vi ut hvor de reiser neste gang??

Vi kan gjøre noe som heter elastisk kollisjon.

Forsøk å forklare i ord hvordan en elastisk kollisjon fungerer, kan være komplisert - det følgende animerte bildet skal gjøre tingene klarere.


Bilde fra http://en.wikipedia.org/wiki/Elastic_collision

Vi bruker bare flere trekanter.

Nå kan vi trene den retningen hver ball tar, men det kan være andre faktorer på jobben. Spinn, friksjon, materialet ballene er laget av, masse og utallige andre faktorer kan brukes for å forsøke å gjøre den "perfekte" kollisjonen. Vi skal bare bekymre oss om en av disse: masse.

I vårt eksempel skal vi anta at radiusen til ballene vi bruker er også deres masse. Hvis vi streber etter realisme, ville dette være unøyaktig siden - antatt at ballene var alle laget av det samme materialet - ballens masse ville være proporsjonal med enten deres område eller volum, avhengig av om du ønsket å vurdere dem eller ikke plater eller sfærer. Men siden dette er et enkelt spill, vil det være tilstrekkelig å bruke radiene.

Vi kan bruke følgende formel til å beregne endringen i x-hastigheten til den første ballen:

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

Så, la oss gå over dette bare:

  • Anta at begge ballene har samme masse (vi sier 10).
  • Den første ballen beveger seg på 5 enheter / oppdatering (til høyre). Den andre ballen beveger seg -1 enheter / oppdatering (til venstre).
 NewVelX = (5 * (10-10) + (2 * 10 * -1)) / (10 + 10) = (5 * 0) + (-20) / 20 = -20/20 = -1

I dette tilfellet, forutsatt en head-on kollisjon, vil den første ballen begynne å bevege seg på -1 enhet / oppdatering. (Til venstre). Fordi ballengassene er like, er kollisjonen direkte og ingen energi har gått tapt, de vil ha "handlet" hastigheter. Endring av noen av disse faktorene vil selvsagt endre utfallet.

(Hvis du bruker samme beregning for å finne ut den andre ballens nye hastighet, vil du oppdage at den beveger seg til 5 enheter / oppdatering, til høyre).

Vi kan bruke denne samme formel til å beregne x / y-hastighetene for begge ballene etter kollisjonen:

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

Forhåpentligvis er det tydelig at hver beregning er den samme, bare å erstatte verdiene tilsvarende.

Når dette er gjort, har vi de nye hastighetene til hver ball. Så er vi ferdige? Ikke helt.

Tidligere har vi sørget for å gjøre alle våre stillingsoppdateringer samtidig, og deretter sjekk for kollisjoner. Dette betyr at når vi sjekker om kolliderende baller, er det sannsynlig at en ball vil være "i" en annen - så når kollisjonsdeteksjonen blir kalt, registrerer både den første ballen og den andre ballen kollisjonen, noe som betyr at objektene våre kan få sitter fast sammen.

(Hvis ballene går sammen, vil den første kollisjonen reversere retningene deres - slik at de beveger seg fra hverandre - og den andre kollisjonen vil reversere retningen igjen og få dem til å bevege seg sammen).

Det er flere måter å håndtere dette, for eksempel å implementere en boolsk som kontrollerer om ballene allerede har kollidert denne rammen, men den enkleste måten er bare å flytte hver ball med den nye hastigheten. Dette betyr i prinsippet at ballene skal bevege seg fra hverandre med samme hastighet som de beveget seg sammen - plassere dem en avstand som er like som rammen før de kolliderte.

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

Og det er det!

Du kan se det endelige produktet her:

Og kildekoden opp til denne delen er tilgjengelig i v4 mappe av kilde nedlasting.

Takk for at du leste! Hvis du vil lære mer om kollisjonsdeteksjonsmetoder på egenhånd, sjekk ut denne sesjonen. Du kan også være interessert i denne opplæringen om quadtrees og denne opplæringen om Separating Axis Test.