Lag et Hockey Game AI ved hjelp av styringsadferd Foundation

Det er forskjellige måter å lage et bestemt spill på. Vanligvis velger en utvikler noe som passer hans ferdigheter, ved hjelp av teknikkene han allerede vet for å gi det beste resultatet mulig. Noen ganger vet folk ikke at de trenger en bestemt teknikk - kanskje enda en enklere og bedre - bare fordi de allerede vet en måte å skape det spillet på. 

I denne serien av opplæringsprogrammer lærer du hvordan du lager kunstig intelligens for et hockeyspill ved hjelp av en kombinasjon av teknikker, for eksempel styringsadferd, som jeg tidligere har forklart som begreper.

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ø.


Introduksjon

Hockey er en morsom og populær sport, og som et videospill inkorporerer det mange gamedev-emner, for eksempel bevegelsesmønster, samarbeid (angrep, forsvar), kunstig intelligens og taktikk. Et spillbart hockeyspill er en flott passform for å demonstrere kombinasjonen av noen nyttige teknikker.

For å simulere hockey mekaniker, med idrettsutøvere som kjører og beveger seg rundt, er en utfordring. Hvis bevegelsesmønstrene er forhåndsdefinerte, selv med forskjellige baner, blir spillet forutsigbart (og kjedelig). Hvordan kan vi implementere et slikt dynamisk miljø samtidig som vi opprettholder kontroll over hva som skjer? Svaret er: ved hjelp av styringsadferd.

Styringsadferdene tar sikte på å skape realistiske bevegelsesmønstre med improvisasjonsnavigering. De er basert på enkle krefter som kombineres hver spilloppdatering, så de er ekstremt dynamiske av natur. Dette gjør dem til det perfekte valget for å implementere noe så komplisert og dynamisk som en hockey eller et fotballspill.

Omfanget av arbeidet

For tiden og undervisning, la oss redusere omfanget av spillet litt. Vårt hockey spill følger bare et lite sett av sportens opprinnelige regler: i vårt spill er det ingen straff og ingen målbevisninger, så alle idrettsutøvere kan bevege seg rundt på rinken:

Hockey spill ved hjelp av forenklede regler.

Hvert mål vil bli erstattet av en liten "vegg" uten nett. For å score må et lag flytte pucken (disken) for å få den til å berøre hvilken som helst side av motstanderens mål. Når noen scorer, vil begge lagene reorganiseres, og pucken blir plassert i midten; kampen starter om noen sekunder etter det.

Når det gjelder puckhåndtering: Hvis en idrettsutøver, sier A, har pucken, og blir berørt av en motstander, sier B, så B får pucken og A blir fast i noen sekunder. Hvis pucken noen gang forlater rinken, blir den umiddelbart plassert på rink senteret.

Jeg vil bruke Flixel-spillmotor til å ta vare på den grafiske delen av koden. Motorkoden blir imidlertid forenklet eller utelatt i eksemplene, for å holde fokus på selve spillet.

Strukturering av miljøet

La oss begynne med spillmiljøet, som består av en rink, en rekke idrettsutøvere og to mål. Rinken er laget av fire rektangler plassert rundt isområdet; Disse rektanglene vil kollidere med alt som berører dem, så ingenting vil forlate isområdet.

En idrettsutøver vil bli beskrevet av Atlet klasse:

offentlig klasse idrettsutøver private var mBoid: Boid; // styrer oppførselen ting private var mId: int; // en unik identifikator for athelete offentlig funksjon Atlet (thePosX: Number, thePosY: Number, theTotalMass: Number) mBoid = new Boid (thePosX, thePosY, theTotalMass);  offentlig funksjon oppdatering (): void // Fjern alle styringskrefter mBoid.steering = null; // Vandre rundt wanderInTheRink (); // Oppdater alle styringsspørsmålene mBoid.update ();  privat funksjon wanderInTheRink (): void var aRinkCenter: Vector3D = getRinkCenter (); // Hvis avstanden fra sentrum er større enn 80, // flytt tilbake til sentrum, ellers fortsett å vandre. hvis (Utils.distance (dette, aRinkCenter)> = 80) mBoid.steering = mBoid.steering + mBoid.seek (aRinkCenter);  ellers mBoid.steering = mBoid.steering + mBoid.wander (); 

Eiendommen mBoid er en forekomst av Boid klassen, en innkapsling av matematikklogikken som brukes i styringsadferdsserien. De mBoid eksempel har blant annet elementer mattevektorer som beskriver nåværende retning, styringskraft og posisjon av enheten.

De Oppdater() metode i Atlet klassen vil bli påkalt hver gang spillet oppdateres. For nå rydder det bare en aktiv styringskraft, legger til en svingkraft, og til slutt ringer mBoid.update (). Den tidligere kommandoen oppdaterer all styringsadferdslogikken innkapslet i mBoid, gjør atleten flytte (ved hjelp av Euler integrasjon).

Spillklassen, som er ansvarlig for spillsløyfen, vil bli kalt PlayState. Den har rink, to grupper av idrettsutøvere (en gruppe for hvert lag) og to mål:

offentlig klasse PlayState private var mAthletes: FlxGroup; privat var mRightGoal: Mål; privat var mLeftGoal: Mål; offentlig funksjon lage (): void // Her er alt laget og lagt til på skjermen.  overstyre offentlig funksjon oppdatering (): void // Lag rinken kolliderer med idrettsutøvere kolliderer (mRink, mAthletes); // Sørg for at alle idrettsutøvere forblir inne i rinken. applyRinkContraints ();  privat funksjon applyRinkContraints (): void // sjekk om idrettsutøvere er innenfor rink // grenser. 

Forutsatt at en enkelt idrettsutøver ble lagt til kampen, er nedenfor resultatet av alt så langt:

Etter musemarkøren

Atleten må følge musepekeren, slik at spilleren faktisk kan kontrollere noe. Siden musepekeren har en posisjon på skjermen, kan den brukes som destinasjon for ankomstadferd.

Ankomstadferdelsen vil gjøre en idrettsutøver søker markørposisjonen, redusere hastigheten sakte når den nærmer seg markøren, og til slutt stoppe der. 

I Atlet klasse, la oss erstatte vandringsmetoden med ankomstadferansen:

offentlig klasse idrettsutøver // (...) offentlig funksjon oppdatering (): void // Fjern alle styring styrker mBoid.steering = null; // Atleten kontrolleres av spilleren, / / ​​så følg bare musemarkøren. followMouseCursor (); // Oppdater alle styringsspørsmålene mBoid.update ();  privat funksjon followMouseCursor (): void var aMouse: Vector3D = getMouseCursorPosition (); mBoid.steering = mBoid.steering + mBoid.arrive (aMouse, 50); 

Resultatet er en idrettsutøver som kan markøren. Siden bevegelseslogikken er basert på styringsadferd, navigerer idrettsutøvere på en overbevisende og jevn måte. 

Bruk musepekeren til å veilede atleten i demonstrasjonen nedenfor:

Legge til og styre Puck

Pucken vil bli representert av klassen puck. De viktigste delene er Oppdater() metode og mOwner eiendom:

offentlig klasse Puck offentlig varhastighet: Vector3D; offentlig varstilling: Vector3D; privat varemester: idrettsutøver; // atleten som bærer pucken. offentlig funksjon setOwner (theOwner: Athlete): void if (mOwner! = theOwner) mOwner = theOwner; hastighet = null;  offentlig funksjon oppdatering (): void  offentlig funksjon få eier (): idrettsutøver retur mOwner; 

Etter samme logikk av utøveren, er puckens Oppdater() Metoden vil bli påkalt hver gang spillet oppdateres. De mOwner eiendom bestemmer om pucken er i besittelse av noen idrettsutøver. Hvis mOwner er null, det betyr at pucken er "fri", og den vil bevege seg, til slutt hoppe av rinken går.

Hvis mOwner er ikke null, det betyr at pucken blir båret av en idrettsutøver. I dette tilfellet vil det ignorere eventuelle kollisjonskontroller og vil bli kraftig plassert foran idrettsutøveren. Dette kan oppnås ved å bruke idrettsutøveren hastighet vektor, som også samsvarer med utøverens retning:

Forklaring på hvordan pucken er plassert foran idrettsutøveren.

De fremover vektor er en kopi av utøveren hastighet vektor, så peker de i samme retning. Etter fremover er normalisert, det kan skaleres av noen verdi-si, 30-å kontrollere hvor langt pucken blir plassert foran idrettsutøveren.

Til slutt, puckens stilling mottar atleten stilling lagt til fremover, Plasser pucken i ønsket posisjon. 

Nedenfor er koden for alt det:

offentlig klasse Puck // (...) private funksjon stedAheadOfOwner (): void var foran: Vector3D = mOwner.boid.velocity.clone (); fremover = normaliser (fremover) * 30; posisjon = mOwner.boid.position + foran;  overstyre offentlig funksjon oppdatering (): void if (mOwner! = null) placeAheadOfOwner ();  // (...)

I PlayState klasse, det er en kollisjonstest for å sjekke om pucken overlapper enhver idrettsutøver. Hvis det gjør det, blir idrettsutøveren som nettopp rørt pucken sin nye eier. Resultatet er en puck som "stikker" til utøveren. I den nedenstående demonstrasjonen, veileder atleten å røre pucken i midten av rinken for å se dette i aksjon:


Slår på Puck

Det er på tide å få pucken til å bevege seg som et resultat av å bli rammet av pinnen. Uansett atleten som bærer pucken, er alt som kreves for å simulere en treff av pinnen, å beregne en ny hastighetsvektor. Den nye hastigheten vil flytte pucken til ønsket destinasjon.

En hastighetsvektor kan genereres av en positionsvektor fra en annen; Den nylig genererte vektoren vil da gå fra en posisjon til en annen. Det er akkurat det som trengs for å beregne puckens nye hastighetsvektor etter et treff:

Beregning av puckens nye hastighet etter et slag fra pinnen.

I bildet over er målpunktet musemarkøren. Puckens nåværende posisjon kan brukes som utgangspunkt, mens poenget hvor pucken skal være etter at den har blitt rammet av pinnen, kan brukes som sluttpunkt. 

Pseudokoden nedenfor viser implementeringen av goFromStickHit (), en metode i puck klasse som implementerer logikken illustrert i bildet ovenfor:

offentlig klasse Puck // (...) offentlig funksjon goFromStickHit (theAthlete: Atlet, theDestination: Vector3D, theSpeed: Number = 160): void // Plasser pucken foran eieren for å hindre uventede baner // (f.eks. idrettsutøver som bare slo det) placeAheadOfOwner (); // Marker pucken som gratis (ingen eier) setOwner (null); // Beregn puckens nye hastighet var new_velocity: Vector3D = theDestination - posisjon; hastighet = normalisere (new_velocity) * theSpeed; 

De new_velocity vektoren går fra puckens nåværende posisjon til målet (målet). Etter det blir det normalisert og skalert av farten, som definerer størrelsen (lengden) på new_velocity. Denne operasjonen, med andre ord, definerer hvor fort pucken vil bevege seg fra sin nåværende stilling til målet. Til slutt, puckens hastighet vektor er erstattet av new_velocity.

I PlayState klasse, goFromStichHit () Metoden påberopes hver gang spilleren klikker på skjermen. Når det skjer, brukes musepekeren som mål for målet. Resultatet er sett i denne demonstrasjonen:

Legge til A.I.

Så langt har vi bare hatt en enkelt idrettsutøver som beveger seg rundt rinken. Som flere idrettsutøvere blir lagt til, må AI implementeres for å gjøre alle disse idrettsutøvere ser ut som de lever og tenker.

For å oppnå det, bruker vi en stabelbasert finite state machine (stabelbasert FSM, for kort). Som tidligere beskrevet, er FSMs allsidige og nyttige for å implementere AI i spill. 

For vårt hockeyspill, en eiendom som heter mBrain vil bli lagt til i Atlet klasse:

offentlig klasse idrettsutøver // (...) private var mBrain: StackFSM; // kontrollerer AI ting offentlig funksjon Atlet (thePosX: Number, thePosY: Number, theTotalMass: Number) // (...) mBrain = new StackFSM ();  // (...)

Denne egenskapen er en forekomst av StackFSM, en klasse som tidligere ble brukt i MSM-opplæringen. Den bruker en stabel for å kontrollere AI-tilstandene til en enhet. Hver stat er beskrevet som en metode; Når en stat blir skjøvet inn i stabelen, blir den den aktiv metode og kalles under hver spilloppdatering.

Hver stat vil utføre en bestemt oppgave, for eksempel å flytte utøveren mot pucken. Hver stat er ansvarlig for å slutte seg selv, noe som betyr at det er ansvarlig for å poppe seg fra stakken.

Atleten kan styres av spilleren eller av AI nå, så Oppdater() metode i Atlet klassen må endres for å sjekke denne situasjonen:

offentlig klasse idrettsutøver // (...) offentlig funksjon oppdatering (): void // Fjern alle styring styrker mBoid.steering = null; hvis (mControlledByAI) // Atleten kontrolleres av AI. Oppdater hjernen (FSM) og // hold deg unna rink vegger. mBrain.update ();  else // Atleten er kontrollert av spilleren, så følg bare // musemarkøren. followMouseCursor ();  // Oppdater alle styringsspørsmålene mBoid.update (); 

Hvis AI er aktiv, mBrain er oppdatert, som påkaller den nåværende aktive tilstandsmetoden, slik at utøveren oppfører seg tilsvarende. Hvis spilleren er i kontroll, mBrain blir ignorert alle sammen og atleten beveger seg som ledet av spilleren. 

Når det gjelder statene å skyve inn i hjernen: For nå, la oss bare implementere to av dem. En stat vil la en idrettsutøver forberede seg på en kamp; Når du forbereder kampen, vil en idrettsutøver flytte til sin posisjon i rinken og stå stille og stirre på pucken. Den andre staten vil gjøre atleten bare stå stille og stirre på pucken.

I de neste avsnittene implementerer vi disse statene.

Den tomme tilstanden

Hvis utøveren er i tomgang stat, han vil slutte å flytte og stirre på pucken. Denne tilstanden brukes når idrettsutøveren allerede er på plass i rinken og venter på at noe skal skje, som starten på kampen.

Staten vil bli kodet i Atlet klasse, under tomgang() metode:

offentlig klasse idrettsutøver // (...) offentlig funksjon Idrettsutøver (thePosX: Nummer, tallet: Nummer, theTotalMass: Number, theTeam: FlxGroup) // (...) // Fortell hjernen den nåværende tilstanden er "tomgang" mBrain.pushState (tomgang);  privat funksjon tomgang (): void var aPuck: Puck = getPuck (); stopAndlookAt (aPuck.position);  privat funksjon stopAndlookAt (thePoint: Vector3D): void mBoid.velocity = thePoint - mBoid.position; mBoid.velocity = normalisere (mBoid.velocity) * 0,01; 

Siden denne metoden ikke poper seg fra stakken, vil den forbli aktiv for alltid. I fremtiden vil denne tilstanden pope seg selv for å gjøre rom for andre stater, for eksempel angrep, men for nå gjør det kunsten.

De stopAndStareAt () Metoden følger det samme prinsippet som brukes til å beregne puckens hastighet etter et treff. En vektor fra utøverens posisjon til puckens posisjon beregnes av ThePoint - mBoid.position og brukes som utøverens nye hastighetsvektor.

Den nye hastighetsvektoren vil flytte utøveren mot pucken. For å sikre at utøveren ikke beveger seg, blir vektoren skalert av 0.01 , "krymper" lengden til nesten null. Det gjør atleten slutte å flytte, men holder ham stirre på pucken.

Forbereder for en kamp

Hvis utøveren er i prepareForMatch stat, han vil bevege seg mot sin opprinnelige posisjon, og stoppe det jevnt. Den opprinnelige posisjonen er hvor utøveren skal være like før kampen starter. Siden atleten skal stoppe ved destinasjonen, kan ankomstadferansen brukes igjen:

offentlig klasse idrettsutøver // (...) privat var mInitialPosition: Vector3D; // posisjonen i rinken hvor idrettsutøveren skal plasseres offentlig funksjon Atlet (thePosX: Nummer, tallet: Nummer, theTotalMass: Number, theTeam: FlxGroup) // (...) mInitialPosition = ny Vector3D (thePosX, thePosY); // Fortell hjernen, den nåværende tilstanden er 'tomgang' mBrain.pushState (tomgang);  privat funksjon prepareForMatch (): void mBoid.steering = mBoid.steering + mBoid.arrive (mInitialPosition, 80); // Er jeg på den første stillingen? hvis (avstand (mBoid.position, mInitialPosition) <= 5)  // I'm in position, time to stare at the puck. mBrain.popState(); mBrain.pushState(idle);   // (… ) 

Staten bruker ankomstadferd for å flytte idrettsutøveren mot startposisjonen. Hvis avstanden mellom utøveren og hans startposisjon er mindre enn 5, det betyr at utøveren har kommet til ønsket sted. Når dette skjer, prepareForMatch popper seg fra stakken og skyver tomgang, gjør den til den nye aktive tilstanden.

Nedenfor er resultatet av å bruke en stabelbasert FSM for å kontrollere flere idrettsutøvere. trykk G å plassere dem i tilfeldige stillinger i rinken, trykke på prepareForMatch stat:


Konklusjon

Denne opplæringen presenterte grunnlaget for å implementere et hockeyspill ved hjelp av styringsadferd og stakkbaserte, finite state maskiner. Ved å bruke en kombinasjon av disse konseptene, kan en idrettsutøver flytte i rinken, etter musepekeren. Atleten kan også slå pucken mot en destinasjon.

Ved å bruke to stater og en stabelbasert FSM, kan idrettsutøvere organisere og flytte til sin posisjon i rinken, forberede seg på kampen.

I neste opplæring lærer du hvordan å gjøre atleterne angrepet, og bærer pucken mot målet, samtidig som motstanderne unngås..

referanser

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