Forstå styringsadferd kø

Tenk deg en spillscene hvor et rom er overfylt med AI-kontrollerte enheter. Av en eller annen grunn må de forlate rommet og passere gjennom en døråpning. I stedet for å få dem til å gå over hverandre i en kaotisk strøm, lær dem hvordan de høflig skal gå mens de står i kø. Denne opplæringen presenterer styringsadferd med ulike tilnærminger for å få folkemengden til å bevege seg mens de danner rader av enheter.

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

, i sammenheng med denne opplæringen, er prosessen med å stå i kø, og danner en rekke tegn som tålmodig venter på å komme et sted. Som den første i linjen beveger seg, følger resten, og skaper et mønster som ser ut som et tog som trekker vogner. Når du venter, bør et tegn aldri forlate linjen.

For å illustrere køenes adferd og vise de forskjellige implementasjonene, er en demonstrasjon med en "køer scene" den beste måten å gå. Et godt eksempel er et rom som er overfylt med AI-kontrollerte enheter, alle prøver å forlate rommet og passere gjennom døråpningen:


Boids forlater rommet og går gjennom døråpningen uten køens oppførsel. Klikk for å vise styrker.

Denne scenen ble laget ved hjelp av to tidligere beskrevne atferd: søk og kollisjonssvikt.

Døråpningen er laget av to rektangulære hindringer plassert side ved side med et gap mellom dem (døråpningen). Tegnene søker et poeng bak det. Når det er, plasseres tegnene nederst på skjermen.

Akkurat nå, uten køenes adferd, ser scenen ut som en horde av vildmenn som trapper på hverandres hoder for å komme fram til destinasjonen. Når vi er ferdige, går publikum jevnt ut av plassen og danner rader.


Ser fremover

Den første evnen en karakter må oppnå for å stå i kø er å finne ut om det er noen foran dem. Basert på denne informasjonen, kan den bestemme om å fortsette eller å slutte å flytte.

Til tross for eksistensen av mer sofistikerte måter å sjekke naboer fremover, bruker jeg en forenklet metode basert på avstanden mellom et punkt og et tegn. Denne tilnærmingen ble brukt i kollisjonssvindingsadferdigheten for å sjekke for hindringer fremover:


Test for naboer ved hjelp av det forrige punktet.

Et poeng som heter fremover er projisert foran tegnet. Hvis avstanden mellom det punktet og et nabostatus er mindre enn eller lik MAX_QUEUE_RADIUS, det betyr at det er noen foran og tegnet må slutte å bevege seg.

De fremover punkt beregnes som følger (pseudo-kode):

 // Både qa og fremover er mattevektorer qa = normaliser (hastighet) * MAX_QUEUE_AHEAD; fremover = qa + posisjon;

Hastigheten, som også gir karakterens retning, blir normalisert og skalert av MAX_QUEUE_AHEAD å produsere en ny vektor kalt qa. Når qa er lagt til i stilling vektor, resultatet er et punkt foran karakteren, og en avstand på MAX_QUEUE_AHEAD enheter vekk fra det.

Alt dette kan pakkes inn i getNeighborAhead () metode:

 privat funksjon getNeighborAhead (): Boid var jeg: int; var rett: Boid = null; var qa: Vector3D = hastighet.klone (); qa.normalize (); qa.scaleBy (MAX_QUEUE_AHEAD); fremover = position.clone (). add (qa); for (i = 0; i < Game.instance.boids.length; i++)  var neighbor :Boid = Game.instance.boids[i]; var d :Number = distance(ahead, neighbor.position); if (neighbour != this && d <= MAX_QUEUE_RADIUS)  ret = neighbor; break;   return ret; 

Metoden kontrollerer avstanden mellom fremover punkt og alle andre tegn, returnerer det første tegnet hvis avstand er mindre eller lik MAX_QUEUE_AHEAD. Hvis ingen tegn er funnet, returnerer metoden null.


Opprette køningsmetoden

Som med alle andre oppføringer, kjørekraft beregnes ved hjelp av en metode som heter kø():

 privatfunksjonskø (): Vector3D var nabo: Boid = getNeighborAhead (); hvis (nabo! = null) // TODO: ta tiltak fordi naboen er foran returnere ny Vector3D (0, 0); 

Resultatet av getNeighborAhead () i lagret i variabelen nabo. Hvis nabo! = null det betyr at det er noen foran Ellers er banen klar.

De kø(), som alle andre atferdsmetoder, må returnere en kraft som er styrkraften knyttet til metoden selv. kø() vil returnere en kraft uten styrke for nå, så det vil ikke gi noen effekt.

De Oppdater() Metode for alle tegn i døråpningsscenen, til nå, er (pseudokode):

 offentlig funksjon oppdatering (): void var døråpning: Vector3D = getDoorwayPosition (); styring = søk (døråpning); // Søk dørstyring = styring + kollisjonAvoidance (); // unngå hindringer styring = styring + kø (); // kø langs veien styring = avkorte (styring, MAX_FORCE); styring = styring / masse; hastighet = trunkere (hastighet + styring, MAX_SPEED); posisjon = posisjon + hastighet;

Siden kø() Returnerer null null, tegnene fortsetter å bevege seg uten å danne rader. Det er på tide å få dem til å gjøre noe når en nabo blir oppdaget rett fram.


Noen ord om å stoppe bevegelsen

Styringsadferdene er basert på krefter som hele tiden endres, slik at hele systemet blir veldig dynamisk. Avhengig av implementeringen, jo flere krefter som er involvert, jo vanskeligere blir det å finne og avbryte en bestemt kraftvektor.

Implementeringen som brukes i denne styringsadferanseserien, legger til alle krefter sammen. Som en konsekvens, for å avbryte en kraft, må den beregnes, omvendt og legges til den gjeldende styrkraftsvektoren igjen.

Det er ganske mye som skjer i ankomstadferansen, hvor hastigheten avbrytes for å få tegnet til å stoppe. Men hva skjer når flere krefter opptrer sammen, som for eksempel kollisjon, unngå, flee og mer?

Følgende avsnitt presenterer to ideer for å få et tegnstopp å flytte. Den første bruker en "hard stop" tilnærming som virker direkte på hastighetsvektoren, og ignorerer alle andre styringskrefter. Den andre bruker en kraftvektor som heter brems, for å fornøyd avbryte alle andre styringskrefter, slik at karakteren slutter å bevege seg.


Stoppende bevegelse: "Hard Stop"

Flere styringskrefter er basert på karakterens hastighetsvektor. Hvis den vektoren endres, vil alle andre krefter bli påvirket når de omberegnes. Den "harde stopp" -ideen er ganske enkel: Hvis det er et tegn foran, "krymper vi" hastighetsvektoren:

 privatfunksjonskø (): Vector3D var nabo: Boid = getNeighborAhead (); hvis (nabo! = null) hastighet.scaleBy (0.3);  returner ny Vector3D (0, 0); 

I koden ovenfor er hastighet vektoren er skalert til 30% av dens nåværende størrelse (lengde) mens et tegn er foran. Som følge av dette er bevegelsen drastisk redusert, men det vil etter hvert komme tilbake til sin normale størrelse når tegnet som blokkerer veien beveger seg.

Det er lettere å forstå ved å analysere hvordan bevegelsen beregnes hver oppdatering:

 hastighet = trunkere (hastighet + styring, MAX_SPEED); posisjon = posisjon + hastighet;

Hvis hastighet kraften fortsetter å krympe, det gjør også styring tvinge, fordi den er basert på hastighet makt. Det skaper en ond syklus som vil ende opp med en ekstremt lav verdi for hastighet. Det er da karakteren slutter å bevege seg.

Når krympeprosessen slutter, vil hver spilloppdatering øke hastighet vektor litt, påvirker styring tvinge også. Til slutt flere oppdateringer etter vil bringe begge hastighet og styring vektor tilbake til sine normale størrelser.

Den "harde stopp" -tilnærmingen gir følgende resultat:


Kjønsadferd med "hard stop" tilnærming. Klikk for å vise styrker.

Selv om dette resultatet er ganske overbevisende, føles det som et "robot" utfall. En ekte mengde har vanligvis ingen tomme mellomrom mellom medlemmene sine.


Stoppende bevegelse: Bremsekraft

Den andre tilnærmingen for å stoppe bevegelsen forsøker å skape et mindre "robot" resultat ved å kansellere alle aktive styrkrefter ved hjelp av a brems makt:

 privatfunksjonskø (): Vector3D var v: Vector3D = hastighet.klone (); var brems: Vector3D = ny Vector3D (); var nabo: Boid = getNeighborAhead (); hvis (nabo! = null) brake.x = -steering.x * 0,8; brake.y = -steering.y * 0,8; v.scaleBy (-1); bremse = brake.add (v);  returbrems; 

Istedenfor å skape brems tvinge ved å regne ut og reversere hver enkelt av de aktive styrkrefter, brems beregnes basert på gjeldende styring vektor, som holder alle styringskrefter lagt til i øyeblikket:


Representasjon av bremsekraft.

De brems kraft mottar begge sine x og y komponenter fra styring tvinge, men invertert og med en skala av 0.8. Det betyr at brems har 80% av størrelsen på styring og peker i motsatt retning.

Tips: Bruker styring tvinge direkte er farlig. Hvis kø() er den første oppførselen som skal brukes på et tegn, den styring kraft vil være "tom". Som en konsekvens, kø() må påberopes etter alle andre styringsmetoder, slik at den kan få tilgang til det komplette og endelige styring makt.

De brems kraft må også avbryte karakterens hastighet. Det er gjort ved å legge til -hastighet til brems makt. Etter det, metoden kø() kan returnere finalen brems makt.

Resultatet av å bruke bremsekraften er følgende:


Kjøregenerering ved hjelp av bremsekrafttilnærming. Klikk for å vise styrker.

Overlapning av overførings tegn

Bremsemetoden gir et mer naturlig resultat sammenlignet med den "robotic" gamle, fordi alle tegn prøver å fylle de tomme mellomrom. Det introduserer imidlertid et nytt problem: tegn overlapper.

For å fikse det, kan bremsemetoden forbedres med en litt modifisert versjon av "hard stop" -tilnærmingen:

 privatfunksjonskø (): Vector3D var v: Vector3D = hastighet.klone (); var brems: Vector3D = ny Vector3D (); var nabo: Boid = getNeighborAhead (); hvis (nabo! = null) brake.x = -steering.x * 0,8; brake.y = -steering.y * 0,8; v.scaleBy (-1); bremse = brake.add (v); hvis (avstand (posisjon, naboposisjon) <= MAX_QUEUE_RADIUS)  velocity.scaleBy(0.3);   return brake; 

En ny test brukes til å sjekke nærliggende naboer. Denne gangen i stedet for å bruke fremover pek på å måle avstanden, den nye testen kontrollerer avstanden mellom tegnene stilling vektor:


Kontroller nærliggende naboer innenfor MAX_QUEUE_RADIUS-radiusen sentrert på stillingen i stedet for det fremadrettede punktet.

Denne nye testen kontrollerer om det er noen nærliggende tegn innenfor MAX_QUEUE_RADIUS radius, men nå er det sentrert på stilling vektor. Hvis noen er innenfor rekkevidde, betyr det at omgivelsene blir for overfylte, og tegn begynner sannsynligvis å overlappe.

Overlappingen reduseres ved å skalere hastighet vektor til 30% av sin nåværende størrelse hver oppdatering. Akkurat som i "hard stop" -tilnærmingen, krymper hastighet vektor reduserer bevegelsen drastisk.

Resultatet virker mindre "robotic", men det er ikke ideelt, siden tegnene fortsatt overlapper ved døråpningen:


Køsadferd med "hard stopp" og bremsekraft kombinert. Klikk for å vise styrker.

Legge til separasjon

Selv om tegnene prøver å nå døråpningen på en overbevisende måte, fyller alle tomme mellomrom når banen blir smal, de kommer for nær hverandre ved døråpningen.

Dette kan løses ved å legge til en separasjonskraft:

 privatfunksjonskø (): Vector3D var v: Vector3D = hastighet.klone (); var brems: Vector3D = ny Vector3D (); var nabo: Boid = getNeighborAhead (); hvis (nabo! = null) brake.x = -steering.x * 0,8; brake.y = -steering.y * 0,8; v.scaleBy (-1); bremse = brake.add (v); bremse = brake.add (separasjon ()); hvis (avstand (posisjon, naboposisjon) <= MAX_QUEUE_RADIUS)  velocity.scaleBy(0.3);   return brake; 

Tidligere brukt i lederens oppførsel, separasjonskraften ble lagt til brems kraft vil få tegnene til å stoppe samtidig som de prøver å holde seg vekk fra hverandre.

Resultatet er en overbevisende mengde som prøver å nå døråpningen:


Køsadferd med "hard stopp", bremsekraft og separasjon kombinert. Klikk for å vise styrker.

Konklusjon

Køenes adferd tillater at tegn står i kø og venter tålmodig for å komme fram til destinasjonen. En gang i kø, vil et tegn ikke forsøke å "jukse" ved hoppeposisjoner; Den beveger seg bare når tegnet rett foran det beveger seg.

Døråpningsscenen som ble brukt i denne opplæringen, presenterte hvor allsidig og tilpassbar denne oppførselen kan være. Noen få endringer gir forskjellige resultater, som kan finjusteres til en rekke situasjoner. Oppførselen kan også kombineres med andre, for eksempel kollisjonssvikt.

Jeg håper du likte denne nye oppførselen og begynner å bruke den til å legge til bevegelige folkemengder i spillet ditt!