When Worlds Collide Simulating Circle-Circle Collisions

De meeste botsingsdetectie in computerspellen wordt gedaan met behulp van de AABB-techniek: heel eenvoudig, als twee rechthoeken elkaar snijden, is er een botsing opgetreden. Het is snel, efficiënt en ongelooflijk effectief - voor rechthoekige objecten. Maar wat als we samen cirkels wilden breken? Hoe berekenen we het botspunt en waar gaan de objecten naartoe? Het is niet zo moeilijk als je zou denken ...

Notitie: Hoewel deze tutorial geschreven is met behulp van AS3 en Flash, zou je in bijna elke game-ontwikkelomgeving dezelfde technieken en concepten moeten kunnen gebruiken.

Bekijk een voorbeeld van de klassieke Psdtuts + tutorial.


Stap 1: Maak enkele ballen

Ik ga deze stap verdoezelen, want als je geen basissprites kunt maken, zal de rest van de tutorial een beetje te ver gaan.

Het volstaat te zeggen dat we een beginpunt hebben. Hieronder is een zeer rudimentaire simulatie van ballen die rond een scherm kaatsen. Als ze de rand van het scherm raken, stuiteren ze terug.

Er is echter een belangrijk ding om op te merken: vaak, wanneer u sprites maakt, wordt het linkerbovengedeelte op de oorsprong ingesteld (0, 0) en rechtsonder is (breedte hoogte). Hier zijn de cirkels die we hebben gemaakt gecentreerd op de sprite.

Dit maakt alles aanzienlijk eenvoudiger, omdat als de cirkels niet gecentreerd zijn, we voor min of meer elke berekening deze moeten compenseren met de straal, de berekening uitvoeren en deze dan opnieuw instellen.

Je kunt de code tot op dit punt vinden in de v1 map van de brondownload.


Stap 2: Controleer op overlappingen

Het eerste dat we willen doen is controleren of onze ballen bij elkaar in de buurt zijn. Er zijn een paar manieren om dit te doen, maar omdat we goede kleine programmeurs willen zijn, beginnen we met een AABB-controle.

AABB staat voor as-uitgelijnd begrenzingsvak en verwijst naar een rechthoek die is getekend om strak om een ​​object te passen, uitgelijnd zodat de zijden evenwijdig zijn aan de assen.

Een AABB-botsingcontrole doet dat wel niet controleer of de cirkels elkaar overlappen, maar laat ons weten of ze dat wel zijn in de buurt elkaar. Omdat onze game maar vier objecten gebruikt, is dit niet nodig, maar als we een simulatie met 10.000 objecten zouden uitvoeren, zou een kleine optimalisatie ons veel CPU-cycli besparen.

Hier gaan we:

 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 

Dit zou redelijk eenvoudig moeten zijn: we stellen grensdozen vast met de grootte van de diameter van elke bal in het kwadraat.

Hier is een "botsing" opgetreden - of liever gezegd, de twee AABB's overlappen elkaar, wat betekent dat de cirkels dicht bij elkaar staan ​​en mogelijk in botsing komen.

Zodra we weten dat de ballen in de buurt zijn, kunnen we een beetje complexer zijn. Met behulp van trigonometery kunnen we de afstand tussen de twee punten bepalen:

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

Hier gebruiken we de stelling van Pythagoras, a ^ 2 + b ^ 2 = c ^ 2, om de afstand tussen de centra van de twee cirkels te bepalen.

We weten niet meteen de lengte van een en b, maar we kennen de coördinaten van elke bal, dus het is triviaal om uit te werken:

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

We lossen het vervolgens op c met een beetje algebraïsche herschikking: c = Math.sqrt (a ^ 2 + b ^ 2) - vandaar dit deel van de code:

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

We controleren deze waarde vervolgens op de som van de radii van de twee cirkels:

 als (afstand < firstBall.radius + secondBall.radius)  //balls have collided 

Waarom controleren we de gecombineerde radii van de cirkels? Welnu, als we naar de onderstaande afbeelding kijken, kunnen we zien dat - ongeacht in welke hoek de cirkels elkaar raken - als ze de lijn raken c is gelijk aan r1 + r2.

Dus indien c is gelijk aan of kleiner dan r1 + r2, dan moeten de cirkels elkaar raken. Eenvoudig!

Merk ook op dat u, om botsingen correct te berekenen, waarschijnlijk al uw objecten eerst wilt verplaatsen en vervolgens botsingsdetectie op hen wilt uitvoeren. Anders heb je misschien een situatie waarin ball1 updates, controles op botsingen, botsen, dan ball2 updates, bevindt zich niet meer in hetzelfde gebied als ball1, en meldt geen botsing. Of, in codetermen:

 voor (n = 0; n 

is veel beter dan

 voor (n = 0; n   

Je kunt de code tot op dit punt vinden in de v2 map van de brondownload.


Stap 3: bereken botsingspunten

Dit deel is niet echt nodig voor balbotsingen, maar het is best gaaf, dus ik gooi het erin. Als je gewoon alles wilt laten rondstuiteren, ga dan verder met de volgende stap.

Het kan soms handig zijn om het punt te berekenen waarop twee ballen zijn gebotst. Als je bijvoorbeeld een partikeleffect (misschien een kleine explosie) wilt toevoegen, of als je een soort richtlijn voor een snookerspel maakt, dan kan het handig zijn om het botspunt te kennen.

Er zijn twee manieren om dit uit te werken: op de juiste manier en op de verkeerde manier.

De verkeerde manier, die veel tutorials gebruiken, is om de twee punten te gemiddelde:

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

Dit werkt, maar alleen als de ballen even groot zijn.

De formule die we willen gebruiken is iets gecompliceerder, maar werkt voor ballen in alle maten:

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

Dit gebruikt de stralen van de ballen om ons de ware x- en y-coördinaten van het botspunt te geven, weergegeven door de blauwe stip in de onderstaande demo.

Je kunt de code tot op dit punt vinden in de v3 map van de brondownload.


Stap 4: Uit elkaar stuiteren

Nu weten we wanneer onze objecten in elkaar botsen en we hun snelheid en hun x- en y-locaties kennen. Hoe kunnen we bepalen waar ze vervolgens naartoe reizen??

We kunnen iets doen dat genoemd wordt Elastische botsing.

Proberen om in woorden uit te leggen hoe een elastische botsing werkt, kan ingewikkeld zijn - de volgende geanimeerde afbeelding moet dingen duidelijker maken.


Afbeelding van http://en.wikipedia.org/wiki/Elastic_collision

Gewoon, we gebruiken meer driehoeken.

Nu kunnen we de richting van elke bal bepalen, maar er kunnen andere factoren aan het werk zijn. Spin, wrijving, het materiaal waarvan de ballen zijn gemaakt, massa en talloze andere factoren kunnen worden toegepast om de "perfecte" botsing te maken. We maken ons alleen zorgen om een ​​van deze: massa.

In ons voorbeeld gaan we ervan uit dat de straal van de ballen die we gebruiken ook hun massa is. Als we naar realisme streefden, dan zou dit onnauwkeurig zijn omdat - aangenomen dat de ballen allemaal van hetzelfde materiaal waren gemaakt - de massa van de ballen evenredig zou zijn aan hun gebied of volume, afhankelijk van het feit of je ze al dan niet wilde overwegen schijven of bollen. Aangezien dit echter een eenvoudig spel is, volstaat het gebruik van hun radii.

We kunnen de volgende formule gebruiken om de verandering in x velocity van de eerste bal te berekenen:

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

Laten we dit eenvoudig bekijken:

  • Stel dat beide ballen dezelfde massa hebben (we zeggen 10).
  • De eerste bal beweegt met 5 eenheden / update (naar rechts). De tweede bal beweegt -1 eenheden / update (naar links).
 NewVelX = (5 * (10-10) + (2 * 10 * -1)) / (10 + 10) = (5 * 0) + (-20) / 20 = -20/20 = -1

In dit geval, uitgaande van een frontale botsing, begint de eerste bal te bewegen met -1 eenheid / update. (Naar links). Omdat de massa van de ballen gelijk is, is de botsing direct en is er geen energie verloren. De ballen zullen "verhandelde" snelheden hebben. Het veranderen van een van deze factoren zal de uitkomst uiteraard veranderen.

(Als je dezelfde berekening gebruikt om de nieuwe snelheid van het tweede balletje te bepalen, zul je zien dat het beweegt met 5 eenheden / update, naar rechts).

We kunnen dezelfde formule gebruiken om de x / y-snelheden van beide ballen na de botsing te berekenen:

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

Hopelijk is het duidelijk dat elke berekening hetzelfde is, simpelweg door de waarden dienovereenkomstig te vervangen.

Zodra dit is gebeurd, hebben we de nieuwe snelheden van elke bal. Zijn we klaar? Niet helemaal.

Eerder hebben we ervoor gezorgd dat al onze positieupdates tegelijk zijn uitgevoerd en dan controleer op botsingen. Dit betekent dat wanneer we controleren op botsende ballen, het zeer waarschijnlijk is dat de ene bal "in" een andere zal zijn - dus wanneer de botsingdetectie wordt aangeroepen, zullen zowel de eerste bal als de tweede bal die botsing registreren, wat betekent dat onze objecten mogelijk worden vast aan elkaar.

(Als de ballen samen op weg zijn, zal de eerste botsing hun richtingen omkeren - zodat ze uit elkaar gaan - en de tweede botsing zal hun richting weer omkeren, waardoor ze samen bewegen).

Er zijn verschillende manieren om hiermee om te gaan, zoals het implementeren van een Booleaanse die controleert of de ballen al op dit frame botsen, maar de gemakkelijkste manier is om elke bal met de nieuwe snelheid te verplaatsen. Dit betekent in principe dat de ballen uit elkaar moeten bewegen met dezelfde snelheid die ze samen bewogen - ze op een afstand van elkaar geplaatst gelijk aan het frame voordat ze botsten.

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

En dat is het!

Je kunt het uiteindelijke product hier bekijken:

En broncode tot aan dit deel is beschikbaar in de v4 map van de brondownload.

Bedankt voor het lezen! Als u meer wilt weten over botsingsdetectiemethoden op zichzelf, bekijkt u deze sessie. Mogelijk bent u ook geïnteresseerd in deze tutorial over quadles en deze tutorial over de Separating Axis Test.