Finite-state maskiner og styringsadferd er et perfekt match: deres dynamiske natur tillater kombinasjonen av enkle stater og krefter for å skape komplekse oppførselsmønstre. I denne opplæringen lærer du hvordan du kodes a Tall mønster ved hjelp av en stabelbasert finite-state-maskin kombinert med styringsadferd.
Alle FSM ikoner laget av Lorc og tilgjengelig på http://game-icons.net. Eiendeler i demo: Top / Down Shoot 'Em Up Spritesheet av takomogames and Alien Breed (esque) Top-Down Tilesheet av SpicyPixel.
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ø.
Etter å ha fullført denne opplæringen, vil du kunne implementere et gruppemønster der en gruppe soldater vil følge lederen, jakte på fiender og plageelementer:
Den forrige veiledningen om finite-state-maskiner beskrev hvor nyttig de er for å implementere kunstig intelligenslogikk: i stedet for å skrive en svært kompleks haug med AI-kode, kan logikken spres over et sett med enkle stater, hver som utfører svært spesifikke oppgaver, som for eksempel løper vekk fra en fiende.
Kombinasjonen av tilstander resulterer i et sofistikert AI, men lett å forstå, tilpasse og vedlikeholde. Den strukturen er også en av pilarene bak styringsadferd: kombinasjonen av enkle krefter for å skape komplekse mønstre.
Det er derfor FSMs og styringsadferd gjør en god kombinasjon. Statene kan brukes til å kontrollere hvilke krefter som skal fungere på et tegn, og forbedre det allerede kraftige settet av mønstre som kan opprettes ved hjelp av styringsadferd.
For å organisere alle oppføringer, vil de bli spredt over statene. Hver stat vil generere en bestemt oppførselskraft, eller et sett av dem, som å søke, fly, og kollisjon unngås.
Når en bestemt tilstand er aktiv, vil bare dens resulterende kraft bli brukt på tegnet, slik at den oppfører seg tilsvarende. For eksempel, hvis den nåværende aktive tilstanden er Runaway
og dens styrker er en kombinasjon av flykte
og kollisjon unngås
, tegnet vil fly et sted samtidig som det unngås noen hindring.
Styringskrefter beregnes hver spilloppdatering, og legges deretter til tegnets hastighetsvektor. Som en konsekvens, når den aktive tilstanden endres (og dermed bevegelsesmønsteret), overgår tegnet jevnt til det nye mønsteret etter hvert som de nye kreftene legges til etter hver oppdatering.
Den dynamiske naturen til styreadferd sikrer denne fluidovergangen; statene samordner bare hvilke styringskrefter som er aktive når som helst.
Strukturen for å implementere et lagsmønster vil inkapslere FSMs og styringsadferd i egenskapene til en klasse. Enhver klasse som representerer en enhet som beveger seg eller ellers påvirkes av styringskrefter, vil ha en eiendom som kalles boid
, som er en forekomst av Boid
klasse:
offentlig klasse Boid public var posisjon: Vector3D; offentlig varhastighet: Vector3D; offentlig varstyring: Vector3D; offentlig var masse: tall; offentlig funksjonssøk (mål: Vector3D, slowingRadius: Number = 0): Vector3D (...) offentlig funksjon flyr (posisjon: Vector3D): Vector3D (...) offentlig funksjon oppdatering (): void (...) )
De Boid
Klassen ble brukt i styringsadferdsserien og det gir egenskaper som hastighet
og stilling
(begge matematiske vektorer), sammen med metoder for å legge til styringskrefter, for eksempel søke()
, flykte()
, etc.
En enhet som bruker en stabelbasert FSM vil ha samme struktur av Maur
klasse fra forrige FSM-opplæringen: Stack-basert FSM styres av hjerne
eiendom og hver stat er implementert som en metode.
Nedenfor er Soldat
klasse, som har styringsadferd og FSM evner:
offentlig klasse soldat privat var hjerne: StackFSM; // Kontrollerer FSM ting private var boid: Boid; // Kontrollerer styreregistrering offentlig funksjon Soldat (posX: Nummer, posY: Nummer, totalMasse: Nummer) (...) hjerne = ny StackFSM (); // Trykk på "følg" -tilstanden, så soldaten vil følge lederen brain.pushState (følg); offentlig funksjon oppdatering (): void // Oppdater hjernen. Den vil kjøre den nåværende tilstandsfunksjonen. brain.update (); // Oppdater styringsadferdene boid.update ();
Truppemønsteret vil bli implementert ved hjelp av en stabelbasert finite-state-maskin. Soldatene, som er medlemmer av truppen, vil følge lederen (kontrollert av spilleren), jakte på noen nærliggende fiender.
Når en fiende dør, kan det falle et element som kan være bra eller dårlig (a Medkit eller a badkit, henholdsvis). En soldat vil bryte troppformasjonen og samle nærliggende gode ting, eller vil unnslippe stedet for å unngå eventuelle dårlige gjenstander.
Nedenfor er en grafisk fremstilling av den stabelbaserte FSM som styrer soldatens "hjerne":
De neste avsnittene presenterer implementeringen av hver stat. Alle kodestykker i denne opplæringen beskriver hovedideen til hvert trinn, unnlatelse av alle detaljer angående spillmotoren som brukes (Flixel, i dette tilfellet).
Den første staten som skal gjennomføres, er den som vil forbli aktiv nesten hele tiden: Følg lederen. Plyndringsdelen vil bli implementert senere, så for nå Følg
staten vil bare få soldaten til å følge lederen, bytte nåværende tilstand til jakt
hvis det er en fiende i nærheten:
offentlig funksjon følger (): void var aLeader: Boid = Game.instance.boids [0]; // få en peker til lederen addSteeringForce (boid.followLeader (aLeader)); // Følg lederen // Er det et monster i nærheten? hvis (getNearestEnemy ()! = null) // Ja, det er! Hunt det ned! // Trykk på "jakten" -tilstanden. Det vil få soldaten til å stoppe etter lederen og / / begynne å jakte på monsteret. brain.pushState (jakt); privat funksjon getNearestEnemy (): Monster // Her går implementeringen for å få nærmeste fiende
Til tross for tilstedeværelsen av fiender, mens staten er aktiv, vil den alltid generere en styrke for å følge lederen ved å bruke lederens oppførsel.
Hvis getNearestEnemy ()
Returnerer noe, det betyr at det er en fiende rundt. I så fall er det jakt
tilstanden skyves inn i stabelen gjennom samtalen brain.pushState (hunt)
, slik at soldaten slutter å følge lederen og begynner å jakte på fiender.
For nå, implementeringen av jakt()
Staten kan bare stikke seg selv fra stabelen, slik at soldatene ikke vil bli sittende fast i jaktstaten:
offentlig funksjon jakt (): void // For nå, la oss bare pop jakten () staten fra hjernen. brain.popState ();
Merk at ingen informasjon er sendt til jakt
stat, for eksempel hvem er nærmeste fiende. Denne informasjonen må samles inn av jakt
stat selv fordi den bestemmer om jakt
bør forbli aktiv eller pop seg selv fra stabelen (returnerer kontrollen til Følg
stat).
Resultatet hittil er en soldatgruppe som følger lederen (merk at soldatene ikke vil jakte fordi jakt()
Metoden bare popper seg):
Tips: hver stat burde være ansvarlig for å avslutte sin eksistens ved å poppe seg fra stakken.
Den neste tilstanden som skal implementeres er jakt
, som vil få soldater til å jakte på en nærliggende fiende. Koden for jakt()
er:
offentlig funksjon jakt (): void var aNearestEnemy: Monster = getNearestEnemy (); // Har vi et monster i nærheten? hvis (aNestestEnemy! = null) // Ja, det gjør vi. La oss beregne hvor langt det er. var aDistance: Number = calculateDistance (aNearestEnemy, dette); // Er monsteret nært nok til å skyte? hvis (aDistance <= 80) // Yes, so let's face it! faceEnemyStandingStill(aNearestEnemy); // Fire away! Take that, monster! shoot(); else // No, the monster is far away. Seek it until it gets close enough. addSteeringForce(boid.seek(aNearestEnemy.boid.position)); // Avoid crowding while seeking the target… addSteeringForce(boid.separation()); else // No, there is no monster nearby. Maybe it was killed or ran away. Let's pop the "hunt" // state and come back doing what we were doing before the hunting. brain.popState();
Staten begynner med å tildele aNearestEnemy
med nærmeste fiende. Hvis aNearestEnemy
er null
det betyr at det ikke er noen fiende rundt, så staten må ende. Samtalen brain.popState ()
popper på jakt
state, bytte soldaten til neste tilstand i stabelen.
Hvis aNearestEnemy
er ikke null
, det betyr at det er en fiende som skal jages ned og staten skal forbli aktiv. Jaktalgoritmen er basert på avstanden mellom soldaten og fienden: hvis avstanden er større enn 80, vil soldaten søke fiendens posisjon; hvis avstanden er mindre enn 80, vil soldaten møte fienden og skyte mens du står stille.
Siden jakt()
vil bli påkalt hver spilloppdatering, hvis en fiende er rundt da vil soldaten søke eller skyte den fienden. Beslutningen om å flytte eller skyte styres dynamisk av avstanden mellom soldaten og fienden.
Resultatet er en gruppe soldater som kan følge lederen og jakte på nærliggende fiender:
Hver gang en fiende blir drept, kan det falle et element. Soldaten må samle varen hvis den er en god, eller flykte varen hvis den er dårlig. Atferden er representert av to stater i den tidligere beskrevne FSM:
collectItem
og Runaway
stater. De collectItem
staten vil få en soldat til å komme til det tapt elementet, mens Runaway
staten vil få soldaten til å flykte fra den dårlige varens plassering. Begge statene er nesten identiske, den eneste forskjellen er ankomst- eller flyktekraften:
offentlig funksjon runAway (): void var aItem: Item = getNearestItem (); hvis (aItem! = null && aItem.alive && aItem.type == Item.BADKIT) var aItemPos: Vector3D = new Vector3D (); aItemPos.x = aItem.x; aItemPos.y = aItem.y; addSteeringForce (boid.flee (aItemPos)); ellers brain.popState (); offentlig funksjon collectItem (): void var aItem: Item = getNearestItem (); hvis (aItem! = null && aItem.alive && aItem.type == Item.MEDKIT) var aItemPos: Vector3D = ny Vector3D (); aItemPos.x = aItem.x; aItemPos.y = aItem.y; addSteeringForce (boid.arrive (aItemPos, 50)); ellers brain.popState (); privat funksjon getNearestItem (): Objekt // her går koden for å få nærmeste element
Her kommer en optimalisering av overgangene til nytte. Koden til overgang fra Følg
stat til collectItem
eller Runaway
stater er det samme: sjekk om det finnes et element i nærheten, og trykk deretter på den nye tilstanden.
Staten som skal skyves, avhenger av varens type. Som følge av overgangen til collectItem
eller Runaway
kan implementeres som en enkelt metode, oppkalt checkItemsNearby ()
:
privat funksjon checkItemsNearby (): void var aItem: Item = getNearestItem (); hvis (aItem! = null) brain.pushState (aItem.type == Item.BADKIT? runAway: collectItem);
Denne metoden kontrollerer nærmeste element. Hvis det er en god en, så collectItem
staten blir skjøvet inn i hjernen; hvis det er en dårlig en, Runaway
tilstanden er presset. Hvis det ikke er noe å samle, gjør metoden ingenting.
At optimalisering tillater bruk av checkItemsNearby ()
å kontrollere overgangen fra hvilken som helst tilstand til collectItem
eller Runaway
. Ifølge soldaten FSM, eksisterer denne overgangen i to stater: Følg
og jakt
.
Deres gjennomføring kan endres litt for å imøtekomme den nye overgangen:
offentlig funksjon følger (): void var aLeader: Boid = Game.instance.boids [0]; // få en peker til lederen addSteeringForce (boid.followLeader (aLeader)); // følg lederen // Sjekk om det er et element som skal samles inn (eller løpe bort fra) checkItemsNearby (); // Er det et monster i nærheten? hvis (getNearestEnemy ()! = null) // Ja, det er! Hunt det ned! // Trykk på "jakten" -tilstanden. Det vil få soldaten til å stoppe etter lederen og / / begynne å jakte på monsteret. brain.pushState (jakt); offentlig funksjon jakt (): void var aNearestEnemy: Monster = getNearestEnemy (); // Sjekk om det er et element å samle inn (eller løpe bort fra) checkItemsNearby (); // Har vi et monster i nærheten? hvis (aNestestEnemy! = null) // Ja, det gjør vi. La oss beregne hvor langt det er. var aDistance: Number = calculateDistance (aNearestEnemy, dette); // Er monsteret nært nok til å skyte? hvis (aDistance <= 80) // Yes, so let's face it! faceEnemyStandingStill(aNearestEnemy); // Fire away! Take that, monster! shoot(); else // No, the monster is far away. Seek it until it gets close enough. addSteeringForce(boid.seek(aNearestEnemy.boid.position)); // Avoid crowding while seeking the target… addSteeringForce(boid.separation()); else // No, there is no monster nearby. Maybe it was killed or ran away. Let's pop the "hunt" // state and come back doing what we were doing before the hunting. brain.popState();
Mens lederen følger, vil en soldat sjekke for nærliggende gjenstander. Når du jakter på en fiende, vil en soldat også sjekke for nærliggende gjenstander.
Resultatet er demoen nedenfor. Legg merke til at en soldat vil prøve å samle inn eller unngå et objekt når som helst der er en i nærheten, selv om det er fiender å jakte og lederen følger.
Et viktig aspekt ved tilstander og overganger er prioritet blant dem. Avhengig av linjen der en overgang er plassert innenfor en stats implementering, endres prioriteten for den overgangen.
Bruker Følg
stat og overgangen laget av checkItemsNearby ()
For eksempel, ta en titt på følgende implementering:
offentlig funksjon følger (): void var aLeader: Boid = Game.instance.boids [0]; // få en peker til lederen addSteeringForce (boid.followLeader (aLeader)); // følg lederen // Sjekk om det er et element som skal samles inn (eller løpe bort fra) checkItemsNearby (); // Er det et monster i nærheten? hvis (getNearestEnemy ()! = null) // Ja, det er! Hunt det ned! // Trykk på "jakten" -tilstanden. Det vil få soldaten til å stoppe etter lederen og / / begynne å jakte på monsteret. brain.pushState (jakt);
Den versjonen av Følg()
vil gjøre en soldat bytte til collectItem
eller Runaway
før sjekke om det er en fiende rundt. Som en konsekvens vil soldaten samle (eller flykte fra) et element selv når det er fiender rundt det som skal jages ned av jakt
stat.
Her er en annen implementering:
offentlig funksjon følger (): void var aLeader: Boid = Game.instance.boids [0]; // få en peker til lederen addSteeringForce (boid.followLeader (aLeader)); // Følg lederen // Er det et monster i nærheten? hvis (getNearestEnemy ()! = null) // Ja, det er! Hunt det ned! // Trykk på "jakten" -tilstanden. Det vil få soldaten til å stoppe etter lederen og / / begynne å jakte på monsteret. brain.pushState (jakt); ellers // Sjekk om det er et element å samle inn (eller løpe bort fra) checkItemsNearby ();
Den versjonen av Følg()
vil gjøre en soldat bytte til collectItem
eller Runaway
bare etter han finner ut at det ikke er noen fiender å drepe.
Den nåværende gjennomføringen av Følg()
, jakt()
og collectItem ()
lider av prioriterte problemer. Soldaten vil prøve å samle et element selv når det er viktigere ting å gjøre. For å fikse det, er det behov for noen tweaks.
Angående Følg
stat, koden kan oppdateres til:
(følg () med prioriteter)
offentlig funksjon følger (): void var aLeader: Boid = Game.instance.boids [0]; // få en peker til lederen addSteeringForce (boid.followLeader (aLeader)); // Følg lederen // Er det et monster i nærheten? hvis (getNearestEnemy ()! = null) // Ja, det er! Hunt det ned! // Trykk på "jakten" -tilstanden. Det vil få soldaten til å stoppe etter lederen og / / begynne å jakte på monsteret. brain.pushState (jakt); ellers // Sjekk om det er et element å samle inn (eller løpe bort fra) checkItemsNearby ();
De jakt
Staten må endres til:
offentlig funksjon jakt (): void var aNearestEnemy: Monster = getNearestEnemy (); // Har vi et monster i nærheten? hvis (aNestestEnemy! = null) // Ja, det gjør vi. La oss beregne hvor langt det er. var aDistance: Number = calculateDistance (aNearestEnemy, dette); // Er monsteret nært nok til å skyte? hvis (aDistance <= 80) // Yes, so let's face it! faceEnemyStandingStill(aNearestEnemy); // Fire away! Take that, monster! shoot(); else // No, the monster is far away. Seek it until it gets close enough. addSteeringForce(boid.seek(aNearestEnemy.boid.position)); // Avoid crowding while seeking the target… addSteeringForce(boid.separation()); else // No, there is no monster nearby. Maybe it was killed or ran away. Let's pop the "hunt" // state and come back doing what we were doing before the hunting. brain.popState(); // Check if there is an item to collect (or run away from) checkItemsNearby();
Endelig, den collectItem
Staten må endres for å avbryte noen plager hvis det er en fiende rundt:
offentlig funksjon collectItem (): void var aItem: Item = getNearestItem (); var aMonsterNearby: Boolean = getNearestEnemy ()! = null; hvis (! aMonsterNearby && aItem! = null && aItem.alive && aItem.type == Item.MEDKIT) var aIemPos: Vector3D = ny Vector3D (); aItemPos.x = aItem.x; aItemPos.y = aItem.y; addSteeringForce (boid.arrive (aItemPos, 50)); ellers brain.popState ();
Resultatet av alle disse endringene er demoen fra begynnelsen av opplæringen:
I denne opplæringen lærte du hvordan du kodes et gruppemønster der en gruppe soldater vil følge en leder, jakte ned og plyndre nærliggende fiender. AI er implementert ved hjelp av en stabelbasert FSM kombinert med flere styringsegenskaper.
Som vist er finite-state maskiner og styring atferd en kraftig kombinasjon og en flott kamp. Spredning av logikken over FSM-statene, det er mulig å dynamisk velge hvilken styringskrefter som skal fungere på et tegn, slik at det blir mulig å lage komplekse AI-mønstre.
Kombiner styringsadferdene du allerede kjenner med FSMs, og opprett nye og fremragende mønstre!