Navigarea decentă a NPC necesită adesea capacitatea de a evita obstacolele. Acest tutorial acoperă evitarea coliziunii comportament de direcție, care permite caracterelor să evite în mod grațios orice număr de obstacole în mediul înconjurător.
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. Trebuie să aveți o înțelegere de bază a vectorilor de matematică.
Ideea de bază a evitării coliziunii este de a genera o forță de direcție pentru a evita obstacolele de fiecare dată când este suficient de aproape pentru a bloca trecerea. Chiar dacă mediul are mai multe obstacole, acest comportament va folosi unul câte unul pentru a calcula forța de evitare.
Se analizează doar obstacolele care se află înaintea caracterului; cea mai apropiată, despre care se spune că este cea mai amenințătoare, este selectată pentru evaluare. Drept urmare, personajul este capabil să evite toate obstacolele din zonă, trecând de la un la altul cu grație și fără probleme.
Comportamentul de evitare a coliziunii nu este un algoritm de depistare a traseului. Aceasta va face ca personajele să se miște prin mediul înconjurător, evitând obstacolele, găsind în cele din urmă un traseu pentru a trece prin blocuri - dar nu funcționează foarte bine cu obstacolele "L" sau "T", de exemplu.
Bacsis: Acest comportament de evitare a coliziunii poate suna similar comportamentului de fugă, dar există o diferență importantă între ele. Un personaj care se mișcă lângă un perete îl va evita numai dacă blochează drumul, dar comportamentul de fugă va împinge întotdeauna caracterul departe de perete.Primul pas pentru a evita obstacolele în mediul înconjurător este de a le percepe. Singurele obstacole pe care personajul trebuie să le îngrijoreze sunt cele care se află în fața acestuia și blocarea directă a rutei actuale.
Așa cum am explicat anterior, vectorul de viteză descrie direcția personajului. Acesta va fi folosit pentru a produce un nou vector numit înainte
, care este o copie a vectorului de viteză, dar cu o lungime diferită:
înainte
vector este linia de vedere a personajului. Acest vector se calculează după cum urmează:
înainte = poziția + normalizează (viteza) * MAX_SEE_AHEAD
înainte
vector lungime (ajustat cu MAX_SEE_AHEAD
) definește cât de departe caracterul va "vedea".
Cu cât e mai mare MAX_SEE_AHEAD
este, mai devreme, personajul va începe să acționeze pentru a evita un obstacol, deoarece îl va percepe ca o amenințare chiar dacă este departe:
Pentru a verifica coliziunea, fiecare obstacol (sau caseta delimitată) trebuie să fie descrisă ca o formă geometrică. Folosirea unei sfere (cerc în două dimensiuni) oferă cele mai bune rezultate, astfel încât fiecare obstacol din mediu va fi descris ca atare.
O posibilă soluție pentru a verifica coliziunea este intersecția liniei-sferă - linia este înainte
vector și sfera este obstacolul. Această abordare funcționează, dar voi folosi o simplificare a ceea ce este mai ușor de înțeles și care are rezultate similare (chiar și mai bune la timp).
înainte
vector va fi folosit pentru a produce un alt vector cu jumătate din lungimea sa:
ahead2
vectorul este calculat exact ca înainte
, dar lungimea lui este tăiată în jumătate:
înainte = poziția + normalizează (viteza) * MAX_SEE_AHEAD înainte2 = poziția + normalizează (viteza) * MAX_SEE_AHEAD * 0.5
Vrem să efectuăm o verificare a coliziunii pentru a testa dacă unul dintre acești doi vectori se află în interiorul sferei de obstacole. Acest lucru este ușor de realizat prin compararea distanței dintre sfârșitul vectorului și centrul sferei.
Dacă distanța este mai mică sau egală cu raza sferică, atunci vectorul se află în sferă și s-a găsit o coliziune:
Dacă fie din cele două vectori din față sunt în interiorul sferei de obstacole, atunci acest obstacol blochează calea. Se poate utiliza distanța euclidiană între două puncte:
(a: Obiect, b: Obiect): Număr retur Math.sqrt ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); linie de funcții privateIntersectsCircle (înainte: Vector3D, ahead2: Vector3D, obstacol: Circle): Boolean // proprietatea "centrul" obstacolului este Vector3D. distanța de întoarcere (obstacol. centru, înainte) <= obstacle.radius || distance(obstacle.center, ahead2) <= obstacle.radius;
Dacă mai mult de un obstacol blochează calea, atunci cea mai apropiată (cea mai "amenințătoare") este selectată pentru calcul:
Forța de evitare trebuie să împingă caracterul departe de obstacol, permițându-i să evite sfera. Se poate face folosind un vector format din centrul sferei (care este un vector de poziție) și înainte
vector. Calculam această forță de evitare după cum urmează:
avoidance_force = înainte - obstacle_center avoidance_force = normaliza (avoidance_force) * MAX_AVOID_FORCE
După avoidance_force
se calculează că este normalizată și scalată de MAX_AVOID_FORCE
, care este un număr utilizat pentru a defini avoidance_force
lungime. Cu cât e mai mare MAX_AVOID_FORCE
este, cu atât este mai puternică forța de evitare împingând caracterul departe de obstacol.
Implementarea finală pentru evitarea coliziunii()
care returnează forța de evitare, este:
funcția privată collisionAvoidance (): Vector3D ahead = ...; // se calculează vectorul următor forward2 = ...; // calcula vectorul forward2 var mostThreatening: Obstacle = findMostThreateningObstacle (); var evitarea: Vector3D = Vector3D nou (0, 0, 0); dacă (cea mai mareThreatening! = null) avoidance.x = ahead.x - mostThreatening.center.x; avoidance.y = ahead.y - mostThreatening.center.y; avoidance.normalize (); avoidance.scaleBy (MAX_AVOID_FORCE); altceva avoidance.scaleBy (0); // anulează forța de evitare evitarea returnării; funcția privată findMostThreateningObstacle (): Obstacle var mostThreatening: Obstacle = null; pentru (var i: int = 0; i < Game.instance.obstacles.length; i++) var obstacle :Obstacle = Game.instance.obstacles[i]; var collision :Boolean = lineIntersecsCircle(ahead, ahead2, obstacle); // "position" is the character's current position if (collision && (mostThreatening == null || distance(position, obstacle) < distance(position, mostThreatening))) mostThreatening = obstacle; return mostThreatening;
Forța de evitare trebuie adăugată la vectorul vitezei caracterului. După cum sa explicat anterior, toate forțele de direcție pot fi combinate într-una, producând o forță care reprezintă tot comportamentul activ care acționează asupra personajului.
În funcție de unghiul și direcția forței de evitare nu va întrerupe alte forțe de direcție, cum ar fi căutarea sau rătăcirea. Forța de evitare este adăugată la viteza jucătorului ca de obicei:
direcție = nimic (); // vectorul nul, adică "magnitudinea forței zero" direcție = direcție + căuta (); // presupunând că personajul caută ceva direcție = direcție + coliziuneAvoidance (); direcție = trunchi (direcție, max_force) direcție = direcție / viteză masă = trunchiat (viteză + direcție, max_speed) poziție = poziție + viteză
Deoarece toate comportamentele de direcție sunt recalculate la fiecare actualizare de joc, forța de evitare va rămâne activă atâta timp cât obstacolul blochează drumul.
Imediat ce obstacolul nu interceptează înainte
vector, forța de evitare va deveni nulă (fără efect) sau va fi recalculată pentru a evita noul obstacol amenințător. Rezultatul este un personaj care poate evita obstacolele:
Actuala implementare are două probleme, ambele legate de detectarea coliziunilor. Prima se întâmplă când înainte
vectorii se află în afara sferei de obstacole, dar caracterul este prea apropiat de (sau în interiorul) obstacolului.
Dacă se întâmplă acest lucru, personajul va atinge (sau va introduce) obstacolul, depășind procesul de evitare deoarece nu a fost detectată nicio coliziune:
înainte
vectori sunt în afara obstacolului, dar caracterul este în interior. Această problemă poate fi rezolvată prin adăugarea unui al treilea vector la verificarea coliziunii: vectorul de poziție al caracterului. Utilizarea a trei vectori îmbunătățește foarte mult detectarea coliziunilor.
Cea de-a doua problemă se întâmplă atunci când caracterul este aproape de obstacol, departe de el. Uneori manevra va provoca o coliziune, chiar dacă personajul se rotește doar pentru a se confrunta cu altă direcție:
Această problemă poate fi rezolvată prin scalarea înainte
vectori în funcție de viteza curentă a personajului. Codul pentru a calcula înainte
vector, de exemplu, este schimbat la:
dynamic_length = lungime (viteza) / MAX_VELOCITY înainte = poziție + normalizare (viteză) * dynamic_length
Variabila dynamic_length
va varia de la 0 la 1. Când caracterul se mișcă la viteză maximă, dynamic_length
este 1; când caracterul este încetinit sau accelerat, dynamic_length
este 0 sau mai mare (de exemplu, 0,5).
Ca o consecință, dacă personajul este doar manevră fără să se miște, dynamic_length
tinde la zero, producând o nulă înainte
vector, care nu are coliziuni.
Mai jos rezultatul cu aceste îmbunătățiri:
Pentru a demonstra comportamentul de evitare a coliziunii în acțiune, cred că o hoardă de zombi se potrivește perfect. Mai jos este o demonstrație care arată mai multe zombi (cu viteze diferite) care caută cursorul mouse-ului. Artă de către SpicyPixel și Clint Bellanger, de la OpenGameArt.
Comportamentul de evitare a coliziunii permite oricărui personaj să evite obstacolele din mediul înconjurător. Deoarece toate forțele de direcție sunt recalculate fiecare actualizare de joc, personajele interacționează fără probleme cu diferite obstacole, analizând întotdeauna cea mai amenințătoare (cea mai apropiată).
Chiar dacă acest comportament nu este un algoritm de găsire a traseului, rezultatele obținute sunt destul de convingătoare pentru hărțile aglomerate.