Förstå styrningsbeteenden Kollisionsundvikande

Anständig NPC-navigering kräver ofta förmåga att undvika hinder. Denna handledning täcker kollisionsundvikande styrning beteende, vilket gör att tecken graciöst undviker några hinder i miljön.

Notera: Även om denna handledning skrivs med AS3 och Flash, borde du kunna använda samma tekniker och begrepp i nästan vilken spelutvecklingsmiljö som helst. Du måste ha en grundläggande förståelse för matematiska vektorer.


Introduktion

Grundidén bakom kollisionsundvikande är att skapa en styrkraft för att undvika hinder varje gång man är tillräckligt nära för att blockera passagen. Även om miljön har flera hinder, kommer detta beteende att använda en av dem i taget för att beräkna undvikningskraften.

Endast hindren framför karaktären analyseras; Den närmaste, som sägs vara den mest hotande, är vald för utvärdering. Som ett resultat kan karaktären undvika alla hinder i området, övergången från en till en annan graciöst och sömlöst.


Hindringar framför karaktären analyseras och den närmaste (mest hotande) är vald.

Kollisionsundviklingsbeteendet är inte en sökfelsalgoritm. Det kommer att göra tecken rör sig genom miljön, undvika hinder, så småningom hitta en väg att gå igenom blocken - men det fungerar inte riktigt bra med "L" eller "T" hinder, till exempel.

Tips: Detta kollisionsundvikande beteende kan låta likna flygningsbeteendet, men det finns en viktig skillnad mellan dem. Ett tecken som rör sig nära en vägg kommer att undvika det bara om det blockerar vägen, men flyktsättet kommer alltid att trycka tecknet bort från väggen.

Ser framåt

Det första steget att undvika hinder i miljön är att uppfatta dem. De enda hinder som karaktären måste oroa är de som står framför den och direkt blockerar den nuvarande vägen.

Som tidigare förklarat beskriver hastighetsvektorn karaktärens riktning. Det kommer att användas för att producera en ny vektor som heter ett huvud, vilken är en kopia av hastighetsvektorn men med en annan längd:


De ett huvud vektorn är karaktärens synfält.

Denna vektor beräknas enligt följande:

 framåt = position + normalisera (hastighet) * MAX_SEE_AHEAD

De ett huvud vektorlängd (justerad med MAX_SEE_AHEAD) definierar hur långt tecknet kommer att "se".

Ju större MAX_SEE_AHEAD ju desto tidigare börjar karaktären att agera för att undvika ett hinder, för att det uppfattar det som ett hot även om det är långt ifrån:


Ju större framåt längden är, ju tidigare karaktären börjar börja fungera för att undvika ett hinder.

Kollar efter kollision

För att kontrollera efter kollision måste varje hinder (eller dess avgränsningsbox) beskrivas som en geometrisk form. Att använda en sfär (cirkel i två dimensioner) ger de bästa resultaten, så alla hinder i miljön kommer att beskrivas som sådana.

En möjlig lösning för att kontrollera om kollisionen är linjär korsning - linjen är ett huvud vektor och sfären är hindret. Det här tillvägagångssättet fungerar, men jag kommer att använda en förenkling av det som är lättare att förstå och har liknande resultat (ännu bättre ibland).

De ett huvud vektor kommer att användas för att producera en annan vektor med hälften av dess längd:


Samma riktning, halva längden.

De ahead2 vektorn beräknas exakt som ett huvud, men dess längd är skuren i halva:

 framåt = position + normalisera (hastighet) * MAX_SEE_AHEAD ahead2 = position + normalisera (hastighet) * MAX_SEE_AHEAD * 0,5

Vi vill utföra en kollisionskontroll för att testa om någon av de två vektorerna är inuti hinderzonen. Det uppnås lätt genom att jämföra avståndet mellan vektorns ände och sfärens centrum.

Om avståndet är mindre än eller lika med sfärraden är vektorn inuti sfären och en kollision hittades:


Framåtvektorn avlyssnar hindret om d < r. The ahead2 vector was omitted for clarity.

Om antingen av de två framåtriktade vektorerna är inuti hinder sfären, då hindrar detta hinder vägen. Det euklidiska avståndet mellan två punkter kan användas:

 privata funktionsavstånd (a: Objekt, b: Objekt): Nummer returnera Math.sqrt ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));  privat funktionslinjeIntersectsCircle (framåt: Vector3D, ahead2: Vector3D, hinder: Circle): Booleska // egenskapen "center" av hinderet är en Vector3D. returavstånd (hinder.center, framåt) <= obstacle.radius || distance(obstacle.center, ahead2) <= obstacle.radius; 

Om mer än ett hinder blockerar vägen väljs den närmaste ("mest hotande") för beräkning:


Det närmaste hindret (mest hotande) väljs för beräkning.

Beräkning av undvikningskraften

Undvikningskraften måste trycka karaktären bort från hinderet, så att den kan undvika sfären. Det kan göras med hjälp av en vektor som bildas genom att använda sfärens mitt (vilket är en positionsvektor) och ett huvud vektor. Vi beräknar denna undvikande kraft enligt följande:

 avoidance_force = ahead - obstacle_center avoidance_force = normalisera (avoidance_force) * MAX_AVOID_FORCE

Efter avoidance_force beräknas det är normaliserat och skalas av MAX_AVOID_FORCE, vilket är ett tal som används för att definiera avoidance_force längd. Ju större MAX_AVOID_FORCE desto starkare är undvikningskraften som skjuter karaktären bort från hinderet.


Undvikningskraftberäkning. Den streckade orange linjen visar den väg som tecknet tar för att undvika hinderet. Tips: Positionen för vilken enhet som helst kan beskrivas som en vektor, så att de kan användas i beräkningar med andra vektorer och krafter.

Undvik hindret

Den slutliga genomförandet för kollisionsundvikande() metod som returnerar undvikningskraften är:

 privat funktionskollisionAvoidance (): Vector3D ahead = ...; // beräkna framåtriktad vektor ahead2 = ...; // beräkna framåt2 vektorn var mostThreatening: Obstacle = findMostThreateningObstacle (); var undvikande: Vector3D = ny Vector3D (0, 0, 0); om (mostThreatening! = null) avoidance.x = ahead.x - mostThreatening.center.x; avoance.y = ahead.y - mostThreatening.center.y; avoidance.normalize (); avoidance.scaleBy (MAX_AVOID_FORCE);  annars avoidance.scaleBy (0); // nullify the avoidance force  privat funktion findMostThreateningObstacle (): Hindring var mostThreatening: Hindring = null; för (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; 

Undvikningskraften måste läggas till karaktärens hastighetsvektor. Som tidigare förklarats kan alla styrkor sammanföras i ett, vilket ger en kraft som representerar allt aktivt beteende som verkar på karaktären.

Beroende på undvikningskraftens vinkel och riktning kommer det inte att avbryta andra styrkor, som att söka eller vandra. Undvikningskraften läggs till spelarens hastighet som vanligt:

 styrning = inget (); // nollvektorn, som betyder "nollstyrka" styrning = styrning + sök (); // förutsatt att tecknet söker något styrning = styrning + kollisionAvoidance (); styrning = stympa (styrning, max_force) styrning = styrning / masshastighet = trunkera (hastighet + styrning, max_hastighet) position = läge + hastighet

Eftersom alla styrningsbeteenden omräknas varje speluppdatering kommer undvikningskraften att förbli aktiv så länge hinderet blockerar vägen.

Så snart hindret inte avlyssnar ett huvud vektorlinjen kommer undvikningskraften att bli null (ingen effekt) eller det kommer att beräknas för att undvika det nya hotande hinderet. Resultatet är ett tecken som kan undvika hinder:


Flytta muspekaren. Klicka för att visa styrkor.

Förbättra kollisionsdetektering

Det aktuella genomförandet har två problem, både relaterade till kollisionsdetektering. Den första sker när ett huvud vektorer ligger utanför hinder sfären, men karaktären är för nära (eller inuti) hindret.

Om det händer kommer tecknet att röra (eller ange) hindret och hoppa över undvikande processen eftersom ingen kollision upptäcktes:


Ibland är det ett huvud vektorer ligger utanför hindret, men karaktären är inuti.

Detta problem kan lösas genom att lägga till en tredje vektor till kollisionskontrollen: karaktärens positionsvektor. Användningen av tre vektorer förbättrar kollisionsdetektering.

Det andra problemet händer när karaktären ligger nära hindret, styrning bort från det. Ibland kommer manövreringen att orsaka kollision, även om karaktären bara roterar för att möta en annan riktning:


Manövrering kan orsaka kollision, även om karaktären bara roterar.

Det problemet kan lösas genom att skala ett huvud vektorer enligt karaktärens aktuella hastighet. Koden för att beräkna ett huvud vektor, till exempel, ändras till:

 dynamic_length = längd (hastighet) / MAX_VELOCITY framåt = position + normalisera (hastighet) * dynamic_length

Variabeln dynamic_length kommer att sträcka sig från 0 till 1. När tecknet rör sig i full hastighet, dynamic_length är 1; när tecknet saktar eller accelererar, dynamic_length är O eller större (t ex 0,5).

Som en konsekvens, om karaktären bara manövrerar utan att flytta, dynamic_length tenderar att nollställa en nullställning ett huvud vektor, som inte har några kollisioner.

Nedan är resultatet med dessa förbättringar:


Flytta muspekaren. Klicka för att visa styrkor.

Demo: Det är Zombie Time!

För att demonstrera kollisionen undvikande beteende i åtgärd, tror jag att en horde av zombies är den perfekta passformen. Nedan är en demonstration som visar flera zombier (med olika hastigheter) som söker muspekaren. Konst av SpicyPixel och Clint Bellanger, från OpenGameArt.


Flytta muspekaren. Klicka för att visa styrkor.

Slutsats

Kollisionsundviklingsbeteendet gör att alla tecken kan undvika hinder i miljön. Eftersom alla styrkrafter omräknas varje speluppdatering, samverkar karaktärerna sömlöst med olika hinder och analyserar alltid den mest hotande (den närmaste).

Även om detta beteende inte är en sökningsalgoritm, är de uppnådda resultaten ganska övertygande för trånga kartor.