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