Forstå styringsadferd Kollisjonssvikt

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.


Introduksjon

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.


Hindringer foran karakteren analyseres og det nærmeste (mest truende) er valgt.

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.

Ser fremover

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:


De 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:


Jo større fremtiden er, desto tidligere begynner karakteren å fungere for å unnslippe et hinder.

Sjekker for kollisjon

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:


Samme retning, halv 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:


Den fremadrettede vektoren avskjærer hindringen dersom d < r. The ahead2 vector was omitted for clarity.

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:


Det nærmeste hinderet (mest truende) er valgt for beregning.

Beregning av Unngå kraften

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.


Unngå kraftkalkulering. Den stiplede oransje linjen viser stien karakteren vil ta for å unngå hindringen. Tips: Posisjonen til en hvilken som helst enhet kan beskrives som en vektor, slik at de kan brukes i beregninger med andre vektorer og krefter.

Unngå 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:


Flytt musepekeren. Klikk for å vise styrker.

Forbedre kollisjonsdeteksjon

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:


Noen ganger 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:


Manøvrering kan føre til kollisjon, selv om tegnet bare roterer.

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:


Flytt musepekeren. Klikk for å vise styrker.

Demo: Det er Zombie Time!

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.


Flytt musepekeren. Klikk for å vise styrker.

Konklusjon

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.