Anstendig NPC-navigasjon krever ofte evnen til å unngå hindringer. Denne opplæringen dekker kollisjon unngås styringsadferd, som gjør at tegn kan grave seg unødvendig utallige hindringer i miljøet.
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ø. Du må ha en grunnleggende forståelse av matematiske vektorer.
Den grunnleggende ideen bak kollisjonssvikt er å skape styrestyrke for å unngå hindringer hver gang man er nær nok til å blokkere passasjen. Selv om miljøet har flere hindringer, vil denne oppførselen bruke en av dem om gangen til å beregne unngåttstyrken.
Bare hindringene foran karakteren blir analysert; Den nærmeste, som sies å være den mest truende, er valgt for evaluering. Som et resultat er karakteren i stand til å unnvike alle hindringer i området, overgang fra den ene til den andre grasiøst og sømløst.
Kollisjonen unngår atferd er ikke en vei-algoritme. Det vil få karakterer til å bevege seg gjennom miljøet, unngå hindringer, og etter hvert finne en rute for å gå gjennom blokkene - men det virker ikke veldig bra med "L" eller "T" hindringer, for eksempel.
Tips: Denne kollisjonen unngår atferd kan høres ut som flyteadferd, men det er en viktig forskjell mellom dem. Et tegn som beveger seg nær en vegg, vil unngå det bare hvis det blokkerer veien, men flykteferdene vil alltid skyve tegnet bort fra veggen.Det første skrittet for å unngå hindringer i miljøet er å oppleve dem. De eneste hindringene karakteren må bekymre seg er de som står foran den, og direkte blokkering av den nåværende ruten.
Som tidligere forklart, beskriver hastighetsvektoren retningen for tegnet. Det vil bli brukt til å produsere en ny vektor kalt fremover
, som er en kopi av hastighetsvektoren, men med en annen lengde:
fremover
vektor er tegnet siktlinje. Denne vektoren beregnes som følger:
fremover = posisjon + normaliser (hastighet) * MAX_SEE_AHEAD
De fremover
vektor lengde (justert med MAX_SEE_AHEAD
) definerer hvor langt karakteren vil "se".
Det bedre MAX_SEE_AHEAD
er jo jo tidligere karakteren begynner å fungere for å unnslippe et hinder, fordi det vil oppleve det som en trussel, selv om den er langt borte:
For å sjekke for kollisjon, må hver hindring (eller dens avgrensningsboks) beskrives som en geometrisk form. Bruke en sfære (sirkel i to dimensiner) gir de beste resultatene, så alle hindringer i miljøet vil bli beskrevet som sådan.
En mulig løsning for å sjekke for kollisjon er linjeskarens kryss - linjen er fremover
vektor og sfæren er hindringen. Denne tilnærmingen fungerer, men jeg skal bruke en forenkling av det som er lettere å forstå og har lignende resultater (enda bedre de til tider).
De fremover
vektor vil bli brukt til å produsere en annen vektor med halvparten av dens lengde:
De ahead2
vektoren er beregnet akkurat som fremover
, men lengden er kuttet i halvdel:
fremover = posisjon + normaliser (hastighet) * MAX_SEE_AHEAD ahead2 = posisjon + normaliser (hastighet) * MAX_SEE_AHEAD * 0,5
Vi ønsker å utføre en kollisjonskontroll for å teste om en av de to vektorer er innenfor hindringssfæren. Det er lett å oppnå ved å sammenligne avstanden mellom vektorens ende og sfærens senter.
Hvis avstanden er mindre enn eller lik kuleradien, er vektoren inne i sfæren, og en kollisjon ble funnet:
Hvis enten av de to fremadrettede vektorer er inne i hindringssfæren, så hindrer denne hindringen veien. Den euklidiske avstanden mellom to punkter kan brukes:
privat funksjonsavstand (a: Objekt, b: Objekt): Nummer retur Math.sqrt ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); privat funksjon linjeIntersectsCircle (fremover: Vector3D, ahead2: Vector3D, hindring: Circle): Boolsk // eiendommen "sentrum" av hindringen er en Vector3D. returavstand (obstacle.center, fremover) <= obstacle.radius || distance(obstacle.center, ahead2) <= obstacle.radius;
Hvis flere hindringer blokkerer veien, blir den nærmeste ("mest truende") valgt for beregning:
Undvikningskraften må skyve karakteren bort fra hindringen, slik at den kan unnslippe sfæren. Det kan gjøres ved hjelp av en vektor dannet ved å bruke senterets sfære (som er en posisjonsvektor) og fremover
vektor. Vi beregner denne unntakskraften som følger:
avoidance_force = ahead - obstacle_center avoidance_force = normalisere (unngåelse) * MAX_AVOID_FORCE
Etter avoidance_force
Beregnes det er normalisert og skalert av MAX_AVOID_FORCE
, som er et tall som brukes til å definere avoidance_force
lengde. Det bedre MAX_AVOID_FORCE
jo sterkere er unnvikende kraft som skyver tegnet bort fra hindringen.
Den endelige gjennomføringen for collisionAvoidance ()
metode, som returnerer unntakskraften, er:
privat funksjon kollisjonAvoidance (): Vector3D ahead = ...; // beregne fremadrettet vektor ahead2 = ...; // beregne fremover2 vektoren var mestEndring: Hindring = finneMostThreateningObstacle (); var unngåelse: Vector3D = ny Vector3D (0, 0, 0); hvis (mostThreatening! = null) avoidance.x = ahead.x - mostThreatening.center.x; avoidance.y = ahead.y - mostThreatening.center.y; avoidance.normalize (); avoidance.scaleBy (MAX_AVOID_FORCE); else avoidance.scaleBy (0); // nullify the avoidance force retur unngåelse; privat funksjon findMostThreateningObstacle (): Hindring var mostThreatening: Hindring = null; for (var jeg: 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;
Unngåelseskraften må legges til tegnets hastighetsvektor. Som tidligere forklart, kan alle styringskrefter kombineres til en, som produserer en kraft som representerer all aktiv oppførsel som virker på tegnet.
Avhengig av vekkevirkningsvinkelen og -retningen vil den ikke forstyrre andre styringskrefter, for eksempel å søke eller vandre. Unngåelseskraften legges til spillerhastigheten som vanlig:
styring = ingenting (); // null-vektoren, som betyr "nullstyrke" styring = styring + søk (); // forutsatt at tegnet søker noe styring = styring + kollisjonAvoidance (); styring = trunkering (styring, maks_force) styring = styring / massehastighet = trunkering (hastighet + styring, maks_speed) posisjon = posisjon + hastighet
Siden alle styreadferdene blir beregnet på nytt hver spilloppdatering, vil unngåttstyrken forbli aktiv så lenge hindringen blokkerer veien.
Så snart hindringen ikke fanger opp fremover
vektorlinje, vil unngåningskraften bli null (ingen effekt), eller det vil bli beregnet på nytt for å unngå den nye truende hindringen. Resultatet er et tegn som kan unngå hindringer:
Den nåværende implementeringen har to problemer, både relatert til kollisjonsdeteksjonen. Den første skjer når fremover
vektorer er utenfor hindringssfæren, men tegnet er for nær (eller inne) hindringen.
Hvis det skjer, vil tegnet berøre (eller angi) hinderet, hoppe over unngåringsprosessen fordi det ikke oppdaget kollisjoner:
fremover
vektorer er utenfor hindringen, men tegnet er inne. Dette problemet kan løses ved å legge til en tredje vektor til kollisjonskontrollen: tegnets posisjonvektor. Bruken av tre vektorer forbedrer kollisjonsdeteksjonen sterkt.
Det andre problemet skjer når karakteren er nær hindringen, styrer bort fra den. Noen ganger vil manøvrering forårsake kollisjon, selv om tegnet bare roterer for å møte en annen retning:
Det problemet kan løses ved å skalere fremover
vektorer i henhold til tegnets nåværende hastighet. Koden for å beregne fremover
vektor, for eksempel, endres til:
dynamic_length = lengde (hastighet) / MAX_VELOCITY foran = posisjon + normalisere (hastighet) * dynamic_length
Variabelen dynamic_length
vil variere fra 0 til 1. Når tegnet beveger seg i full fart, dynamic_length
er 1; når tegnet senker eller akselererer, dynamic_length
er 0 eller større (f.eks. 0,5).
Som en konsekvens, hvis tegnet bare manøvrerer uten å bevege seg, dynamic_length
har en tendens til null, noe som gir null fremover
vektor, som ikke har kollisjoner.
Nedenfor er resultatet med disse forbedringene:
For å demonstrere kollisjonen unngår atferd i aksjon, tror jeg en horde av zombier er den perfekte passformen. Nedenfor er en demonstrasjon som viser flere zombier (med forskjellige hastigheter) som søker musemarkøren. Kunst av SpicyPixel og Clint Bellanger, fra OpenGameArt.
Kollisjonen unngår atferd gjør det mulig å unnslippe hindringer i miljøet. Siden alle styringskreftene blir beregnet på nytt hver spilloppdatering, påvirker tegnene sømløst med forskjellige hindringer, og analyserer alltid den mest truende (den nærmeste).
Selv om denne oppførselen ikke er en vei-algoritme, er de oppnådde resultatene ganske overbevisende for overfylte kart.