Când lumea se ciocnește simularea coliziunilor cerc-cerc

Majoritatea detectării coliziunilor în jocurile pe calculator se face folosind tehnica AABB: foarte simplu, dacă se intersectează două dreptunghiuri, atunci s-a produs o coliziune. Este rapid, eficient și incredibil de eficient - pentru obiecte rectangulare. Dar dacă am vrea să împușcăm cercuri împreună? Cum calculam punctul de coliziune și unde se îndreaptă obiectele? Nu este așa de greu de crezut ...

Notă: Deși acest tutorial este scris folosind AS3 și Flash, ar trebui să puteți utiliza aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului.

Exportați imaginea preluată din acest tutorial clasic Psdtuts +.


Pasul 1: Creați niște bile

Am să glisez peste acest pas, pentru că dacă nu poți crea sprite de bază, restul tutorialului va fi puțin dincolo de tine.

Este suficient să spunem că avem un punct de plecare. Mai jos este o simulare foarte rudimentară a bilelor care strălucesc în jurul unui ecran. Dacă atinge marginea ecranului, ei se întorc înapoi.

Există, totuși, un lucru important de observat: adesea, atunci când creați sprites, partea stângă sus este setată la origine (0, 0) iar partea din dreapta jos este (latime inaltime). Aici, cercurile pe care le-am creat sunt centrate pe sprite.

Acest lucru face totul mult mai ușor, deoarece dacă cercurile nu sunt centrate, pentru mai mult sau mai puțin la fiecare calcul trebuie să o compensăm de rază, să efectuăm calculul, apoi să-l resetăm.

Puteți găsi codul până la acest punct în v1 folderul sursei de descărcare.


Pasul 2: Verificați suprapunerile

Primul lucru pe care vrem să-l facem este să verificăm dacă bilele noastre sunt aproape una de cealaltă. Există câteva moduri de a face acest lucru, dar pentru că vrem să fim mici programatori, vom începe cu o verificare AABB.

AABB reprezintă o casetă de legare aliniată pe axă și se referă la un dreptunghi desenat să se potrivească strâns în jurul unui obiect, aliniat astfel încât laturile sale să fie paralele cu axele.

O verificare de coliziune AABB face nu verificați dacă cercurile se suprapun între ele, dar ne informează dacă sunt lângă fiecare. Deoarece jocul nostru folosește doar patru obiecte, acest lucru nu este necesar, dar dacă am fi rulat o simulare cu 10.000 de obiecte, atunci această optimizare mică ne-ar salva multe cicluri de CPU.

Deci, aici mergem:

 dacă (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 

Aceasta ar trebui să fie destul de simplă: stabiliți casete de margine cu dimensiunea fiecărui bilă diametru pătrat.

Aici a apărut o "coliziune" - sau, mai degrabă, cele două AABB-uri se suprapun, ceea ce înseamnă că cercurile sunt aproape unul de altul și potențial se ciocnesc.

Odată ce știm că bilele sunt în proximitate, putem fi un pic mai complexe. Folosind trigonometru, putem determina distanța dintre cele două puncte:

 distanta = Math.sqrt ((firstBall.x - secondBall.x) * (firstBall.x - secondBall.x)) + ((firstBall.y - secondBall.y) ; dacă (distanța < firstBall.radius + secondBall.radius)  //balls have collided 

Aici, folosim teorema lui Pythagoras, a ^ 2 + b ^ 2 = c ^ 2, pentru a afla distanța dintre centrele celor două cercuri.

Nu cunoaștem imediat lungimea lui A și b, dar știm coordonatele fiecărei mingi, așa că e puțin banal să afli:

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

Am rezolvat apoi pentru c cu o rearanjare algebrică: c = Math.sqrt (a ^ 2 + b ^ 2) - prin urmare, această parte a codului:

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

Apoi verificăm această valoare față de suma razei celor două cercuri:

 dacă (distanța < firstBall.radius + secondBall.radius)  //balls have collided 

De ce verificăm razele combinate ale cercurilor? Ei bine, dacă ne uităm la imaginea de mai jos, putem vedea că - indiferent la ce unghi se ating cercurile - dacă ating o linie atunci c este egal cu r1 + r2.

Astfel, dacă c este egală cu sau mai mică decât r1 + r2, atunci cercurile trebuie să atingă. Simplu!

Rețineți, de asemenea, că, pentru a calcula corect coliziunile, probabil că veți dori să mutați mai întâi toate obiectele și apoi să efectuați detectarea coliziunilor pe ele. În caz contrar, este posibil să aveți o situație în care Ball1 actualizări, verificări pentru coliziuni, ciocniri, apoi Ball2 actualizări, nu mai este în aceeași zonă ca Ball1, și nu raportează nici o coliziune. Sau, în termeni de cod:

 pentru (n = 0; n 

este mult mai bună decât

 pentru (n = 0; n   

Puteți găsi codul până la acest punct în v2 folderul sursei de descărcare.


Pasul 3: Calculați punctele de coliziune

Această parte nu este cu adevărat necesară pentru coliziuni cu bile, dar e destul de răcoritoare, așa că o arunc înăuntru. Dacă doriți doar să obțineți tot ce se învârte în jur, nu ezitați să treceți la pasul următor.

Poate uneori să fie la îndemână să descopere punctul în care s-au ciocnit două mingi. Dacă doriți, de exemplu, să adăugați un efect de particule (poate o mică explozie) sau dacă creați un fel de îndrumare pentru un joc de snooker, atunci poate fi util să cunoașteți punctul de coliziune.

Există două modalități de a rezolva această problemă: calea cea bună și calea greșită.

Modul greșit, pe care îl folosesc multe tutoriale, este de a obține în medie două puncte:

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

Acest lucru funcționează, dar numai dacă bilele au aceeași dimensiune.

Formula pe care dorim să o folosim este puțin mai complicată, dar funcționează pentru bile de toate dimensiunile:

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

Aceasta folosește razele bilelor pentru a ne da coordonatele adevărate x și y ale punctului de coliziune reprezentat de punctul albastru din demo-ul de mai jos.

Puteți găsi codul până la acest punct în v3 folderul sursei de descărcare.


Pasul 4: Bouncing Apart

Acum, știm când obiectele noastre se ciocnesc unul în celălalt și îi cunoaștem viteza și locațiile lor x și y. Cum ne descurcăm în cazul în care călătoresc în continuare?

Putem face ceva numit coliziune elastică.

Încercarea de a explica în cuvinte cum funcționează o coliziune elastică poate fi complicată - următoarea imagine animată ar trebui să facă lucrurile mai clare.


Imagine de la http://ro.wikipedia.org/wiki/Elastic_collision

Pur și simplu, folosim mai multe triunghiuri.

Acum, putem trasa direcția pe care o ia fiecare minge, dar pot exista alți factori la locul de muncă. Spinarea, frecare, materialul din care sunt făcute bilele, masa și nenumărați alți factori pot fi aplicați încercând să facă coliziunea "perfectă". O să ne îngrijorăm numai una dintre ele: în masă.

În exemplul nostru, vom presupune că raza bilelor pe care le folosim este și masa lor. Dacă ne străduim să realizăm realism, atunci ar fi inexactă, deoarece - presupunând că toate bilele erau făcute din același material - masa bilelor ar fi proporțională fie cu suprafața, fie cu volumul lor, în funcție de faptul dacă doriți sau nu să le considerați discuri sau sfere. Cu toate acestea, deoarece acesta este un joc simplu, folosirea razei lor va fi suficientă.

Putem folosi următoarea formulă pentru a calcula modificarea vitezei x a primei mingi:

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

Deci, să trecem peste acest lucru pur și simplu:

  • Să presupunem că ambele mingi au aceeași masă (vom spune 10).
  • Prima minge se deplasează la 5 unități / actualizare (la dreapta). A doua minge se mișcă cu 1 unitate / actualizare (spre stânga).
 NewVelX = (5 * (10-10) + (2 * 10 * -1)) / (10 + 10) = (5x0) + (-20) / 20 = -20/20 = -1

În acest caz, presupunând o coliziune de cap, prima minge va începe să se deplaseze la -1 unitate / actualizare. (La stanga). Deoarece masele bilelor sunt egale, coliziunea este directă și nu sa pierdut nici o energie, bilele vor avea viteze "tranzacționate". Schimbarea oricăror dintre acești factori va schimba în mod evident rezultatul.

(Dacă utilizați același calcul pentru a afla noua viteză a celei de-a doua minge, veți descoperi că se mișcă la 5 unități / actualizare, spre dreapta).

Putem folosi aceeași formulă pentru a calcula viteza x / y a ambelor bile după coliziune:

 newVelX1 = (firstBall.speed.x * (primaBall.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 * primaBall.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);

Sperăm că este evident că fiecare calcul este același, înlocuind pur și simplu valorile în consecință.

Odată ce acest lucru se face, avem noile viteze ale fiecărei mingi. Deci am terminat? Nu chiar.

Anterior, ne-am asigurat că vom face toate actualizările noastre de poziție simultan și atunci verificați pentru coliziuni. Asta inseamna ca atunci cand verificam bilele de coliziune, este foarte probabil ca o minge sa fie "in" alta - asa ca atunci cand este apelata detectia coliziunii, atat prima minge, cat si a doua minge vor inregistra acea coliziune, ceea ce inseamna ca obiectele noastre pot ajunge blocati impreuna.

(În cazul în care bilele se îndreaptă împreună, prima ciocnire va inversa direcțiile - astfel încât se vor deplasa în afară - iar cea de-a doua ciocnire va inversa direcția din nou, determinându-le să se mute împreună).

Există mai multe modalități de abordare a acestui lucru, cum ar fi implementarea unui boolean care verifică dacă bilele s-au ciocnit deja de acest cadru, dar cel mai simplu mod este acela de a muta fiecare minge cu noua viteză. Aceasta inseamna, in principiu, ca bilele trebuie sa se deplaseze cu aceeasi viteza pe care au mutat-o, plasandu-le la o distanta egala de cadru, inainte de a se ciocni.

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

Si asta e!

Puteți vedea produsul final aici:

Și codul sursă până la această parte este disponibil în v4 folderul sursei de descărcare.

Vă mulțumim pentru lectură! Dacă doriți să aflați mai multe despre metodele de detectare a coliziunilor pe cont propriu, consultați această sesiune. Ați putea fi, de asemenea, interesat de acest tutorial despre quadtrees și acest tutorial despre testul de separare a axelor.