Creați un joc de hochei AI utilizând comportamentele de conducere Fundația

Există modalități diferite de a face un anumit joc. De obicei, un dezvoltator alege ceva care se potrivește cu abilitățile sale, folosind tehnicile pe care deja le știe să producă cel mai bun rezultat posibil. Uneori, oamenii nu știu încă că au nevoie de o anumită tehnică - poate chiar mai ușoară și mai bună - pur și simplu pentru că știu deja o modalitate de a crea acel joc. 

În această serie de tutoriale, veți învăța cum să creați inteligența artificială pentru un joc de hochei utilizând o combinație de tehnici, cum ar fi comportamentele de direcție, pe care le-am explicat anterior ca concepte.

Notă: Deși acest tutorial este scris folosind AS3 și Flash, ar trebui să puteți utiliza aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului.


Introducere

Hocheiul este un sport distractiv și popular și, ca joc video, încorporează multe subiecte de tip gamedev, cum ar fi modelele de mișcare, lucrul în echipă (atac, apărare), inteligența artificială și tactici. Un joc de hochei care se joacă este foarte potrivit pentru a demonstra combinația unor tehnici utile.

Pentru a simula mecanicul de hochei, cu sportivii care rulează și se mișcă, este o provocare. Dacă modelele de mișcare sunt predefinite, chiar și cu căi diferite, jocul devine previzibil (și plictisitor). Cum putem implementa un astfel de mediu dinamic, menținând în același timp controlul asupra a ceea ce se întâmplă? Răspunsul este: utilizarea comportamentelor de direcție.

Conducerile de orientare urmăresc să creeze modele reale de mișcare cu navigație improvizată. Ele se bazează pe forțe simple care sunt combinate fiecare actualizare de joc, astfel încât acestea sunt extrem de dinamice prin natura lor. Acest lucru le face alegerea perfectă pentru a implementa ceva atât de complex și dinamic ca un joc de hochei sau de fotbal.

Scopul lucrării

Din motive de timp și de predare, să reducem puțin scopul gamei. Jocul nostru de hochei va urma doar un mic set de reguli originale ale sportului: în jocul nostru nu vor fi penalizări și nici gărzi, astfel încât fiecare sportiv se poate deplasa în jurul patinoarului:

Joc de hochei folosind reguli simplificate.

Fiecare gol va fi înlocuit de un mic "perete" fără plasă. Pentru a înscrie, o echipă trebuie să deplaseze pucul (discul) pentru al face să atingă orice parte a țelului adversarului. Când cineva scoruri, ambele echipe se vor reorganiza, iar pucul va fi plasat în centru; meciul va reporni câteva secunde după aceea.

În ceea ce privește manevrarea pucului: dacă un atlet, spune A, are pucul și este atins de un adversar, spune B, atunci B câștigă pucul și A devine imobil pentru câteva secunde. Dacă pucul va părăsi vreodată patinoarul, acesta va fi plasat imediat în centrul pantofului.

Voi folosi motorul de joc Flixel pentru a avea grijă de partea grafică a codului. Cu toate acestea, codul motorului va fi simplificat sau omis în exemple, pentru a păstra focalizarea asupra jocului propriu-zis.

Structurarea mediului

Să începem cu mediul de joc, care este compus dintr-un patinoar, un număr de sportivi și două goluri. Patinoarul este alcătuit din patru dreptunghiuri amplasate în jurul zonei de gheață; aceste dreptunghiuri se vor ciocni cu tot ceea ce le atinge, deci nimic nu va părăsi zona de gheață.

Un atlet va fi descris de către Atlet clasă:

clasa publica sportiv private var mBoid: Boid; // controlează comportamentul de direcție privat var mId: int; // un identificator unic pentru functia publica athelete publica atlet (numarul, numarul, numarul, numarul, numarul total): mBoid = noul Boid (ThePosX, ThePosY, TotalTime);  public function update (): void // Elimina toate fortele de directie mBoid.steering = null; // Umblați în jurul valorii de wanderInTheRink (); // Actualizați toate chestiile de direcție mBoid.update ();  funcția privată wanderInTheRink (): void var aRinkCenter: Vector3D = getRinkCenter (); // Dacă distanța de la centru este mai mare de 80, // mișcați înapoi spre centru, altfel păstrați rătăcirea. dacă (Utils.distance (aceasta, aRinkCenter)> = 80) mBoid.steering = mBoid.steering + mBoid.seek (aRinkCenter);  altfel mBoid.steering = mBoid.steering + mBoid.wander (); 

Proprietatea mBoid este o instanță a Boid clasă, o încapsulare a logicii matematice utilizate în seria comportamentelor de direcție. mBoid exemplu, are, printre alte elemente, vectori de matematică care descriu direcția curentă, forța de direcție și poziția entității.

Actualizați() metodă în Atlet clasa va fi invocată de fiecare dată când jocul se actualizează. Deocamdată, curăță doar forța de direcție activă, adaugă o forță de rătăcire și în cele din urmă cheamă mBoid.update (). Comanda anterioară actualizează toate logica comportamentului direcției încapsulate înăuntru mBoid, făcând mișcarea atletului (utilizând integrarea lui Euler).

Se va numi clasa de joc, care este responsabilă pentru jocul de joc PlayState. Ea are patinoarul, două grupuri de atleți (câte un grup pentru fiecare echipă) și două goluri:

clasa publica PlayState private var mAthletes: FlxGroup; privat var mRightGoal: Obiectiv; privat var mLeftGoal: Obiectiv; funcția publică create (): void // Aici totul este creat și adăugat pe ecran.  suprascrie actualizarea funcției publice (): void // face coliziunea patinoarului cu coliziunea sportivilor (mRink, mAthletes); // Asigurați-vă că toți sportivii vor rămâne în interiorul patinoarului. applyRinkContraints ();  funcția privată applyRinkContraints (): void // verificați dacă sportivii se află în limitele jocului. 

Presupunând că la meci a fost adăugat un singur atlet, rezultatul de mai jos este totul până acum:

Urmând cursorul mouse-ului

Atletul trebuie să urmeze cursorul mouse-ului, astfel încât jucătorul să poată controla ceva. Deoarece cursorul mouse-ului are o poziție pe ecran, acesta poate fi folosit ca destinație pentru comportamentul de sosire.

Comportamentul de sosire va face un atlet să caute poziția cursorului, să încetinească încet viteza pe măsură ce se apropie de cursor și, eventual, să se oprească acolo. 

În Atlet clasa, să înlocuim metoda rătăcirii cu comportamentul de sosire:

clasa publică Athlete // (...) funcția publică funcțională (): void // Șterge toate forțele de direcție mBoid.steering = null; // Atletul este controlat de jucator, // asa ca urmeaza cursorul mouse-ului. followMouseCursor (); // Actualizați toate chestiile de direcție mBoid.update ();  funcția privată followMouseCursor (): void var aMouse: Vector3D = getMouseCursorPosition (); mBoid.steering = mBoid.steering + mBoid.arrive (aMouse, 50); 

Rezultatul este un atlet care poate folosi cursorul mouse-ului. Deoarece logica de mișcare se bazează pe comportamentele de direcție, sportivii navighează pe patinoar într-un mod convingător și neted. 

Utilizați cursorul mouse-ului pentru a ghida sportivul în demo-ul de mai jos:

Adăugarea și controlul puckului

Pucul va fi reprezentat de clasă Pucul. Cele mai importante părți sunt Actualizați() și metoda mOwner proprietate:

clasa publică Puck public var velocity: Vector3D; pozitie publică var: Vector3D; privat var mOwner: sportiv; // atletul care poartă în prezent pucul. funcția publică setOwner (proprietarul: atlet): void if (mOwner! = theOwner) mOwner = theOwner; viteza = null;  funcția publică funcția (): void  funcția publică obține proprietar (): Atlet return mOwner; 

Urmând aceeași logică a atletului, pucul Actualizați() metoda va fi invocată de fiecare dată când jocul se actualizează. mOwner proprietatea determină dacă pucul este în posesia oricărui atlet. Dacă mOwner este nul, inseamna ca pucul este "liber" si se va misca in jurul acestuia, eventual renuntand la plimbarile de la patinoar.

Dacă mOwner nu este nul, înseamnă că pucul este purtat de un atlet. În acest caz, acesta va ignora orice verificări de coliziune și va fi plasat cu forța înaintea sportivului. Acest lucru se poate realiza folosind atletul viteză vector, care se potrivește și direcției atletului:

Explicarea modului în care pucul este plasat în fața sportivului.

înainte vector este o copie a atletului viteză vector, astfel încât acestea să indice în aceeași direcție. După înainte este normalizată, poate fi scalată de orice valoare - de exemplu, 30-pentru a controla cât de departe pucul va fi plasat înaintea sportivului.

În cele din urmă, pucul e poziţie primește atletul poziţie adăugat la înainte, plasarea pucului în poziția dorită. 

Mai jos este codul pentru toate acestea:

clasa publică Puck // (...) funcția privată placeAheadOfOwner (): void var ahead: Vector3D = mOwner.boid.velocity.clone (); înainte = normalizare (înainte) * 30; pozitie = mOwner.boid.position + ahead;  suprascrie actualizarea funcției publice (): void if (mOwner! = null) placeAheadOfOwner ();  // (...)

În PlayState clasa, există un test de coliziune pentru a verifica dacă pucul se suprapune peste orice atlet. Dacă da, atletul care a atins pucul devine noul său proprietar. Rezultatul este un puc care "se lipeste" de atlet. În demonstrația de mai jos, ghidează atletul să atingă pucul din centrul patinoarului pentru a vedea acest lucru în acțiune:


La lovit pe Puck

Este timpul să facem pucul să se miște ca urmare a faptului că a fost lovit de băț. Indiferent de atletul care transportă pucul, tot ceea ce este necesar pentru a simula un hit de stick este de a calcula un nou vector de viteză. Această viteză nouă va muta pucul spre destinația dorită.

Un vector de viteză poate fi generat de un vector de poziție de la altul; vectorul nou generat va trece de la o poziție la alta. Asta este exact ceea ce este necesar pentru a calcula vectorul de viteză nou al pucului după o lovitură:

Calcularea vitezei noi a pucului după o lovitură de la băț.

În imaginea de mai sus, punctul de destinație este cursorul mouse-ului. Poziția actuală a pucului poate fi folosită ca punct de plecare, în timp ce punctul în care pucul ar trebui să fie după ce a fost lovit de baston poate fi folosit ca punct final. 

Pseudo-codul de mai jos arată implementarea goFromStickHit (), o metodă în Pucul clasa care implementează logica ilustrată în imaginea de mai sus:

clasa publică Puck // (...) funcția publică goFromStickHit (atelierul: atlet, destinația: Vector3D, viteza: numărul = 160): void // plasați pucul înaintea proprietarului pentru a preveni traiectoriile neașteptate // atlet care tocmai a lovit-o) placeAheadOfOwner (); // marchează pucul drept free (no owner) setOwner (null); // Calculați noua viteză a puckului var new_velocity: Vector3D = Destinația - poziție; velocity = normalize (new_velocity) * theSpeed; 

new_velocity vectorul trece de la poziția actuală a pucului la țintă (destinatia). După aceasta, este normalizată și scalată de viteza, care definește magnitudinea (lungimea) lui new_velocity. Această operație, cu alte cuvinte, definește cât de repede pucul se va deplasa de la poziția sa actuală la destinație. În cele din urmă, pucul e viteză vector este înlocuit cu new_velocity.

În PlayState de clasă, goFromStichHit () metoda este invocată de fiecare dată când jucătorul face clic pe ecran. Când se întâmplă, cursorul mouse-ului este folosit ca destinație pentru lovitură. Rezultatul este văzut în această demonstrație:

Adăugând A.I.

Până acum, am avut doar un singur atlet care se mișca în jurul patinoarului. Pe măsură ce sunt adăugați mai mulți sportivi, AI trebuie pus în aplicare pentru a face ca toți acești sportivi să arate că sunt vii și gândesc.

Pentru a realiza acest lucru, vom folosi o mașină de stat finită bazată pe stack (FSM bazată pe stack, pe scurt). După cum sa descris anterior, FSM-urile sunt versatile și utile pentru implementarea AI în jocuri. 

Pentru jocul nostru de hochei, o proprietate numită mBrain vor fi adăugate la Atlet clasă:

clasa publica Athlete // (...) privat var mBrain: StackFSM; // controlează funcția publică AI Sportivul (numărul, numărul, numărul, numărul total, numărul) // (...) mBrain = nou StackFSM ();  // (...)

Această proprietate este o instanță de StackFSM, o clasă utilizată anterior în tutorialul FSM. Utilizează o stivă pentru a controla stările AI ale unei entități. Fiecare stat este descris ca o metodă; când o stare este împinsă în stivă, ea devine activ și se numește în timpul fiecărei actualizări a jocului.

Fiecare stat va îndeplini o sarcină specifică, cum ar fi mutarea atletului spre puc. Fiecare stat este responsabil de a se termina, ceea ce înseamnă că este responsabil să se desprindă de stivă.

Atletul poate fi controlat de jucator sau de AI acum, deci Actualizați() metodă în Atlet clasa trebuie modificată pentru a verifica situația:

clasa publică Athlete // (...) funcția publică funcțională (): void // Șterge toate forțele de direcție mBoid.steering = null; dacă (mControlledByAI) // Atletul este controlat de AI. Actualizați creierul (FSM) și / / stați departe de pereții patinoarului. mBrain.update ();  altceva // Atletul este controlat de player, așa că urmează doar // cursorul mouse-ului. followMouseCursor ();  // Actualizați toate chestiile de direcție mBoid.update (); 

Dacă AI este activă, mBrain este actualizată, care invocă metoda de stare activă curentă, făcând atletul să se comporte în mod corespunzător. Dacă jucătorul este în control, mBrain este ignorat împreună, iar sportivul se mișcă în timp ce îl conduce. 

În ceea ce privește statele în care să se împrăștie creierul: pentru moment, să punem în aplicare doar două dintre ele. Un stat va permite unui atlet să se pregătească pentru un meci; când se pregătește pentru meci, un sportiv se va muta în poziția sa în patinoar și se va așeza în picioare, priviți la puc. Celălalt stat va face ca atletul să stea pur și simplu și să privească pucul.

În secțiunile următoare vom implementa aceste stări.

Starea de inactivitate

Dacă atletul este în inactiv stat, el se va opri în mișcare și se va uita la puc. Această stare este utilizată atunci când atletul este deja în poziție în patină și așteaptă ceva să se întâmple, cum ar fi începutul meciului.

Statul va fi codificat în Atlet clasa, sub inactiv() metodă:

clasa publica Athlete // (...) functie publica atlet (numarul, numarul, numarul, numarul, numarul total, numarul, grupul: FlxGroup) // (...) // Spuneti creierului ca starea curenta este inactiva mBrain.pushState (inactiv);  funcția privată idle (): void var aPuck: Puck = getPuck (); stopAndlookAt (aPuck.position);  funcția privată stopAndlookAt (thePoint: Vector3D): void mBoid.velocity = Punctul - mBoid.position; mBoid.velocity = normalizează (mBoid.velocity) * 0,01; 

Deoarece această metodă nu se încarcă din stivă, ea va rămâne activă pentru totdeauna. În viitor, acest stat va apărea pentru a face loc altor state, cum ar fi atac, dar deocamdată face acest truc.

stopAndStareAt () Metoda urmează același principiu folosit pentru a calcula viteza pucului după o lovitură. Un vector de la poziția atletului la poziția puckului este calculat prin Poziția - Poziție mBoid și folosit ca vector de viteză nou atlet.

Acest vector de viteză nou va mișca atletul spre puc. Pentru a vă asigura că sportivul nu se mișcă, vectorul este scalat 0,01 , "scăzând" lungimea sa la aproape zero. Aceasta face ca sportivul să se oprească în mișcare, dar îl ține să privească pucul.

Pregătirea pentru o potrivire

Dacă atletul este în prepareForMatch , se va deplasa spre poziția sa inițială, oprindu-se fără probleme. Poziția inițială este locul în care sportivul trebuie să fie chiar înainte de începerea meciului. Deoarece atletul ar trebui să se oprească la destinație, comportamentul de sosire poate fi folosit din nou:

clasa publica Athlete // (...) privat var mInitialPosition: Vector3D; // poziția în patinoar în care sportivul ar trebui să fie plasat public Funcționar sportiv (thePosX: Number, ThePosY: Number ,TotalMass: Number, TheTeam: FlxGroup) // (...) mInitialPosition = nou Vector3D (PosX ,PosY); // Spuneți creierului că starea actuală este "inactiv" mBrain.pushState (inactiv);  funcția privată prepareForMatch (): void mBoid.steering = mBoid.steering + mBoid.arrive (mInitialPosition, 80); // Sunt eu în poziția inițială? dacă (distanța (mBoid.position, mInitialPosition) <= 5)  // I'm in position, time to stare at the puck. mBrain.popState(); mBrain.pushState(idle);   // (… ) 

Statul folosește comportamentul de sosire pentru a muta atletul spre poziția inițială. Dacă distanța dintre atlet și poziția sa inițială este mai mică decât 5, înseamnă că atletul a ajuns în locul dorit. Când se întâmplă asta, prepareForMatch se arată din stivă și împinge inactiv, devenind noua stare activă.

Mai jos este rezultatul utilizării unui FSM bazat pe stack pentru a controla mai mulți sportivi. presa G pentru a le plasa în poziții aleatorii în patinoar, împingând prepareForMatch stat:


Concluzie

Acest tutorial a prezentat fundațiile pentru implementarea unui joc de hochei folosind comportamente de direcție și mașini de stat finite bazate pe stack. Folosind o combinație a acestor concepte, un atlet este capabil să se miște în patinoar, urmând cursorul mouse-ului. Atletul poate, de asemenea, să lovească pucul spre o destinație.

Folosind două stări și un FSM bazat pe stack, sportivii se pot reorganiza și se pot muta în poziția lor în patinoar, pregătindu-se pentru meci.

În următorul tutorial, veți învăța cum să faceți atacul sportivului, purtând pucul spre obiectiv, evitând adversarii.

Referințe

  • Sprite: Stadionul de hochei pe GraphicRiver
  • Sprites: Jucătorii de hochei de Taylor J Glidden