Intrarea jocului simplificată

Imaginați-vă un personaj de joc numit "Bob the Butcher" stând pe cont propriu într-o cameră întunecată, în timp ce hoardele de zombi de cârnați mutanți încep să toarne prin uși și ferestre sparte. În acest moment, ar fi o idee bună ca Bob să înceapă să zboare zombi de cârnați în bucăți mici de carne, dar cum va face Bob acest lucru într-un joc cross-platform? Va trebui ca jucătorul să apese una sau mai multe taste pe o tastatură, să facă clic pe mouse, să atingă ecranul sau să apese un buton de pe un jocpad?

Când programați un joc de tip cross-platform, acesta este tipul de lucru pe care probabil vă veți petrece mult timp luptând dacă nu sunteți pregătiți pentru asta. Dacă nu ești atent, poți ajunge cu masiv, spaghete dacă declarații sau intrerupator declarații pentru a face față tuturor dispozitivelor de intrare diferite.

În acest tutorial, vom face lucrurile mult mai simple, prin crearea unei singure clase care să unifice mai multe dispozitive de intrare. Fiecare instanță a clasei va reprezenta o anumită acțiune sau comportament în joc (cum ar fi "trage", "alerga" sau "săriți") și poate fi spus să asculte diferite chei, butoane și pointeri pe mai multe dispozitive de intrare.

Notă: Limba de programare utilizată în acest tutorial este JavaScript, dar tehnica utilizată pentru unificarea mai multor dispozitive de intrare poate fi transferată cu ușurință în orice limbaj de programare inter-platformă care oferă API pentru dispozitivele de intrare.

Împușcărea cârnaților

Înainte de a începe să scriem codul pentru clasa pe care o vom crea în acest tutorial, să aruncăm o privire rapidă asupra modului în care ar putea fi folosită clasa.

// Creați o intrare pentru acțiunea "trage". shoot = new GameInput (); // Spuneți la intrare la ce să reacționați. shoot.add (GameInput.KEYBOARD_SPACE); shoot.add (GameInput.GAMEPAD_RT); // În timpul fiecărei actualizări a jocului, verificați intrarea. function update () if (shoot.value> 0) // Spune Bob sa traga zombie mutante!  altfel // Spune-i lui Bob să nu mai tragă. 

GameInput este clasa pe care o vom crea, și puteți vedea cât de mult mai simplu va face lucrurile. shoot.value proprietatea este un număr și va fi o valoare pozitivă dacă spațiu bar pe o tastatură sau pe dreapta declanșator pe un jocpad este apăsat. Dacă nu se apasă nici bara de spațiu, nici declanșatorul drept, valoarea va fi zero.

Noțiuni de bază

Primul lucru pe care trebuie să-l facem este să creeze o închidere a funcției pentru GameInput clasă. Majoritatea codurilor pe care le vom scrie nu fac parte din clasă, ci trebuie să fie accesibile din interiorul clasei, rămânând ascunse de orice altceva. O închidere a funcțiilor ne permite să facem acest lucru în JavaScript.

(Într-un limbaj de programare precum ActionScript sau C #, ai putea pur și simplu să folosești membri de clasă privată, dar asta nu este un lux pe care-l avem în JavaScript, din păcate.)

(funcția () // codul merge aici) ();

Restul codului din acest tutorial va înlocui comentariul "codul merge aici".

Variabilele

Codul are nevoie doar de o serie de variabile care trebuie definite în afara funcțiilor, iar acele variabile sunt după cum urmează.

var KEYBOARD = 1; var POINTER = 2; var GAMEPAD = 3; var DEVICE = 16; var CODE = 8; var __pointer = curentX: 0, curentY: 0, anteriorX: 0, anteriorY: 0, distanceX: 0, distanceY: 0, identificator: 0, mutat: false, presat: false; var __keyboard = ; var __inputs = []; var __channels = []; var __mouseDetected = false; var __touchDetected = false;

Constanta TASTATURĂ, POINTER, GAMEPAD, DISPOZITIV și COD valorile sunt folosite pentru a defini canalele dispozitivelor de intrare, precum GameInput.KEYBOARD_SPACE, iar utilizarea lor va deveni mai clară ulterior în tutorial.

__pointer obiect conține proprietăți relevante pentru dispozitivele de intrare pe mouse și touch screen și __tastatură obiect este folosit pentru a urmări stările tastaturii tastaturii. __inputs și __channels matricele sunt folosite pentru stocare GameInput instanțe și orice canale de dispozitive de intrare adăugate la aceste instanțe. În cele din urmă, __mouseDetected și __touchDetected indicați dacă a fost detectat un mouse sau un ecran tactil.

Notă: Variabilele nu trebuie prefixate cu două subliniere; aceasta este pur și simplu convenția de codare pe care am ales să o folosesc pentru codul din acest tutorial. Aceasta ajută la separarea acestora de variabilele definite în funcții.

Funcțiile

Aici vine cea mai mare parte a codului, astfel încât poate doriți să luați o cafea sau ceva înainte de a începe să citiți această parte!

Aceste funcții sunt definite după variabilele din secțiunea anterioară a acestui tutorial și ele sunt definite în ordinea apariției.

// Inițializează sistemul de intrare. function main () // Expune constructorul GameInput. window.GameInput = GameInput; // Adăugați ascultătorii evenimentului. addMouseListeners (); addTouchListeners (); addKeyboardListeners (); // Unele acțiuni ale UI pe care ar trebui să le prevenim într-un joc. window.addEventListener ("contextmenu", killEvent, true); window.addEventListener ("selectstart", killEvent, true); // Porniți buclă de actualizare. window.requestAnimationFrame (actualizare); 

principal() funcția se numește la sfârșitul codului - adică la sfârșitul codului funcția de închidere am creat mai devreme. Ea face ceea ce spune pe tablă și face totul să meargă așa GameInput clasa poate fi utilizată.

Un lucru pe care ar trebui să-ți aduc în atenție este utilizarea requestAnimationFrame () , care face parte din specificația W3C Animation Timing. Jocuri și aplicații moderne utilizează această funcție pentru a rula buclele de actualizare sau de redare deoarece au fost foarte optimizate în acest scop în majoritatea browserelor web.

// Actualizează sistemul de intrare. funcția de actualizare () window.requestAnimationFrame (update); // Actualizați mai întâi valorile indicatoarelor. updatePointer (); var i = __inputs.length; var intra = nul; canalele var = null; în timp ce (i -> 0) input = __inputs [i]; canale = __canale [i]; dacă (input.enabled === true) updateInput (intrare, canale);  altceva input.value = 0; 

Actualizați() funcționează prin lista activă GameInput instanțe și actualizează cele activate. Următoarele updateInput () funcția este destul de lungă, deci nu voi adăuga codul aici; puteți vedea codul în întregime prin descărcarea fișierelor sursă.

// Actualizează o instanță GameInput. function updateInput (intrare, canale) // nota: vezi fișierele sursă

updateInput () funcționează la canalele dispozitivului de intrare care au fost adăugate la a GameInput exemplul și realizează ceea ce valoare proprietate a GameInput exemplu ar trebui setat la. După cum se vede în Împușcărea cârnaților exemplul codului, valoare proprietatea indică dacă se declanșează un canal de dispozitiv de intrare și care permite unui joc să reacționeze în mod corespunzător, probabil spunându-i lui Bob să tragă zombie mutante cârnați.

// Actualizează valoarea unei instanțe GameInput. funcția updateValue (intrare, valoare, prag) if (prag! == undefined) if (valoare < threshold )  value = 0;   // The highest value has priority. if( input.value < value )  input.value = value;  

updateValue () funcția determină dacă valoare proprietatea unui GameInput instanță ar trebui actualizată. prag este folosit în primul rând pentru a împiedica canalele analogice de intrare a dispozitivului, cum ar fi butoanele gamepad-ului și stick-urile, care nu se resetează corect de la declanșarea constantă a GameInput instanță. Acest lucru se întâmplă destul de des cu jocpaduri defecte sau defecte.

Ca updateInput () funcția, următoarele updatePointer () funcția este destul de lungă, deci nu voi adăuga codul aici. Puteți vedea codul în întregime prin descărcarea fișierelor sursă.

// Actualizează valorile indicatorului. funcția updatePointer () // notă: vedeți fișierele sursă

updatePointer () funcția actualizează proprietățile în __pointer obiect. Pe scurt, funcția fixează poziția pointerului pentru a vă asigura că nu părăsește fereastra de vizualizare a ferestrei browserului web și calculează distanța pe care indicatorul sa mutat de la ultima actualizare.

// Chemat când se detectează un dispozitiv de intrare a mouse-ului. funcția mouseDetected () if (__mouseDetected === false) __mouseDetected = true; // Ignorați evenimentele de atingere dacă se utilizează mouse-ul. removeTouchListeners ();  / Chemat când este detectat un dispozitiv de intrare pe ecran tactil. funcția touchDetected () if (__touchDetected === false) __touchDetected = true; // Ignorați evenimentele mouse-ului dacă se utilizează un ecran tactil. removeMouseListeners (); 

mouseDetected () și touchDetected () funcțiile indică codul să ignore un dispozitiv de intrare sau celălalt. Dacă un mouse este detectat înaintea unui ecran tactil, ecranul tactil va fi ignorat. Dacă este detectat un ecran tactil înaintea unui mouse, mouse-ul va fi ignorat.

// Apel când este apăsat un dispozitiv de intrare asemănător indicatorului. (x, y, identificator) __pointer.identifier = identificator; __pointer.pressed = true; pointerMoved (x, y);  // Apel când este lansat un dispozitiv de intrare asemănător indicatorului. funcția pointerReleased () __pointer.identifier = 0; __pointer.pressed = false;  // Apel când este mutat un dispozitiv de intrare asemănător indicatorului. funcția pointerMoved (x, y) __pointer.currentX = x >>> 0; __pointer.currentY = y >>> 0; dacă (__pointer.moved === false) __pointer.moved = true; __pointer.previousX = __pointer.currentX; __pointer.previousY = __pointer.currentY; 

pointerPressed (), pointerReleased () și pointerMoved () funcțiile de manipulare de intrare de la un mouse sau un ecran tactil. Toate cele trei funcții actualizează pur și simplu proprietățile în __pointer obiect.

După aceste trei funcții, avem o mână de funcții standard de gestionare a evenimentelor JavaScript. Funcțiile sunt explicite, deci nu voi adăuga codul aici; puteți vedea codul în întregime prin descărcarea fișierelor sursă.

// Adaugă un canal de dispozitiv de intrare într-o instanță GameInput. funcția inputAdd (intrare, canal) var i = __inputs.indexOf (intrare); dacă (i === -1) __inputs.push (intrare); __channels.push ([canal]); întoarcere;  var ca = __channels [i]; var ci = ca.indexOf (canal); dacă (ci === -1) ca.push (canal);  // Elimină un canal de dispozitiv de intrare într-o instanță GameInput. funcția de intrareRemove (intrare, canal) var i = __inputs.indexOf (intrare); dacă (i === -1) retur;  var ca = __channels [i]; var ci = ca.indexOf (canal); dacă (ci! == -1) ca.splice (ci, 1); dacă (ca.length === 0) __inputs.splice (i, 1); __channels.splice (i, 1);  // Reinițializează o instanță GameInput. funcția inputReset (intrare) var i = __inputs.indexOf (intrare); dacă (i! == -1) __inputs.splice (i, 1); __channels.splice (i, 1);  input.value = 0; input.enabled = true; 

inputAdd (), inputRemove () și inputReset () funcțiile sunt apelate de la GameInput (vezi mai jos). Funcțiile modifică __inputs și __channels în funcție de ce trebuie făcut.

A GameInput instanța este considerată activă și adăugată la __inputs array, atunci când un canal de dispozitiv de intrare a fost adăugat la GameInput instanță. Dacă este activ GameInput instanța a eliminat toate canalele sale de dispozitive de intrare, GameInput instanță considerată inactivă și eliminată din __inputs mulțime.

Acum ajungem la GameInput clasă.

// Constructor GameInput. funcția GameInput ()  GameInput.prototype = value: 0, enabled: true, // Adaugă un canal de dispozitiv de intrare. add: funcție (canal) inputAdd (acest canal); , // Elimină un canal de dispozitiv de intrare. eliminați: funcția (canal) inputRemove (acest canal); , // Elimină toate canalele dispozitivelor de intrare. resetare: funcția () inputReset (aceasta); ;

Da, asta e totul - este o clasă super-ușoară care în esență acționează ca o interfață cu codul principal. valoare proprietate este un număr care variază de la 0 (zero) până la 1 (unu). Dacă valoarea este 0, înseamnă că GameInput instanța nu primește nimic de la niciun canal de dispozitiv de intrare care a fost adăugat la acesta.

GameInput clasa are câteva proprietăți statice, deci le vom adăuga acum.

// Poziția X a indicatorului în fereastra de vizualizare a ferestrei. GameInput.pointerX = 0; // Poziția Y a indicatorului în fereastra de vizualizare a ferestrei. GameInput.pointerY = 0; // Distanța pe care pointerul trebuie să o deplaseze, în pixeli per cadru, pentru a determina valoarea unei instanțe GameInput să fie egală cu 1,0. GameInput.pointerSpeed ​​= 10;

Canalele dispozitivelor de tastatură:

GameInput.KEYBOARD_A = KEYBOARD << DEVICE | 65 << CODE; GameInput.KEYBOARD_B = KEYBOARD << DEVICE | 66 << CODE; GameInput.KEYBOARD_C = KEYBOARD << DEVICE | 67 << CODE; GameInput.KEYBOARD_D = KEYBOARD << DEVICE | 68 << CODE; GameInput.KEYBOARD_E = KEYBOARD << DEVICE | 69 << CODE; GameInput.KEYBOARD_F = KEYBOARD << DEVICE | 70 << CODE; GameInput.KEYBOARD_G = KEYBOARD << DEVICE | 71 << CODE; GameInput.KEYBOARD_H = KEYBOARD << DEVICE | 72 << CODE; GameInput.KEYBOARD_I = KEYBOARD << DEVICE | 73 << CODE; GameInput.KEYBOARD_J = KEYBOARD << DEVICE | 74 << CODE; GameInput.KEYBOARD_K = KEYBOARD << DEVICE | 75 << CODE; GameInput.KEYBOARD_L = KEYBOARD << DEVICE | 76 << CODE; GameInput.KEYBOARD_M = KEYBOARD << DEVICE | 77 << CODE; GameInput.KEYBOARD_N = KEYBOARD << DEVICE | 78 << CODE; GameInput.KEYBOARD_O = KEYBOARD << DEVICE | 79 << CODE; GameInput.KEYBOARD_P = KEYBOARD << DEVICE | 80 << CODE; GameInput.KEYBOARD_Q = KEYBOARD << DEVICE | 81 << CODE; GameInput.KEYBOARD_R = KEYBOARD << DEVICE | 82 << CODE; GameInput.KEYBOARD_S = KEYBOARD << DEVICE | 83 << CODE; GameInput.KEYBOARD_T = KEYBOARD << DEVICE | 84 << CODE; GameInput.KEYBOARD_U = KEYBOARD << DEVICE | 85 << CODE; GameInput.KEYBOARD_V = KEYBOARD << DEVICE | 86 << CODE; GameInput.KEYBOARD_W = KEYBOARD << DEVICE | 87 << CODE; GameInput.KEYBOARD_X = KEYBOARD << DEVICE | 88 << CODE; GameInput.KEYBOARD_Y = KEYBOARD << DEVICE | 89 << CODE; GameInput.KEYBOARD_Z = KEYBOARD << DEVICE | 90 << CODE; GameInput.KEYBOARD_0 = KEYBOARD << DEVICE | 48 << CODE; GameInput.KEYBOARD_1 = KEYBOARD << DEVICE | 49 << CODE; GameInput.KEYBOARD_2 = KEYBOARD << DEVICE | 50 << CODE; GameInput.KEYBOARD_3 = KEYBOARD << DEVICE | 51 << CODE; GameInput.KEYBOARD_4 = KEYBOARD << DEVICE | 52 << CODE; GameInput.KEYBOARD_5 = KEYBOARD << DEVICE | 53 << CODE; GameInput.KEYBOARD_6 = KEYBOARD << DEVICE | 54 << CODE; GameInput.KEYBOARD_7 = KEYBOARD << DEVICE | 55 << CODE; GameInput.KEYBOARD_8 = KEYBOARD << DEVICE | 56 << CODE; GameInput.KEYBOARD_9 = KEYBOARD << DEVICE | 57 << CODE; GameInput.KEYBOARD_UP = KEYBOARD << DEVICE | 38 << CODE; GameInput.KEYBOARD_DOWN = KEYBOARD << DEVICE | 40 << CODE; GameInput.KEYBOARD_LEFT = KEYBOARD << DEVICE | 37 << CODE; GameInput.KEYBOARD_RIGHT = KEYBOARD << DEVICE | 39 << CODE; GameInput.KEYBOARD_SPACE = KEYBOARD << DEVICE | 32 << CODE; GameInput.KEYBOARD_SHIFT = KEYBOARD << DEVICE | 16 << CODE;

Indicatorii canalelor dispozitivului:

GameInput.POINTER_UP = POINTER << DEVICE | 0 << CODE; GameInput.POINTER_DOWN = POINTER << DEVICE | 1 << CODE; GameInput.POINTER_LEFT = POINTER << DEVICE | 2 << CODE; GameInput.POINTER_RIGHT = POINTER << DEVICE | 3 << CODE; GameInput.POINTER_PRESS = POINTER << DEVICE | 4 << CODE;

Gamepad canalele dispozitivului:

GameInput.GAMEPAD_A = GAMEPAD << DEVICE | 0 << CODE; GameInput.GAMEPAD_B = GAMEPAD << DEVICE | 1 << CODE; GameInput.GAMEPAD_X = GAMEPAD << DEVICE | 2 << CODE; GameInput.GAMEPAD_Y = GAMEPAD << DEVICE | 3 << CODE; GameInput.GAMEPAD_LB = GAMEPAD << DEVICE | 4 << CODE; GameInput.GAMEPAD_RB = GAMEPAD << DEVICE | 5 << CODE; GameInput.GAMEPAD_LT = GAMEPAD << DEVICE | 6 << CODE; GameInput.GAMEPAD_RT = GAMEPAD << DEVICE | 7 << CODE; GameInput.GAMEPAD_START = GAMEPAD << DEVICE | 8 << CODE; GameInput.GAMEPAD_SELECT = GAMEPAD << DEVICE | 9 << CODE; GameInput.GAMEPAD_L = GAMEPAD << DEVICE | 10 << CODE; GameInput.GAMEPAD_R = GAMEPAD << DEVICE | 11 << CODE; GameInput.GAMEPAD_UP = GAMEPAD << DEVICE | 12 << CODE; GameInput.GAMEPAD_DOWN = GAMEPAD << DEVICE | 13 << CODE; GameInput.GAMEPAD_LEFT = GAMEPAD << DEVICE | 14 << CODE; GameInput.GAMEPAD_RIGHT = GAMEPAD << DEVICE | 15 << CODE; GameInput.GAMEPAD_L_UP = GAMEPAD << DEVICE | 16 << CODE; GameInput.GAMEPAD_L_DOWN = GAMEPAD << DEVICE | 17 << CODE; GameInput.GAMEPAD_L_LEFT = GAMEPAD << DEVICE | 18 << CODE; GameInput.GAMEPAD_L_RIGHT = GAMEPAD << DEVICE | 19 << CODE; GameInput.GAMEPAD_R_UP = GAMEPAD << DEVICE | 20 << CODE; GameInput.GAMEPAD_R_DOWN = GAMEPAD << DEVICE | 21 << CODE; GameInput.GAMEPAD_R_LEFT = GAMEPAD << DEVICE | 22 << CODE; GameInput.GAMEPAD_R_RIGHT = GAMEPAD << DEVICE | 23 << CODE;

Pentru a finaliza codul, trebuie pur și simplu să sunăm principal() funcţie.

// Inițializați sistemul de introducere. principal();

Și asta e tot codul. Din nou, toate sunt disponibile în fișierele sursă.

Fugi!

Înainte de a încheia tutorialul cu o concluzie, să aruncăm o privire la un alt exemplu de cum GameInput clasa poate fi utilizată. De data aceasta, îi vom da lui Bob abilitatea de a se deplasa și de a sari pentru că hoardele de zombie mutante de cârnați ar putea deveni prea mult pentru el să se ocupe singur.

// Creați intrările. var jump = nou jocInput (); var moveLeft = nou jocInput (); var moveRight = nou jocInput (); // Spuneți intrărilor la ce să reacționați. jump.add (GameInput.KEYBOARD_UP); jump.add (GameInput.KEYBOARD_W); jump.add (GameInput.GAMEPAD_A); moveLeft.add (GameInput.KEYBOARD_LEFT); moveLeft.add (GameInput.KEYBOARD_A); moveLeft.add (GameInput.GAMEPAD_LEFT); moveRight.add (GameInput.KEYBOARD_RIGHT); moveRight.add (GameInput.KEYBOARD_D); moveRight.add (GameInput.GAMEPAD_RIGHT); // În timpul fiecărei actualizări a jocului, verificați intrările. function update () if (jump.value> 0) // Spune Bob sa sara.  altfel // Spune-i lui Bob să nu mai sărit.  if (moveLeft.value> 0) // Spune Bob pentru a muta / rula la stânga.  altfel // Spune-i lui Bob să nu se miște la stânga.  if (moveRight.value> 0) // Spune Bob pentru a muta / rula dreapta.  altfel // Spune-i lui Bob să nu se miște bine. 

Simplu și ușor. Rețineți că valoare proprietatea GameInput instanțele variază de la 0 până la 1, astfel încât am putea face ceva de genul schimbării vitezei de mișcare a lui Bob folosind acea valoare dacă unul dintre canalele de intrare active este analogic.

dacă (moveLeft.value> 0) bob.x - = bob.maxDistancePerFrame * moveLeft.value; 

A se distra!

Concluzie

Jocurile pe platformă au un singur lucru în comun: toți au nevoie să se ocupe de o multitudine de dispozitive de intrare pentru jocuri (controlori), iar rezolvarea acestor dispozitive de intrare poate deveni o sarcină descurajantă. Acest tutorial a demonstrat o modalitate de a gestiona mai multe dispozitive de intrare cu ajutorul unui API simplu, unificat.