Forstå styringsadferd Bevegelsesleder

Styringsadferdene er gode for å skape realistiske bevegelsesmønstre, men de er enda større hvis du kan kontrollere, bruke og kombinere dem enkelt. I denne opplæringen diskuterer jeg og dekker implementeringen av en bevegelsesleder for alle tidligere diskuterte oppføringer.

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.


Kombinerer styringskrefter

Som tidligere diskutert, gir hver styreadferd en resulterende kraft (kalt en styrestyrke) som legges til hastighetsvektoren. Retningen og størrelsen på den kraften vil drive karakteren, slik at den beveger seg i henhold til et mønster (søk, fly, vandre og så videre). Den generelle beregningen er:

 styring = søk (); // dette kan være hvilken som helst betjeningsstyring = trunkate (styring, maks_force) styring = styring / massehastighet = trunkere (hastighet + styring, maks_speed) posisjon = posisjon + hastighet

Siden styrkraften er en vektor, kan den legges til en hvilken som helst annen vektor (akkurat som hastigheten). Men den virkelige "magien" ligger i det faktum at du kan legge flere styrekrefter sammen - det er så enkelt som:

 styring = ingenting (); // null-vektoren, som betyr "nullstyrke" styring = styring + søk (); styring = styring + fly (); (...) styring = trunkering (styring, maks_force) styring = styring / masshastighet = trunkering (hastighet + styring, maks_speed) posisjon = posisjon + hastighet

De kombinerte styringskreftene vil resultere i en vektor som representerer alle de krefter. I kodestykket over, vil den resulterende styrekraften få karakteren til å søke noe samtidig samtidig det vil flykte noe ellers.

Se under noen eksempler på styringskrefter kombinert for å produsere en enkelt styringskraft:


Styringskrefter kombinert.

Komplekse mønstre uten problemer

Kombinasjonen av styringskrefter vil produsere ekstremt komplekse bevegelsesmønstre uten problemer. Tenk deg hvor vanskelig det ville være å skrive kode for å få en karakter til å søke noe, men samtidig unngå et bestemt område uten å bruke vektorer og krefter?

Det ville kreve beregning av avstander, områder, stier, grafer og lignende. Hvis ting beveger seg, må alle disse beregningene gjentas hverandre, fordi miljøet endres hele tiden.

Med styringsadferd er alle krefter dynamisk. De er ment å bli beregnet hver spilloppdatering, så de vil naturlig og sømløst reagere på miljøendringer.

Demoen nedenfor viser skip som vil søke musepekeren, men vil flykte midt på skjermen, begge samtidig:


Skipene vil søke på musemarkøren (grå), men vil flykte midt på skjermen (oransje). Klikk for å vise styrker.

Bevegelsesleder

For å kunne bruke flere styreadferdigheter samtidig, på en enkel og enkel måte, a bevegelsesleder kommer til nytte. Tanken er å lage en "svart boks" som kan kobles til en eksisterende enhet, slik at den kan utføre disse oppføringene.

Lederen har en referanse til enheten som den er koblet til ("verten"). Lederen vil gi verten et sett med metoder, for eksempel søke() og flykte(). Hver gang slike metoder blir påkalt, oppdaterer lederen sine interne egenskaper for å produsere en styrekraftvektor.

Etter at prosessoren behandler alle invokasjoner, vil den legge den resulterende styrken til vertshastighetsvektoren. Det vil endre vertens hastighetsvektorstørrelse og retning i henhold til den aktive oppførsel.

Figuren nedenfor viser arkitekturen:


Bevegelsesleder: Pluggarkitektur.

Gjør ting generisk

Lederen har et sett med metoder, hver som representerer en distinkt oppførsel. Hver oppførsel må leveres med ulike deler av ekstern informasjon for å kunne fungere.

Søkeadferansen trenger for eksempel et punkt i rommet som brukes til å beregne styrkraften mot det stedet; forfølgelse trenger flere deler av informasjon fra målet, for eksempel nåværende posisjon og hastighet. Et punkt i rommet kan uttrykkes som en forekomst av Punkt eller Vector2D, begge ganske vanlige klasser i alle rammer.

Målet som brukes i forfølgelsen, kan imidlertid være noe. For å gjøre bevegelseslederen generisk nok må den motta et mål som uavhengig av sin type kan svare på noen få "spørsmål", som for eksempel "Hva er din nåværende hastighet?". Ved å bruke noen prinsipper for objektorientert programmering, kan det oppnås med grensesnitt.

Forutsatt grensesnittet IBoid beskriver en enhet som kan håndteres av bevegelsesbehandleren, hvilken som helst klasse i spillet kan bruke styringsadferd, så lenge det utføres IBoid. Det grensesnittet har følgende struktur:

 offentlig grensesnitt IBoid funksjon getVelocity (): Vector3D; funksjon getMaxVelocity (): Number; funksjon getPosition (): Vector3D; funksjon getMass (): Number; 

Bevegelseslederstruktur

Nå som lederen kan samhandle med alle spill enheter på en generisk måte, kan den grunnleggende strukturen opprettes. Lederen består av to egenskaper (den resulterende styrken og verten referansen) og et sett med offentlige metoder, en for hver oppførsel:

 offentlig klasse SteeringManager offentlig varestyring: Vector3D; offentlig var vert: IBoid; // Konstruktørens offentlige funksjon SteeringManager (vert: IBoid) this.host = host; this.steering = ny Vector3D (0, 0);  // Felles API (en metode for hver opptreden) offentlig funksjon å søke (target: Vector3D, slowingRadius: tall = 20): void  public funksjon flykte (target: Vector3D): void  public funksjon vandre (): void  offentlig funksjon unngås (mål: IBoid): void  offentlig funksjon forfølgelse (mål: IBoid): void  // Oppdateringsmetoden. // Skal ringes etter at alle atferd har blitt påkalt offentlig funksjonoppdatering (): void  // Tilbakestill intern styringskraft. offentlig funksjon reset (): void  // Den innvendige API egen funksjon doSeek (target: Vector3D, slowingRadius: Tall = 0): Vector3D  egen funksjon doFlee (target: Vector3D): Vector3D  egen funksjon doWander (): Vector3D  privat funksjon doEvade (mål: IBoid): Vector3D  privat funksjon doPursuit (mål: IBoid): Vector3D 

Når lederen er opprettet, må den motta en referanse til verten den er koblet til. Det vil tillate lederen å endre vertshastighetsvektoren i henhold til den aktive oppførsel.

Hver oppførsel er representert ved to metoder, en offentlig og en privat. Bruk søk ​​som et eksempel:

 offentlig funksjon å søke (target: Vector3D, slowingRadius: tall = 20): void  egen funksjon doSeek (target: Vector3D, slowingRadius: Tall = 0): Vector3D 

Offentligheten søke() vil bli påkalt for å fortelle lederen å anvende den spesifikke oppførselen. Metoden har ingen returverdi og dens parametere er relatert til selve oppførelsen, for eksempel et punkt i rommet. Under hetten er den private metoden doSeek () vil bli påkalt, og dens returverdi, den beregnede styrken for den spesifikke oppførelsen, vil bli lagt til lederens styring eiendom.

Følgende kode viser implementeringen av søk:

 // Publiseringsmetoden. // Mottar et mål å søke og en slowingRadius (pleide å utføre). offentlig funksjonssøk (mål: Vector3D, slowingRadius: Number = 20): void steering.incrementBy (doSeek (target, slowingRadius));  // Den virkelige implementeringen av søk (med ankomstkode inkludert) privat funksjon doSeek (mål: Vector3D, slowingRadius: Number = 0): Vector3D var force: Vector3D; var avstand: tall; ønsket = target.subtract (host.getPosition ()); avstand = ønsket.lengde; desired.normalize (); hvis (avstand <= slowingRadius)  desired.scaleBy(host.getMaxVelocity() * distance/slowingRadius);  else  desired.scaleBy(host.getMaxVelocity());  force = desired.subtract(host.getVelocity()); return force; 

Alle andre oppførselsmetoder er implementert på en veldig lignende måte. De forfølgelse() Metoden vil for eksempel se slik ut:

 Offentlig funksjonstrening (mål: IBoid): void steering.incrementBy (doPursuit (target));  privat funksjon doPursuit (mål: IBoid): Vector3D distance = target.getPosition (). trekke (host.getPosition ()); var updatesNeeded: Number = distance.length / host.getMaxVelocity (); var tv: Vector3D = target.getVelocity (). klone (); tv.scaleBy (updatesNeeded); targetFuturePosition = target.getPosition (). klone (). legg til (tv); returnere doSeek (targetFuturePosition); 

Bruk koden fra tidligere opplæringsprogrammer, alt du trenger å gjøre er å tilpasse dem i form av oppførsel() og doBehavior (), slik at de kan legges til bevegelsesbehandleren.


Bruke og oppdatere styringskrefter

Hver gang en oppføringsmetode er påkalt, blir den resulterende kraften den produserer lagt til sjefen styring eiendom. Som en konsekvens vil eiendommen akkumulere alle styringskrefter.

Når alle oppføringer er påkrevd, må sjefen bruke den aktuelle styrkraften til vertens hastighet, så den vil bevege seg i henhold til den aktive oppførselen. Det utføres i Oppdater() metode for bevegelsesleder:

 offentlig funksjon oppdatering (): void var hastighet: Vector3D = host.getVelocity (); var posisjon: Vector3D = host.getPosition (); trunkate (styring, MAX_FORCE); steering.scaleBy (1 / host.getMass ()); velocity.incrementBy (styring); trunkate (hastighet, host.getMaxVelocity ()); position.incrementBy (hastighet); 

Fremgangsmåten ovenfor må påberopes av verten (eller en hvilken som helst annen spill-enhet) etter at alle oppføringer er påkrevd, ellers vil verten aldri endre sin hastighetsvektor for å matche den aktive oppførsel.


bruk

La oss anta en klasse som heter Bytte bør bevege seg ved hjelp av styreadferd, men for øyeblikket har den ingen styringskode eller bevegelsesleder. Dens struktur vil se slik ut:

 offentlig klasse Prey offentlig varposisjon: Vector3D; offentlig varhastighet: Vector3D; offentlig var masse: tall; offentlig funksjon Prey (posX: Nummer, posY: Nummer, totalMasse: Nummer) posisjon = ny Vector3D (posX, posY); hastighet = ny Vector3D (-1, -2); masse = totalMass; x = posisjon.x; y = posisjon.y;  offentlig funksjon oppdatering (): void velocity.normalize (); velocity.scaleBy (MAX_VELOCITY); hastighet.scaleBy (1 / masse); avkorte (hastighet, MAX_VELOCITY); posisjon = posisjon.add (hastighet); x = posisjon.x; y = posisjon.y; 

Ved hjelp av denne strukturen kan klassens forekomster bevege seg ved hjelp av Euler-integrasjon, akkurat som den aller første demoen av søkeopplæringen. For å gjøre det mulig å bruke sjefen, trenger den en eiendom som refererer til bevegelseslederen og den må implementere IBoid grensesnitt:

 offentlig klasse Prey implementerer IBoid public var posisjon: Vector3D; offentlig varhastighet: Vector3D; offentlig var masse: tall; offentlig varestyring: styringsleder; offentlig funksjon Prey (posX: Nummer, posY: Nummer, totalMasse: Nummer) posisjon = ny Vector3D (posX, posY); hastighet = ny Vector3D (-1, -2); masse = totalMass; styring = ny styringsleder (dette); x = posisjon.x; y = posisjon.y;  offentlig funksjon oppdatering (): void velocity.normalize (); velocity.scaleBy (MAX_VELOCITY); hastighet.scaleBy (1 / masse); avkorte (hastighet, MAX_VELOCITY); posisjon = posisjon.add (hastighet); x = posisjon.x; y = posisjon.y;  // Nedenfor er metodene grensesnittet IBoid krever. offentlig funksjon getVelocity (): Vector3D returhastighet;  offentlig funksjon getMaxVelocity (): Nummer retur 3;  offentlig funksjon getPosition (): Vector3D returposisjon;  offentlig funksjon getMass (): tall retur masse; 

De Oppdater() Metoden må endres tilsvarende, slik at sjefen også kan oppdateres:

 offentlig funksjon oppdatering (): void // Gjør byttet vandre rundt ... steering.wander (); // Oppdater lederen slik at den vil endre byttehastighetsvektoren. // Lederen vil også utføre Euler-intergrasjonen, endre // -posisjonen vektoren. steering.update (); // Etter at lederen har oppdatert sine interne strukturer, må alt vi må // gjøre, oppdatere vår posisjon i henhold til "posisjon" -vektoren. x = posisjon.x; y = posisjon.y; 

Alle oppføringer kan brukes samtidig, så lenge alle metallsamtaler er gjort før lederen Oppdater() invokasjon, som anvender den akkumulerte styrekraften til vertshastighetsvektoren.

Koden nedenfor viser en annen versjon av Prey's Oppdater() metode, men denne gangen vil den søke en posisjon i kartet og unngå et annet tegn (begge samtidig):

 offentlig funksjon oppdatering (): void var destinasjon: Vector3D = getDestination (); // stedet å søke var jeger: IBoid = getHunter (); // få den enheten som jakter på oss // Søk destinasjonen og unn deg jägeren (samtidig!) steering.seek (destinasjon); steering.evade (Hunter); // Oppdater lederen slik at den vil endre byttehastighetsvektoren. // Lederen vil også utføre Euler-intergrasjonen, endre // -posisjonen vektoren. steering.update (); // Etter at lederen har oppdatert sine interne strukturer, må alt vi må // gjøre, oppdatere vår posisjon i henhold til "posisjon" -vektoren. x = posisjon.x; y = posisjon.y; 

Demo

Demoen nedenfor viser et komplekst bevegelsesmønster hvor flere oppføringer kombineres. Det er to typer tegn på scenen: Jeger og Bytte.

Jegeren vil forfølge et byte om det blir nært nok; det vil forfølge så lenge utholdenhetstilførselen varer; Når det går tom for utholdenhet, blir jakten avbrutt og jegeren vil vandre til den gjenoppretter sine utholdenhetsnivåer.

Her er Hunterens Oppdater() metode:

 offentlig funksjon oppdatering (): void if (hviler && stamina ++> = MAX_STAMINA) hviler = false;  hvis (byttedyr! = null &&! hviler) steering.pursuit (byttedyr); utholdenhet - = 2; hvis (utholdenhet <= 0)  prey = null; resting = true;   else  steering.wander(); prey = getClosestPrey(position);  steering.update(); x = position.x; y = position.y; 

Byttet vil vandre på ubestemt tid. Hvis jegeren blir for nær, vil det unngå. Hvis musemarkøren er nær og det er ingen jeger rundt, vil byttet søke musepekeren.

Her er Prey's Oppdater() metode:

 offentlig funksjon oppdatering (): void var avstand: Number = Vector3D.distance (posisjon, Game.mouse); jeger = getHunterWithinRange (posisjon); hvis (jeger! = null) steering.evade (jeger);  hvis (avstand <= 300 && hunter == null)  steering.seek(Game.mouse, 30);  else if(hunter == null) steering.wander();  steering.update(); x = position.x; y = position.y; 

Det endelige resultatet (grå er å vandre, grønn er søker, oransje er forfølgelse, rød er bortfall):


Jakt. Klikk for å vise styrker.

Konklusjon

En bevegelsesleder er veldig nyttig for å styre flere styreadferd på samme tid. Kombinasjonen av slike oppføringer kan produsere svært komplekse bevegelsesmønstre, slik at en spill-enhet kan søke en ting samtidig som den unngår en annen.

Jeg håper du likte styringssystemet diskutert og implementert i denne opplæringen og bruker den i spillene dine. Takk for at du leser! Ikke glem å holde deg oppdatert ved å følge oss på Twitter, Facebook eller Google+.