I tidligere innlegg i denne serien har vi fokusert på begreper bak den kunstige intelligensen vi har lært om. I denne delen vil vi pakke inn hele implementeringen til et helt spillbart hockey spill. Du lærer hvordan du legger til de manglende brikkene som kreves for å gjøre dette til et spill, for eksempel poeng, oppstart og litt spilldesign.
Nedenfor er spillet som skal implementeres ved hjelp av alle elementene som er beskrevet i denne opplæringen.
De forrige delene av denne serien fokuserte på å forklare hvordan spillet AI fungerer. Hver del detaljert et bestemt aspekt av spillet, som hvordan atleter beveger seg og hvordan angrep og forsvar blir implementert. De var basert på konsepter som styringsadferd og stakkbaserte, finite state maskiner.
For å kunne spille et fullt spillbart spill, må alle disse aspektene pakkes inn i en kjerne spillmekaniker. Det mest åpenbare valget ville være å implementere alle offisielle regler for en offisiell hockey-kamp, men det ville kreve mye arbeid og tid. La oss ta en enklere fantasy tilnærming i stedet.
Alle hockey reglene vil bli erstattet med en enkelt: Hvis du bærer pucken og blir rørt av en motstander, fryser du og knuses i en million stykker! Det vil gjøre spillet enklere å spille og moro for begge spillerne: den som bærer pucken og den som prøver å gjenopprette den.
For å forbedre denne mekanikeren legger vi til noen oppstart. De vil hjelpe spilleren til å score og gjøre spillet litt mer dynamisk.
La oss begynne med scoring systemet, ansvarlig for å bestemme hvem som vinner eller taper. En lag scorer hver gang pucken går inn i motstandernes mål.
Den enkleste måten å implementere dette på er å bruke to overlappede rektangler:
Overlappede rektangler som beskriver målområdet. Hvis pucken kolliderer med det røde rektelet, scorer laget.Det grønne rektangel representerer området okkupert av målstrukturen (rammen og nettet). Det fungerer som en solid blokk, så pucken og utøverne vil ikke kunne bevege seg gjennom den; de vil sprette tilbake.
Det røde rektangel representerer "scoringsområdet". Hvis pucken overlapper dette rektangelet, betyr det at et lag bare har skåret.
Det røde rektangelet er mindre enn det grønne, og plasseres foran det, så hvis pucken rammer målet på alle sider, men foran, vil det hoppe tilbake og ingen poeng blir lagt til:
Noen få eksempler på hvordan pucken ville oppføre seg hvis den rørte rektanglene mens de flyttet.Etter et lag scorer, må alle idrettsutøvere gå tilbake til sin opprinnelige posisjon og pucken må plasseres på rink senteret igjen. Etter denne prosessen kan kampen fortsette.
Som forklart i første del av denne serien, har alle idrettsutøvere en AI-stat som heter prepareForMatch
som vil flytte dem til den opprinnelige posisjonen, og få dem til å komme til et stopp der.
Når pucken overlapper ett av "poengsumene", fjernes en aktuelt aktiv AI-tilstand for alle utøverne og prepareForMatch
blir presset inn i hjernen. Uansett hvor idrettsutøvere er, vil de gå tilbake til sin opprinnelige posisjon etter noen få sekunder:
Siden kameraet alltid følger pucken, hvis den er direkte teleportert til rink senteret etter at noen har scoret, vil den nåværende visningen abrupt bytte, noe som ville være stygg og forvirrende.
En bedre måte å gjøre dette på er å flytte pucken jevnt mot rink senteret; Siden kameraet følger pucken, vil dette forsiktig glide utsikten fra målet til rink senteret.
Dette kan oppnås ved å endre puckens hastighetsvektor etter at den treffer et målområde. Den nye hastighetsvektoren må "skyve" pucken mot rink senteret, slik at den kan beregnes som:
var c: Vector3D = getRinkCenter (); var p: Vector3D = puck.position; var v: Vector3D = c - p; v = normalisere (v) * 100; puck.velocity = v;
Ved å trekke rink senterets posisjon fra puckens nåværende posisjon, er det mulig å beregne en vektor som peker rett mot rink senteret.
Etter normalisering av denne vektoren, kan den skaleres av hvilken som helst verdi, som 100
, som styrer hvor fort pucken beveger seg mot rink senteret.
Nedenfor er et bilde med en representasjon av den nye hastighetsvektoren:
Beregning av en ny hastighetsvektor som vil flytte pucken mot rink senteret.Denne vektoren V
brukes som puckens hastighetsvektor, så pucken vil bevege seg mot rink senteret som beregnet.
For å unngå uvanlig oppførsel mens pucken beveger seg mot rink senteret, for eksempel en interaksjon med en utøver, blir pucken deaktivert under prosessen. Som en konsekvens stopper den å samhandle med idrettsutøvere og er merket som usynlig. Spilleren vil ikke se pucken flytte, men kameraet vil fortsatt følge det.
For å bestemme om pucken allerede er i posisjon, beregnes avstanden mellom den og rinksenteret under bevegelsen. Hvis det er mindre enn 10
, for eksempel er pucken nær nok til å bli plassert direkte på rink senteret og reaktivert slik at kampen kan fortsette.
Tanken bak power-ups er å hjelpe spilleren å oppnå spillets hovedmål, som skal score ved å bære pucken mot motstanderens mål.
For omfangets skyld har vårt spill bare to power-ups: Ghost Hjelp og Frykt Puck. Den førstnevnte legger til tre ekstra idrettsutøvere til spillerens lag i noen tid, mens sistnevnte gjør motstanderne flykte pucken i noen sekunder.
Power-ups blir lagt til begge lagene når noen scorer.
Siden alle idrettsutøvere lagt til av Ghost Hjelp oppstart er midlertidig, den Atlet
klassen må endres slik at en idrettsutøver kan merkes som et "spøkelse". Hvis en idrettsutøver er et spøkelse, vil det fjerne seg fra spillet etter noen få sekunder.
Nedenfor er Atlet
klasse, fremhever bare tilleggene som er laget for å imøtekomme spøkelsesfunksjonaliteten:
offentlig klasse idrettsutøver // (...) private var mGhost: boolsk; // forteller om atleten er et spøkelse (en powerup som legger til nye idrettsutøvere for å hjelpe med å stjele pucken). privat var mGhostCounter: Number; // teller tiden et spøkelse vil forbli aktiv offentlig funksjon Atlet (thePosX: Number, thePosY: Number, theTotalMass: Number) // (...) mGhost = false; mGhostCounter = 0; // (...) offentlig funksjon setGhost (theStatus: Boolean, theDuration: Number): void mGhost = theStatus; mGhostCounter = theDuration; offentlig funksjon amIAGhost (): Boolean return mGhost; offentlig funksjon oppdatering (): void // (...) // Oppdater oppstart tellere og stuff updatePowerups (); // (...) offentlig funksjon updatePowerups (): void // TODO.
Eiendommen mGhost
er en boolsk som forteller om atleten er et spøkelse eller ikke, mens mGhostCounter
inneholder hvor mange sekunder utøveren skal vente før han fjerner seg fra spillet.
Disse to egenskapene brukes av updatePowerups ()
metode:
privat funksjon updatePowerups (): void // Hvis utøveren er et spøkelse, har det en teller som styrer // når den må fjernes. hvis (amIAGhost ()) mGhostCounter - = time_elapsed; hvis (mGhostCounter <= 2) // Make athlete flicker when it is about to be removed. flicker(0.5); if (mGhostCounter <= 0) // Time to leave this world! (again) kill();
De updatePowerups ()
metode, kalt innen utøveren Oppdater()
rutine, vil håndtere all oppstartsprosessering i utøveren. Akkurat nå alt det gjør er å kontrollere om nåværende utøver er et spøkelse eller ikke. Hvis det er, da mGhostCounter
Eiendommen reduseres med tiden som er gått siden siste oppdatering.
Når verdien av mGhostCounter
når null, betyr det at den midlertidige atleten har vært aktiv for lenge nok, så det må fjerne seg fra spillet. For å gjøre spilleren oppmerksom på det, vil atleten begynne å blinke de siste to sekundene før han forsvinner.
Endelig er det på tide å gjennomføre prosessen med å legge til de midlertidige idrettsutøvere når oppstart er aktivert. Det utføres i powerupGhostHelp ()
metode, tilgjengelig i hovedspilllogikken:
privat funksjon powerupGhostHelp (): void var aAlete: Atlet; for (var jeg: int = 0; i < 3; i++) // Add the new athlete to the list of athletes aAthlete = addAthlete(RINK_WIDTH / 2, RINK_HEIGHT - 100); // Mark the athlete as a ghost which will be removed after 10 seconds. aAthlete.setGhost(true, 10);
Denne metoden iterates over en løkke som tilsvarer mengden av midlertidige idrettsutøvere som legges til. Hver ny idrettsutøver er lagt til bunnen av rinken og merket som et spøkelse.
Som tidligere beskrevet vil spøkelsesutøvere fjerne seg fra spillet.
De Frykt Puck oppstart gjør alle motstandere flyr pucken i noen sekunder.
Akkurat som Ghost Hjelp oppstart, Atlet
klassen må endres for å imøtekomme den funksjonaliteten:
offentlig klasse idrettsutøver // (...) privat var mFearCounter: nummer; // teller tiden som idrettsutøveren skulle unngå fra pucken (når fryktoppstart er aktiv). Offentlig funksjon Atlet (thePosX: Number, thePosY: Number, theTotalMass: Number) // (...) mFearCounter = 0; // (...) offentlig funksjon fearPuck (theDuration: Number = 2): void mFearCounter = theDuration; // Returnerer sant hvis mFearCounter har en verdi, og atleten // er ikke inaktiv eller forbereder seg til en kamp. privat funksjon shouldIEvadeFromPuck (): Boolsk return mFearCounter> 0 && mBrain.getCurrentState ()! = tomgang && mBrain.getCurrentState ()! = prepareForMatch; privat funksjon updatePowerups (): void if (mFearCounter> 0) mFearCounter - = elapsed_time; // (...) offentlig funksjon oppdatering (): void // (...) // Oppdater oppstart tellere og ting updatePowerups (); // Hvis utøveren er en AI-kontrollert motstander hvis (amIAnAiControlledOpponent ()) // Sjekk om "frykt for pucken" oppstart er aktiv. // Hvis det er sant, unngå fra pucken. hvis (shouldIEvadeFromPuck ()) evadeFromPuck (); // (...) offentlig funksjon evadeFromPuck (): void // TODO
Først updatePowerups ()
Metoden endres for å redusere mFearCounter
eiendom, som inneholder hvor lang tid utøveren skal unngå pucken. De mFearCounter
Eiendommen endres hver gang metoden fearPuck ()
er kalt.
I Atlet
's Oppdater()
metode, en test er lagt til for å sjekke om oppstart skal skje. Hvis utøveren er en motstander kontrollert av AI (amIAnAiControlledOpponent ()
avkastning ekte
) og idrettsutøveren bør unngå pucken (shouldIEvadeFromPuck ()
avkastning ekte
også), den evadeFromPuck ()
Metoden er påkalt.
De evadeFromPuck ()
Metoden bruker evadeadferd, noe som gjør at en enhet unngår ethvert objekt og dets bane helt og holdent:
privat funksjon evadeFromPuck (): void mBoid.steering = mBoid.steering + mBoid.evade (getPuck (). getBoid ());
Alle evadeFromPuck ()
Metoden er å legge til en evade kraft til den nåværende utøverens styringskraft. Det gjør at han unngår pucken uten å ignorere de allerede tilførte styrkekreftene, som den som ble opprettet av den nåværende aktive AI-staten.
For å være evadabel må pucken oppføre seg som en boid, som alle idrettsutøvere gjør (mer informasjon om det i første del av serien). Som følge av dette må en boid eiendom, som inneholder puckens nåværende posisjon og hastighet, legges til puck
klasse:
klasse Puck // (...) private var mBoid: Boid; // (...) offentlig funksjon oppdatering () // (...) mBoid.update (); offentlig funksjon getBoid (): Boid return mBoid; // (...)
Endelig oppdaterer vi hovedspilllogikken slik at motstanderne frykter pucken når oppstart er aktivert:
privat funksjon powerupFearPuck (): void var jeg: uint, idrettsutøvere: Array = rightTeam.members, size: uint = athletes.length; for (i = 0; i < size; i++) if (athletes[i] != null) // Make athlete fear the puck for 3 seconds. athletes[i].fearPuck(3);
Metoden iterates over alle motstanders idrettsutøvere (det rette laget, i dette tilfellet), ringer til fearkPuck ()
metode for hver enkelt av dem. Dette vil utløse logikken som gjør atleterne frykt for pucken i noen få sekunder, som tidligere forklart.
Det siste tillegget til spillet er den frysende og knusende delen. Det utføres i hovedspilllogikken, hvor rutinemessig kontrollerer om atleterne til venstre lag overlapper med utøverne til det rette laget.
Denne overlappende sjekken utføres automatisk av Flixel-spillmotoren, som påkaller en tilbakeringing hver gang en overlapping er funnet:
Private Funksjon AtletesOverlapped (TheLeftAthlete: Atlet, TheRightAthlete: Atlet): void // Har pucken en eier? hvis (mPuck.owner! = null) // Ja, det gjør det. hvis (mPuck.owner == theLeftAthlete) // Pucks eier er den venstre atleten theLeftAthlete.shatter (); mPuck.setOwner (theRightAthlete); ellers hvis (mPuck.owner == theRightAthlete) // Puck eier er den rette utøveren theRightAthlete.shatter (); mPuck.setOwner (theLeftAthlete);
Denne tilbakeringingen mottar som parametre atleterne til hvert lag som overlappes. En test sjekker om puckens eier ikke er null, noe som betyr at den bæres av noen.
I så fall sammenlignes puckens eier med atletene som bare overlappes. Hvis en av dem bærer pucken (så han er puckens eier), er han knust og puckens eierskap passerer til den andre idrettsutøveren.
De knuse()
metode i Atlet
klassen vil markere atleten som inaktiv og plassere den på bunnen av rinken etter noen få sekunder. Det vil også avgi flere partikler som representerer isbit, men dette emnet vil bli dekket i et annet innlegg.
I denne opplæringen har vi implementert noen få elementer som kreves for å gjøre vår hockey prototype til et fullt spillbart spill. Jeg har med vilje lagt fokus på konseptene bak hver av elementene, i stedet for hvordan man faktisk implementerer dem i spillmotor X eller Y.
Den fryse og knuste tilnærmingen som brukes til spillet kan høres for fantastisk, men det bidrar til å holde prosjektet håndterbart. Idrettsregler er meget spesifikke, og deres gjennomføring kan være vanskelig.
Ved å legge til noen få skjermer og noen HUD-elementer, kan du lage ditt eget fullhockey-spill fra denne demoen!