Lag et Hockey Game AI ved hjelp av styringsadferd angrep

I denne opplæringen fortsetter vi å kode kunstig intelligens for et hockeyspill som bruker styringsadferd og finite state maskiner. I denne delen av serien vil du lære om AI som kreves av spill enheter for å koordinere et angrep, som innebærer å fange opp og bære pucken til motstandernes mål.

Et par ord om angrep

Koordinere og utføre et angrep i et samarbeidsprosess er en svært kompleks oppgave. I den virkelige verden, når mennesker spiller et hockeyspill, gjør de flere beslutninger basert på mange variabler.

Disse beslutningene innebærer beregninger og forståelse av hva som skjer. Et menneske kan fortelle hvorfor en motstander beveger seg basert på handlinger fra en annen motstander, for eksempel, "han beveger seg for å være i en bedre strategisk posisjon." Det er ikke trivielt å portere den forståelsen til en datamaskin.

Som en konsekvens, hvis vi prøver å kode AI for å følge alle menneskelige nyanser og oppfatninger, vil resultatet bli en stor og skummel haug med kode. I tillegg kan resultatet ikke være presis eller enkelt modifiserbart.

Det er grunnen til at vårt angrep AI vil forsøke å etterligne resultat av en gruppe mennesker som spiller, ikke selve menneskets oppfatning. Denne tilnærmingen vil føre til tilnærminger, men koden blir lettere å forstå og finjustere. Resultatet er bra nok til flere brukstilfeller.

Organisere angrepet med stater

Vi vil bryte angrepsprosessen ned i mindre stykker, hver med en veldig spesifikk handling. Disse stykkene er tilstandene til en stabelbasert, finite state machine. Som tidligere forklart, vil hver stat produsere en styringskraft som vil gjøre atleten oppføre seg i samsvar med dette.

Orkestrering av disse statene og betingelsene for å bytte mellom dem vil definere angrepet. Bildet nedenfor viser den komplette FSM som brukes i prosessen:

En stabelbasert finite state machine som representerer angrepsprosessen.

Som illustrert av bildet, vil betingelsene for å bytte mellom tilstandene være utelukkende basert på puckens avstand og eierskap. For eksempel, laget har puckeneller pucken er for langt unna.

Angrepsprosessen vil bestå av fire stater: tomgang, angrep, stealPuck, og pursuePuck. De tomgang Staten ble allerede implementert i den forrige opplæringen, og det er utgangspunktet for prosessen. Derfra vil en idrettsutøver bytte til angrep hvis laget har pucken, til stealPuck hvis motstanders lag har pucken, eller til pursuePuck hvis pucken ikke har noen eier og det er nær nok til å bli samlet inn.

De angrep staten representerer en offensiv bevegelse. Mens i den tilstanden er atleten som bærer pucken (oppkalt leder) vil forsøke å nå motstanders mål. Lagkamerater vil bevege seg sammen, og prøver å støtte handlingen.

De stealPuck staten representerer noe mellom en defensiv og en offensiv bevegelse. I den tilstanden vil en idrettsutøver fokusere på å forfølge motstanderen som bærer pucken. Målet er å gjenopprette pucken, slik at laget kan begynne å angripe igjen.

Endelig, den pursuePuck Staten er ikke relatert til angrep eller forsvar; Det vil bare veilede atleterne når pucken ikke har noen eier. I den tilstanden vil en idrettsutøver prøve å få pucken som beveger seg fritt på rinken (for eksempel etter å ha blitt rammet av en persons pinne).

Oppdaterer tomgangsstaten

De tomgang tilstand som tidligere ble implementert hadde ingen overganger. Siden denne tilstanden er utgangspunktet for hele AI, la oss oppdatere det og gjøre det mulig å bytte til andre stater.

De tomgang Staten har tre overganger:

 Inaktiv tilstand og overganger i FSM som beskriver angrepsprosessen.

Hvis utøverens lag har pucken, tomgang bør poppes fra hjernen og angrep bør skyves. Tilsvarende, hvis motstanders lag har pucken, tomgang bør erstattes av stealPuck. Den resterende overgangen skjer når ingen eier pucken og det er nær atleten; i så fall, pursuePuck bør skyves inn i hjernen.

Den oppdaterte versjonen av tomgang er som følger (alle andre stater vil bli implementert senere):

klasse idrettsutøver // (...) privat funksjon tomgang (): void var aPuck: Puck = getPuck (); stopAndlookAt (aPuck); // Dette er en hack som hjelper til med å teste AI. hvis (mStandStill) returnerer; // Har pucken en eier? hvis (getPuckOwner ()! = null) // Ja, den har. mBrain.popState (); hvis (doesMyTeamHaveThePuck ()) // Mitt lag har bare pucken, det er angrepstid! mBrain.pushState (angrep);  ellers // Motstandernes lag fikk pucken, la oss prøve å stjele den. mBrain.pushState (stealPuck);  annet hvis (avstand (dette, aPuck) < 150)  // The puck has no owner and it is nearby. Let's pursue it. mBrain.popState(); mBrain.pushState(pursuePuck);   private function attack() :void   private function stealPuck() :void   private function pursuePuck() :void   

La oss fortsette med implementeringen av de andre statene.

Forfølge Puck

Nå som atleten har fått litt oppfatning om miljøet og er i stand til å bytte fra tomgang Til enhver stat, la oss fokusere på å forfølge pucken når den ikke har noen eier.

En utøver vil bytte til pursuePuck umiddelbart etter at kampen begynner, fordi pucken blir plassert i midten av rinken uten eier. De pursuePuck Staten har tre overganger:

The pursuePuck-tilstanden og overgangene i FSM som beskriver angrepsprosessen.

Den første overgangen er pucken er for langt unna, og det prøver å simulere hva som skjer i et ekte spill om å jage pucken. Av strategiske årsaker er vanligvis idrettsutøveren nærmest pucken den som prøver å fange den, mens de andre venter eller prøver å hjelpe.

Uten å bytte til tomgang når pucken er fjern, vil alle AI-kontrollerte idrettsutøvere forfølge pucken samtidig, selv om de er borte fra den. Ved å sjekke avstanden mellom utøveren og pucken, pursuePuck popper seg fra hjernen og presser tomgang når pucken er for fjern, noe som betyr at utøveren bare "ga opp" å forfølge pucken:

klasse idrettsutøver // (...) privat funksjon pursuePuck (): void var aPuck: Puck = getPuck (); hvis (avstand (dette, aPuck)> 150) // Puck er for langt unna vår nåværende posisjon, så la oss gi opp // forfølge pucken og håper noen kommer nærmere til å få pucken // for oss. mBrain.popState (); mBrain.pushState (uvirksom);  ellers // Pucken er nær, la oss prøve å ta den.  // (...)

Når pucken er tett, må idrettsutøveren gå etter den, som lett kan oppnås med søkegraden. Ved å bruke puckens posisjon som søkermål, vil idrettsutøveren seile pucken og justere sin bane mens pucken beveger seg:

klasse idrettsutøver // (...) privat funksjon pursuePuck (): void var aPuck: Puck = getPuck (); mBoid.steering = mBoid.steering + mBoid.separation (); hvis (avstand (dette, aPuck)> 150) // Puck er for langt unna vår nåværende posisjon, så la oss gi opp // forfølge pucken og håper noen kommer nærmere til å få pucken // for oss. mBrain.popState (); mBrain.pushState (uvirksom);  ellers // Pucken er nær, la oss prøve å ta den. hvis (aPuck.owner == null) // Ingen har pucken, det er vår sjanse til å søke og få det! mBoid.steering = mBoid.steering + mBoid.seek (aPuck.position);  ellers // Noen har akkurat pucken. Hvis den nye puckeieren tilhører teamet mitt, bør vi bytte til 'angrep', ellers skal jeg bytte til 'stealPuck' // og prøve å få pucken tilbake. mBrain.popState (); mBrain.pushState (doesMyTeamHaveThePuck ()? angrep: stealPuck); 

De resterende to overgangene i pursuePuck stat, laget har pucken og motstanderen har pucken, er relatert til pucken som blir fanget under forfølgingsprosessen. Hvis noen fanger pucken, må utøveren poppe pursuePuck stat og trykk en ny inn i hjernen. 

Staten som skal presses, avhenger av puckens eierskap. Hvis anropet til doesMyTeamHaveThePuck () avkastning ekte, det betyr at en lagkamerat fikk pucken, så atleten må presse angrep, som betyr at det er på tide å slutte å forfølge pucken og begynne å bevege seg mot motstanderens mål. Hvis en motstander har pucken, må utøveren presse stealPuck, som vil få laget til å prøve å gjenopprette pucken.

Som en liten forbedring bør idrettsutøvere ikke forbli for nær hverandre under pursuePuck stat, fordi en "overfylt" forfølgelse av bevegelsen er unaturlig. Legge til separasjon til statens styringskraft (linje 6 i koden ovenfor) sikrer at idrettsutøvere vil holde en minimumsavstand blant dem.

Resultatet er et lag som kan forfølge pucken. For å teste, i denne demonstrasjonen plasseres pucken midt i rinken hvert par sekunder for å gjøre atleterne bevege seg kontinuerlig:

Angrep med Puck

Etter å ha fått pucken, må en idrettsutøver og hans lag bevege seg mot motstanderens mål å score. Det er formålet med angrep stat:

Anfallstilstanden og overgangene i FSM som beskriver angrepsprosessen.

De angrep staten har bare to overganger: motstanderen har pucken og Puck har ingen eier. Siden staten er utelukkende laget for å gjøre atleter beveger seg mot motstanderens mål, er det ikke noe poeng å forbli angrep hvis pucken ikke er under lagets besittelse lenger.

Når det gjelder bevegelsen mot motstanderens mål: atleten som bærer pucken (lederen) og lagkameratene som hjelper ham, bør oppføre seg annerledes. Lederen må nå motstanders mål, og lagkameratene skal hjelpe ham på veien.

Dette kan implementeres ved å sjekke om atleten som kjører koden, har pucken:

klasse idrettsutøver // (...) privat funksjon angrep (): void var aPuckOwner: Athlete = getPuckOwner (); // Har pucken en eier? hvis (aPuckOwner! = null) // Ja, den har. La oss finne ut om eieren tilhører motstandernes lag. hvis (doesMyTeamHaveThePuck ()) if (amIThePuckOwner ()) // Mitt team har pucken og jeg er den som har det! La oss bevege // mot motstanderens mål. mBoid.steering = mBoid.steering + mBoid.seek (getOpponentGoalPosition ());  ellers // Mitt lag har pucken, men en lagkamerat har det. La oss bare følge ham // for å gi litt støtte under angrepet. mBoid.steering = mBoid.steering + mBoid.followLeader (aPuckOwner.boid); mBoid.steering = mBoid.steering + mBoid.separation ();  else // Motstanderen har pucken! Stopp angrepet // og prøv å stjele det. mBrain.popState (); mBrain.pushState (stealPuck);  ellers // Puck har ingen eier, så det er ikke noe poeng å beholde // angripe. Det er på tide å organisere og begynne å forfølge pucken. mBrain.popState (); mBrain.pushState (pursuePuck); 

Hvis amIThePuckOwner () avkastning ekte (linje 10), atleten som kjører koden, har pucken. I så fall vil han bare søke motstanderens målposisjon. Det er ganske mye den samme logikken som brukes til å forfølge pucken i pursuePuck stat.

Hvis amIThePuckOwner () avkastning falsk, Atleten har ikke pucken, så han må hjelpe lederen. Å hjelpe lederen er en komplisert oppgave, så vi vil forenkle det. En idrettsutøver vil hjelpe lederen bare ved å søke en stilling foran ham:

Lagkamerater assisterer lederen.

Som lederen beveger seg, blir han omgitt av lagkamerater som de følger fremover punkt. Dette gir lederen noen muligheter til å passere pucken til hvis det er noen problemer. Som i et ekte spill, bør de omkringliggende lagkameratene også holde seg utenfor lederens måte.

Dette hjelpemønsteret kan oppnås ved å legge til en litt endret versjon av lederens oppførsel (linje 18). Den eneste forskjellen er at idrettsutøvere vil følge et poeng fremover av lederen, i stedet for en bak ham som opprinnelig ble implementert i den oppførselen.

Idrettsutøvere som hjelper lederen bør også holde en minimumsavstand mellom hverandre. Det er implementert ved å legge til en separasjonskraft (linje 19).

Resultatet er et lag som er i stand til å bevege seg mot motstandernes mål uten å samle seg og samtidig simulere en assistert angrepbevegelse:

Forbedre angrepsstøtten

Den nåværende implementeringen av angrep staten er god nok til noen situasjoner, men det har en feil. Når noen fanger pucken, blir han leder og blir umiddelbart etterfulgt av lagkamerater.

Hva skjer hvis lederen beveger seg mot sitt eget mål når han fanger pucken? Ta en nærmere titt på demoen over og legg merke til det unaturlige mønsteret når lagkameratene begynner å følge lederen.

Når lederen fanger pucken, tar søkeregraden litt tid til å rette lederens bane og effektivt få ham til å bevege seg mot motstanderens mål. Selv når lederen er "manøvrering", vil lagkamerater prøve å søke hans fremover punkt, noe som betyr at de vil bevege seg mot deres egen mål (eller stedet som lederen stirrer på).

Når lederen er endelig på plass og klar til å bevege seg mot motstanderens mål, vil lagkameratene "manøvrere" for å følge lederen. Lederen vil da flytte uten lagkameratstøtte så lenge de andre justerer sine baner.

Denne feilen kan løses ved å sjekke om lagkameraten er foran lederen når laget gjenoppretter pucken. Her betyr tilstanden "framover" "nærmere motstanderens mål":

klasse idrettsutøver // (...) privat funksjon isAheadOfMe (theBoid: Boid): Boolsk var aTargetDistance: Number = avstand (getOpponentGoalPosition (), theBoid); var aMyDistance: Number = distance (getOpponentGoalPosition (), mBoid.position); returnere aTargetDistance <= aMyDistance;  private function attack() :void  var aPuckOwner :Athlete = getPuckOwner(); // Does the puck have an owner? if (aPuckOwner != null)  // Yeah, it has. Let's find out if the owner belongs to the opponents team. if (doesMyTeamHaveThePuck())  if (amIThePuckOwner())  // My team has the puck and I am the one who has it! Let's move // towards the opponent's goal. mBoid.steering = mBoid.steering + mBoid.seek(getOpponentGoalPosition());  else  // My team has the puck, but a teammate has it. Is he ahead of me? if (isAheadOfMe(aPuckOwner.boid))  // Yeah, he is ahead of me. Let's just follow him to give some support // during the attack. mBoid.steering = mBoid.steering + mBoid.followLeader(aPuckOwner.boid); mBoid.steering = mBoid.steering + mBoid.separation();  else  // Nope, the teammate with the puck is behind me. In that case // let's hold our current position with some separation from the // other, so we prevent crowding. mBoid.steering = mBoid.steering + mBoid.separation();    else  // The opponent has the puck! Stop the attack // and try to steal it. mBrain.popState(); mBrain.pushState(stealPuck);   else  // Puck has no owner, so there is no point to keep // attacking. It's time to re-organize and start pursuing the puck. mBrain.popState(); mBrain.pushState(pursuePuck);   

Hvis lederen (hvem er puckeieren) er foran atleten som kjører koden, bør atleten følge lederen som han gjorde før (linjene 27 og 28). Hvis lederen er bak ham, skal idrettsutøveren beholde sin nåværende posisjon, idet han holder en minimumsavstand mellom de andre (linje 33).

Resultatet er litt mer overbevisende enn det første angrep gjennomføring:

Tips: Ved å justere avstandsberegningene og sammenligningene i isAheadOfMe () Metode, det er mulig å endre måten atleter holder sine nåværende posisjoner på.

Stealing the Puck

Den endelige tilstanden i den angripende prosessen er stealPuck, som blir aktiv når motstanderne har pucken. Hovedformålet med stealPuck tilstand er å stjele pucken fra motstanderen som bærer den, slik at laget kan begynne å angripe igjen:

 StealPuck-tilstanden og overgangene i FSM som beskriver angrepsprosessen.

Siden ideen bak denne tilstanden er å stjele pucken fra motstanderen, hvis pucken blir gjenopprettet av laget eller det blir gratis (det vil si det har ingen eier), stealPuck vil dukke opp fra hjernen og presse den riktige staten for å håndtere den nye situasjonen:

klasse idrettsutøver // (...) privat funksjon stealPuck (): void // Har pucken noen eier? hvis (getPuckOwner ()! = null) // Ja, den har, men hvem har den? hvis (doesMyTeamHaveThePuck ()) // Mitt team har pucken, så det er på tide å slutte å prøve å stjele // pucken og begynne å angripe. mBrain.popState (); mBrain.pushState (angrep);  else // En motstander har pucken. var aOpponentLeader: Atlet = getPuckOwner (); // La oss forfølge ham mens han mantaining en viss adskillelse fra // de andre for å unngå at alle vil få den samme // posisjonen i jakten. mBoid.steering = mBoid.steering + mBoid.pursuit (aOpponentLeader.boid); mBoid.steering = mBoid.steering + mBoid.separation ();  ellers // Pucken har ingen eier, det løper sannsynligvis fritt i rinken. // Det er ikke noe poeng å fortsette å prøve å stjele det, så la oss fullføre 'stealPuck'-tilstanden // og bytte til' pursuePuck '. mBrain.popState (); mBrain.pushState (pursuePuck); 

Hvis pucken har en eier og han tilhører motstanders lag, må utøveren forfølge motstandsføreren og prøve å stjele pucken. For å forfølge motstanderens leder må en idrettsutøver spå hvor han vil være i nær fremtid, så han kan bli oppfanget i sin bane. Det er forskjellig fra bare å søke motstanderens leder.

Heldigvis kan dette lett oppnås med forfølgelsesadferd (linje 19). Ved å bruke en forfølgelsesstyrke i stealPuck stat, vil idrettsutøvere forsøke å avskjære motstanderens leder, i stedet for bare å følge ham:

Forhindre en overfylt stjelebevegelse

Den nåværende gjennomføringen av stealPuck virker, men i et ekte spill nærmer bare en eller to idrettsutøvere motstandsføreren å stjele pucken. Resten av laget forblir i de omkringliggende områdene og prøver å hjelpe, noe som forhindrer et overfylt stjele mønster.

Det kan løses ved å legge til en avstandskontroll (linje 17) før motstanderens lederjakt:

klasse idrettsutøver // (...) privat funksjon stealPuck (): void // Har pucken noen eier? hvis (getPuckOwner ()! = null) // Ja, den har, men hvem har den? hvis (doesMyTeamHaveThePuck ()) // Mitt team har pucken, så det er på tide å slutte å prøve å stjele // pucken og begynne å angripe. mBrain.popState (); mBrain.pushState (angrep);  else // En motstander har pucken. var aOpponentLeader: Atlet = getPuckOwner (); // Er motstanderen med pucken nær meg? hvis (avstand (aOpponentLeader, dette) < 150)  // Yeah, he is close! Let's pursue him while mantaining a certain // separation from the others to avoid that everybody will ocuppy the same // position in the pursuit. mBoid.steering = mBoid.steering.add(mBoid.pursuit(aOpponentLeader.boid)); mBoid.steering = mBoid.steering.add(mBoid.separation(50));  else  // No, he is too far away. In the future, we will switch // to 'defend' and hope someone closer to the puck can // steal it for us. // TODO: mBrain.popState(); // TODO: mBrain.pushState(defend);    else  // The puck has no owner, it is probably running freely in the rink. // There is no point to keep trying to steal it, so let's finish the 'stealPuck' state // and switch to 'pursuePuck'. mBrain.popState(); mBrain.pushState(pursuePuck);   

I stedet for å blinde forfølge motstandernes leder, vil en idrettsutøver kontrollere om avstanden mellom ham og motstandslederen er mindre enn å si, 150. Hvis det er det ekte, Forfølgelsen skjer normalt, men hvis avstanden er større enn 150, det betyr at utøveren er for langt fra motstandslederen.

Hvis det skjer, er det ikke noe poeng i å fortsette å prøve å stjele pucken, siden det er for langt unna, og det er nok lagkameratene som allerede er på plass, prøver å gjøre det samme. Det beste alternativet er å pop stealPuck fra hjernen og trykk på forsvar state (som forklares i neste opplæring). For nå vil en idrettsutøver bare holde sin nåværende stilling hvis motstandslederen er for langt unna.

Resultatet er et mer overbevisende og naturlig stjele mønster (ingen crowding):

Unngå motstandere mens du angriper

Det er et siste triks som idrettsutøvere må lære for å angripe effektivt. Akkurat nå flytter de mot motstandernes mål uten å vurdere motstanderne underveis. En motstander må sees som en trussel, og bør unngås.

Ved å bruke kollisjonssvindingsadferd, kan idrettsutøvere unngå motstandere mens de beveger seg:

Kollisjonssvindingsadferd som brukes til å unngå motstandere.

Motstandere vil bli sett på som sirkulære hindringer. Som et resultat av den dynamiske naturen til styreadferd, som oppdateres i hver spillsløyfe, vil unnfangelsesmønsteret ynkelig og jevnt arbeide for å bevege seg hindringer (som er tilfelle her).

For å gjøre atleter unngå motstandere (hindringer), må en enkelt linje legges til angrepstilstanden (linje 14):

klasse idrettsutøver // (...) privat funksjon angrep (): void var aPuckOwner: Athlete = getPuckOwner (); // Har pucken en eier? hvis (aPuckOwner! = null) // Ja, den har. La oss finne ut om eieren tilhører motstandernes lag. hvis (doesMyTeamHaveThePuck ()) if (amIThePuckOwner ()) // Mitt team har pucken og jeg er den som har det! La oss bevege // mot motstanders mål, og avod noen motstandere underveis. mBoid.steering = mBoid.steering + mBoid.seek (getOpponentGoalPosition ()); mBoid.steering = mBoid.steering + mBoid.collisionAvoidance (getOpponentTeam (). medlemmer);  ellers // Mitt lag har pucken, men en lagkamerat har det. Er han foran meg? hvis (isAheadOfMe (aPuckOwner.boid)) // Ja, han er foran meg. La oss bare følge ham for å gi litt støtte // under angrepet. mBoid.steering = mBoid.steering + mBoid.followLeader (aPuckOwner.boid); mBoid.steering = mBoid.steering + mBoid.separation ();  ellers // nei, lagkameraten med pucken er bak meg. I så fall // la oss beholde vår nåværende posisjon med litt separasjon fra // andre, så vi forhindrer trengsel. mBoid.steering = mBoid.steering + mBoid.separation ();  else // Motstanderen har pucken! Stopp angrepet // og prøv å stjele det. mBrain.popState (); mBrain.pushState (stealPuck);  ellers // Puck har ingen eier, så det er ikke noe poeng å beholde // angripe. Det er på tide å organisere og begynne å forfølge pucken. mBrain.popState (); mBrain.pushState (pursuePuck); 

Denne linjen vil legge til en kollisjonskamp mot atleten, som vil bli kombinert med de kreftene som allerede eksisterer. Som et resultat vil utøveren unngå hindringer samtidig som han søker motstanders mål.

Nedenfor er en demonstrasjon av en atlet som kjører angrep stat. Motstandere er ubøyelige for å markere kollisjonen unngår atferd:

Konklusjon

Denne opplæringen forklarte gjennomføringen av angrepsmønsteret som brukes av idrettsutøvere til å stjele og bære pucken mot motstandernes mål. Ved å bruke en kombinasjon av styreadferd, er idrettsutøvere nå i stand til å utføre komplekse bevegelsesmønstre, for eksempel å følge en leder eller forfølge motstanderen med pucken.

Som tidligere diskutert, har angrepsimplementasjonen til hensikt å simulere hvilke mennesker gjøre, så resultatet er en tilnærming av et ekte spill. Ved å tilpasse de statene som komponerer angrepet, kan du produsere en bedre simulering, eller en som passer til dine behov.

I den neste opplæringen lærer du hvordan å gjøre idrettsutøvere forsvare. AI vil bli funksjonskomplett, i stand til å angripe og forsvare, noe som resulterer i en kamp med 100% AI-kontrollerte lag som spiller mot hverandre.

referanser

  • Sprite: Hockey Stadium på GraphicRiver
  • Sprites: Hockeyspillere av Taylor J Glidden