Creați un joc de shootere pentru mai mulți jucători în browserul dvs.

Crearea de jocuri multiplayer este o provocare din mai multe motive: acestea pot fi costisitoare pentru gazdă, dificil de proiectat și dificil de implementat. Cu acest tutorial, sper să rezolv această ultimă barieră. 

Acest lucru este destinat dezvoltatorilor care știu cum să facă jocuri și sunt familiarizați cu JavaScript, dar nu au făcut niciodată un joc online multiplayer. Odată ce ați terminat, ar trebui să vă simțiți confortabil să implementați componente de bază de rețea în orice joc și să puteți construi pe el de acolo! 

Aceasta este ceea ce vom construi:

Puteți încerca o versiune live a jocului aici! W sau Up pentru a vă deplasa spre mouse și faceți clic pentru a trage. (Dacă nimeni altcineva nu este online, încercați să deschideți două ferestre de browser pe același computer sau unul pe telefon pentru a viziona modul în care funcționează multiplayerul). Dacă sunteți interesat să rulați acest lucru local, codul sursă complet este disponibil și pe GitHub.

Am pus acest joc împreună folosind activele de artă ale lui Kenney Pirate Pack și cadrul jocului Phaser. Veți lua rolul de programator de rețea pentru acest tutorial. Punctul de pornire va fi o versiune completă a jocului cu un singur jucător al acestui joc și va fi sarcina dvs. să scrieți serverul în Node.js, utilizând Socket.io pentru partea de rețea. Pentru a păstra acest tutorial ușor de gestionat, mă voi concentra asupra componentelor pentru multiplayeri și va schimba conceptele specifice Phaser și Node.js.

Nu este nevoie să configurați nimic local, deoarece vom face acest joc complet în browser-ul de pe Glitch.com! Glitch este un instrument minunat pentru construirea de aplicații web, inclusiv un back end, baza de date și totul. Este minunat pentru prototipuri, predare și colaborare și sunt încântat să vă prezint în acest tutorial.

Hai să ne aruncăm.

1. Configurare

Am pus setul de pornire pe Glitch.com.

Unele sfaturi de interfață rapidă: în orice moment, puteți vedea o previzualizare live a aplicației făcând clic pe Spectacol butonul (din stânga sus).

Bara laterală verticală din stânga conține toate fișierele din aplicație. Pentru a edita această aplicație, va trebui să o "remixați". Aceasta va crea o copie a acestuia pe contul dvs. (sau o furculiță în git lingo). Faceți clic pe Remix asta buton.

În acest moment, veți edita aplicația sub un cont anonim. Vă puteți conecta (sus-dreapta) pentru a vă salva munca.

Acum, înainte de a merge mai departe, este important să vă familiarizați cu codul pentru jocul pe care încercați să îl adăugați pentru multiplayer. Consultați index.html. Există trei funcții importante pentru a fi conștienți de: preîncărcare (linia 99), crea (linia 115) și GameLoop (linia 142), pe lângă obiectul jucătorului (linia 35).

Dacă doriți mai degrabă să învățați prin a face, încercați aceste provocări pentru a vă asigura că înțelegeți cum funcționează jocul:

  • Faceți lumea mai mare (linia 29)-rețineți că există o dimensiune mondială separată pentru lumea din joc și o dimensiune a ferestrei pentru pânza actuală de pe pagină.
  • Faceți și SPACEBAR-ul înainte (linia 53).
  • Modificați tipul de navă pentru jucător (linia 129).
  • Faceți gloanțele să meargă mai încet (linia 155).

Instalarea Socket.io 

Socket.io este o bibliotecă pentru gestionarea comunicării în timp real în browser utilizând WebSockets (spre deosebire de utilizarea unui protocol precum UDP dacă construiți un joc desktop pentru mai mulți jucători). De asemenea, acesta are backbacks pentru a vă asigura că funcționează chiar și atunci când WebSockets nu sunt acceptate. Deci, acesta are grijă de protocoalele de mesagerie și expune un sistem de mesaje pe bază de eveniment pe care să-l folosești.

Primul lucru pe care trebuie să-l facem este să instalați modulul Socket.io. În Glitch, puteți face acest lucru mergând la package.json fișier și fie tastând modulul dorit în dependențe, fie dând clic pe Adăugați pachetul și tastând "socket.io".

Acest lucru ar fi un moment bun pentru a arăta jurnalele serverului. Faceți clic pe Jurnale din stânga pentru a afișa jurnalul serverului. Ar trebui să îl vedeți instalând Socket.io împreună cu toate dependențele sale. Aici puteți viziona eventuale erori sau ieșiri din codul serverului.

Acum mergeți server.js. Aici locuiește codul dvs. de server. În momentul de față, acesta are doar o bază de boilerplate pentru a servi HTML-ul nostru. Adăugați această linie în partea de sus pentru a include Socket.io:

var io = cer ('socket.io') (http); // Asigurați-vă că puneți acest lucru după ce a fost definit http

Acum, de asemenea, trebuie să includeți Socket.io pe client, deci reveniți la index.html și adăugați aceasta în partea superioară din interiorul dvs. etichetă:

 

Notă: Socket.io gestionează automat difuzarea bibliotecii client pe această cale, de aceea această linie funcționează chiar dacă nu vedeți directorul /socket.io/ în folderele dvs..

Acum, Socket.io este inclus și este gata de plecare!

2. Detectoare și jucători de reproducere

Primul pas real va fi acceptarea conexiunilor pe server și crearea de noi jucători pe client. 

Acceptarea conexiunilor pe server

La fundul server.js, adăugați acest cod:

// Spuneți Socket.io pentru a începe acceptarea conexiunilor io.on ('connection', function (socket) console.log ("Clientul nou a conectat cu id:", socket.id);)

Acest lucru îi spune Socket.io să asculte orice conexiune eveniment, care se declanșează automat când un client se conectează. Acesta va crea un nou priză obiect pentru fiecare client, unde socket.id este un identificator unic pentru acel client.

Doar pentru a vă asigura că funcționează, reveniți la clientul dvs. (index.html) și adăugați această linie undeva în crea funcţie:

var socket = io (); // Aceasta declanșează evenimentul "conexiune" pe server

Dacă lansați jocul și apoi vă uitați la jurnalul serverului dvs. (faceți clic pe Jurnale buton, ar trebui să vedeți că este logat evenimentul de conectare! 

Acum, când un nou jucător se conectează, ne așteptăm ca ei să ne trimită informații despre starea lor. În acest caz, trebuie să știm cel puțin X, y și unghi în scopul de a le da în mod corespunzător în locația potrivită. 

Evenimentul conexiune a fost un eveniment încorporat pe care Socket.io îl are pentru noi. Putem asculta pentru orice eveniment personalizat definit vrem. O să-l sun pe a mea Jucator nou, și mă aștept ca clientul să-l trimită imediat ce se conectează cu informații despre locația lor. Ar arata astfel:

// Spuneți Socket.io pentru a începe acceptarea conexiunilor io.on ('connection', function (socket) console.log ("Clientul nou a conectat cu id:", socket.id); socket.on ('new-player ', funcția (state_data) // Ascultați pentru evenimentul nou-player pe acest console.log client ("Noul jucător are stat:", state_data);)

Nu veți vedea nimic în jurnalul serverului, dacă rulați acest lucru. Acest lucru se datorează faptului că nu am spus clientului să emită acest lucru Jucator nou eveniment încă. Dar să presupunem că se ocupă de o clipă și continuăm să mergem pe server. Ce ar trebui să se întâmple după ce am primit locația noului jucător care sa alăturat? 

Am putea trimite un mesaj fiecăruia alte player conectat pentru a le spune că un nou jucător sa alăturat. Socket.io oferă o funcție utilă pentru a face acest lucru:

socket.broadcast.emit ( 'crea jucători', state_data);

apel socket.emit ar trimite mesajul înapoi acelui singur client. apel socket.broadcast.emit îl trimite fiecărui client conectat la server, cu excepția faptului că a fost apelat un singur soclu.

Utilizarea io.emit ar trimite mesajul la fiecare client conectat la server fără excepții. Nu vrem să facem asta cu configurația noastră actuală, deoarece dacă ați primit un mesaj înapoi de la server cerându-vă să vă creați propria nava, ar exista un sprite duplicat, deoarece deja creăm nava jucătorului propriu când începe jocul. Iată o broșură ieftină pentru diferitele tipuri de funcții de mesagerie pe care le vom folosi în acest tutorial.

Codul serverului ar trebui să arate astfel:

// Spuneți Socket.io pentru a începe acceptarea conexiunilor io.on ('connection', function (socket) console.log ("Clientul nou a conectat cu id:", socket.id); socket.on ('new-player ', funcția (state_data) // Ascultați pentru evenimentul nou-player pe acest console.log client ("New player state:", state_data); socket.broadcast.emit (' create-player ', state_data) )

Deci, de fiecare dată când un jucător se conectează, ne așteptăm ca ei să ne trimită un mesaj cu datele despre locația lor și vom trimite aceste date chiar înapoi celorlalți jucători, astfel încât să poată da naștere acelui sprite.

Reproducerea pe Client

Acum, pentru a finaliza acest ciclu, știm că trebuie să facem două lucruri pe client:

  1. Emiteți un mesaj cu datele despre locația noastră odată ce ne conectăm.
  2. Asculta creați-player evenimente și să creeze un jucător în acea locație.

Pentru prima sarcină, după ce am creat player-ul în nostru crea (în jurul liniei 135), putem emite un mesaj care conține datele despre locație pe care dorim să le trimitem astfel:

socket.emit ( 'nou-player', x: player.sprite.x, y: player.sprite.y, unghi: player.sprite.rotation)

Nu trebuie să vă faceți griji cu privire la serializarea datelor trimise. Puteți trece în orice fel de obiect și Socket.io o va gestiona pentru dvs.. 

Înainte de a merge mai departe,testați că aceasta funcționează. Ar trebui să vedeți un mesaj pe jurnalele serverului spunând ceva de genul:

Jucătorul nou are starea: x: 728.8180247836519, y: 261.9979387913289, unghiul: 0

Știm că serverul nostru primește anunțul că un nou jucător a conectat, împreună cu obținerea corectă a datelor despre locație! 

Apoi, dorim să ascultăm pentru o cerere de a crea un nou jucător. Putem plasa acest cod chiar după emițătorul nostru și ar trebui să arate ceva asemănător:

socket.on ('create-player', funcția (state) // CreateShip este o funcție pe care am definit-o deja pentru a crea și a readuce un sprite CreateShip (1, state.x, state.y, state.angle)

Acum testați-l. Deschideți două ferestre din joc și vedeți dacă funcționează.

Ceea ce ar trebui să vedeți este că după deschiderea a doi clienți, primul client va avea două nave nălucite, în timp ce al doilea va vedea doar unul.

Provocare: Îți dai seama de ce se întâmplă asta? Sau cum s-ar putea rezolva? Treceți prin logica client / server pe care am scris-o și încercați să o depanem.

Sper că ai avut șansa să te gândești singur la asta! Ce se întâmplă este că atunci când primul jucător conectat, serverul a trimis un mesaj a creați-player eveniment la orice alt jucator, dar nu a existat nici un alt jucator in jurul sa-l primeasca. Odată ce cel de-al doilea jucător se conectează, serverul trimite din nou difuzarea, iar playerul 1 îl primește și generează în mod corect sprite, în timp ce playerul 2 a pierdut difuzarea inițială a conexiunii playerului 1. 

Deci, problema se întâmplă deoarece jucătorul 2 se alătură târziu în joc și are nevoie să cunoască stadiul jocului. Trebuie să îi spunem oricărui jucător nou care conectează ce jucători există deja (sau ceea ce sa întâmplat deja în lume), astfel încât aceștia să poată prinde din urmă. Înainte de a începe să rezolvăm acest lucru, am un avertisment scurt.

O avertizare privind starea jocului sincronizată

Există două modalități de păstrare a sincronizării jocului fiecărui jucător. Primul este să trimiteți doar cantitatea minimă de informații despre ceea ce a fost modificat în rețea. Deci, de fiecare data cand un nou jucator se conecteaza, vei trimite doar informatiile pentru acel jucator nou tuturor celorlalti jucatori (si ii vei trimite acelui jucator o lista cu toti ceilalti jucatori din lume) si atunci cand se deconecteaza, ii spui tuturor celorlalti jucatori ca acest client individual sa deconectat.

A doua abordare este de a trimite întreaga stare de joc. În acest caz, trebuie să trimiteți o listă completă a tuturor jucătorilor fiecărei persoane de fiecare dată când are loc conectarea sau deconectarea.

Prima este mai bună în sensul că minimizează informațiile trimise prin rețea, dar poate fi foarte dificilă și are riscul ca jucătorii să nu se sincronizeze. Al doilea garantează că jucătorii vor fi întotdeauna sincronizați, dar implică trimiterea mai multor date cu fiecare mesaj.

În cazul nostru, în loc să încercăm să trimitem mesaje atunci când un nou jucător a conectat-o ​​pentru a le crea, când le-au deconectat pentru a le șterge și când s-au mutat pentru a-și actualiza poziția, putem consolida toate acestea într-o singură Actualizați eveniment. Acest eveniment de actualizare va trimite întotdeauna pozițiile fiecărui jucător disponibil tuturor clienților. Asta e tot ce trebuie să facă serverul. Clientul este apoi responsabil pentru păstrarea actuală a lumii sale cu statul pe care îl primește. 

 Pentru a pune în aplicare acest lucru, voi:

  1. Păstrați un dicționar de jucători, cheia fiind codul lor de identitate și valoarea acestora fiind datele lor de locație.
  2. Adăugați playerul în acest dicționar când se conectează și trimit un eveniment de actualizare.
  3. Scoateți playerul din acest dicționar când deconectați și trimiteți un eveniment de actualizare.

Puteți încerca să implementați acest lucru pe cont propriu, deoarece acești pași sunt destul de simpli (foaia de înșelătorie ar putea fi utilă). Iată cum poate arăta implementarea completă:

// Spuneți Socket.io pentru a începe acceptarea conexiunilor // 1 - Păstrați un dicționar al tuturor jucătorilor ca cheie / valoare var players = ; io.on ("conexiune", funcție (socket)) console.log ("Client nou are conectat cu id:", socket.id); socket.on ('new-player' pentru noul jucător pe acest console.log ("Noul jucător are stat:", state_data); // 2 - Adăugați noul player la jucătorii dict [socket.id] = state_data; // Trimiteți un eveniment de actualizare io .mit ('update-players', players);) socket.on ('disconnect', function () // 3- Șterge din dict pe disconnect delete players [socket.id] ))

Partea clientului este puțin mai complicată. Pe de o parte, nu trebuie decât să ne îngrijorăm update-jucători eveniment, dar pe de altă parte, trebuie să contabilizăm crearea a mai multă vapoare dacă serverul ne trimite mai multe nave decât știm sau distrugem dacă avem prea multe.

Iată cum am tratat acest eveniment pe client:

// Ascultați pentru alți jucători conectați // NOTĂ: Trebuie să aveți alt_players =  definit undeva socket.on ('update-players', funcția (players_data) var players_found = ; pentru var id in players_data // Daca playerul nu a fost creat inca daca (other_players [id] == undefined && id! = socket.id) // Asigurati-va ca nu va creati voi data var = [id] = p; console.log ("Creat un nou player la (" + data.x + ", "+ data.y +") "); players_found [id] = true; // Actualizați pozițiile altor jucători dacă (id! = socket.id) other_players [id] .x = play_data [id] .x; // Actualizați ținta, nu poziția actuală, astfel încât să putem interpola alte_playere [id] .y = players_data [id] .y; other_players [id] .rotation = jucători_data [id] .angle; // Verificați dacă un jucător este lipsa și ștergerea lor pentru (var id in other_players) if (! players_found [id]) other_players [id] .destroy (); )

Urmărim navele clientului într-un dicționar numit other_players pe care tocmai l-am definit în partea de sus a scenariului meu (nu este prezentat aici). Deoarece serverul trimite datele jucătorului către toți jucătorii, trebuie să adaug un cec astfel încât un client să nu creeze un sprite suplimentar pentru ei înșiși. (Dacă aveți dificultăți în structurarea acestui lucru, iată codul complet care ar trebui să fie în index.html în acest moment).

Acum testați acest lucru. Ar trebui să puteți să creați și să închideți mai mulți clienți și să vedeți numărul corect de nave de reproducere în pozițiile corecte!

3. Sincronizarea pozițiilor navei

Iată unde ajungem la partea cu adevărat distractivă. Vrem să sincronizăm pozițiile navelor către toți clienții acum. Aici simplitatea structurii pe care am construit-o până acum se dovedește cu adevărat. Avem deja un eveniment de actualizare care poate sincroniza locațiile tuturor. Tot ce trebuie să facem acum este:

  1. Faceți clientul să emită de fiecare dată când s-au mutat cu noua locație.
  2. Asigurați-serverul să asculte acest mesaj de mișcare și să actualizeze intrarea acelui jucător în jucători dicţionar.
  3. Emiteți un eveniment de actualizare tuturor clienților.

Și asta ar trebui să fie! Acum e rândul tău să încerci și să-l implementezi pe cont propriu.

Dacă rămâneți complet blocat și aveți nevoie de un indiciu, puteți consulta proiectul finalizat ca referință.

Notă privind minimizarea datelor din rețea

Cea mai simplă modalitate de a implementa acest lucru este de a actualiza toți jucătorii cu noile locații de fiecare dată când primiți un mesaj de mutare orice jucător. Acest lucru este minunat prin faptul că jucătorii vor primi întotdeauna cele mai recente informații de îndată ce vor fi disponibile, dar numărul de mesaje trimise prin rețea ar putea crește cu ușurință la sute pe cadru. Imaginați-vă dacă ați avut 10 jucători, fiecare trimite un mesaj de mișcare în fiecare cadru, pe care apoi serverul trebuie să îl retransmită tuturor celor 10 jucători. Sunt deja 100 de mesaje pe cadru!

O modalitate mai bună de a face acest lucru ar fi să așteptați până când serverul a primit toate mesajele de la jucători înainte de a trimite o mare actualizare conținând toate informațiile pentru toți jucătorii. În acest fel, veți reduce numărul de mesaje pe care le trimiteți doar la numărul de jucători pe care îi aveți în joc (spre deosebire de pătratul acelui număr). Problema cu asta însă este că toată lumea va trăi la fel de mult ca un jucător cu cea mai lentă legătură în joc. 

Un alt mod de a face acest lucru este pur și simplu ca serverul să trimită actualizări la o rată constantă, indiferent de câte mesaje au primit de la jucători până acum. Actualizarea serverului la aproximativ 30 de ori pe secundă pare a fi un standard comun.

Cu toate acestea, vă decideți să vă structurați serverul, să aveți grijă de numărul de mesaje pe care le trimiteți fiecare cadru în timp ce vă dezvoltați jocul.

4. Sincronizarea gloanțelor

Suntem aproape acolo! Ultima piesă mare va fi sincronizarea gloanțelor în rețea. Am putea să o facem în același mod în care am sincronizat jucătorii:

  • Fiecare client trimite pozițiile tuturor gloanțelor sale în fiecare cadru.
  • Serverul transmite acest lucru către fiecare jucător.

Dar există o problemă.

Asigurarea împotriva cheaturilor

Dacă redați orice vă trimite clientul ca poziția adevărată a gloanțelor, atunci un jucător ar putea să trișeze modificând clientul pentru a vă trimite date false, cum ar fi gloanțele care se teleportează oriunde sunt celelalte nave. Puteți încerca cu ușurință acest lucru prin descărcarea paginii web, modificarea JavaScript-ului și pornirea acestuia din nou. Aceasta nu este doar o problemă pentru jocurile făcute pentru browser. În general, niciodată nu puteți avea încredere în datele furnizate de client. 

Pentru a atenua acest lucru, vom încerca o schemă diferită:

  • Clientul emite ori de câte ori au tras un glonț cu locația și direcția.
  • Serverul simulează mișcarea de gloanțe.
  • Serverul actualizează fiecare client cu locația tuturor gloanțelor.
  • Clienții redă gloanțele în locațiile primite de server.

În acest fel, clientul se ocupă de locul în care glonțul apare, dar nu cât de repede se mișcă sau unde se duce după aceea. Clientul poate schimba amplasarea gloanțelor în propriul ecran, însă nu poate modifica ceea ce văd ceilalți clienți. 

Acum, pentru a pune în aplicare acest lucru, voi adăuga un emit atunci când trageți. Nu voi mai crea nici o sprite actuală, deoarece existența și locația ei sunt acum determinate complet de server. Noul nostru cod de fotografiere index.html ar trebui să arate astfel:

// Trageți cu bullet dacă (game.input.activePointer.leftButton.isDown &&! This.shot) var speed_x = Math.cos (this.sprite.rotation + Math.PI / 2) * 20; var speed_y = Math.sin (acest.sprite.ro + Math.PI / 2) * 20; / * Serverul simulează acum gloanțele, clienții redă doar locațiile bulletului, deci nu mai trebuie să mai faci asta var bullet = ; bullet.speed_x = speed_x; bullet.speed_y = speed_y; bullet.sprite = game.add.sprite (această.sprite.x + bullet.speed_x, this.sprite.y + bullet.speed_y, 'bullet'); bullet_array.push (glonț); * / this.shot = true; // Spuneți serverului că am tras un glonț socket.emit ('shoot-bullet', x: this.sprite.x, y: this.sprite.y, unghi: this.sprite.rotation, speed_x: speed_x, speed_y: speed_y

De asemenea, puteți să comentați acum întreaga secțiune care actualizează marcatorii de pe client:

/ * Actualizăm gloanțele de pe server, deci nu mai trebuie să facem asta pe client // Actualizați gloanțele pentru (var i = 0; i WORLD_SIZE.w || bullet.sprite.y < -10 || bullet.sprite.y > WORLD_SIZE.h) bullet.sprite.destroy (); bullet_array.splice (i, 1); Eu--;  * /

În cele din urmă, trebuie să-l sunăm pe client pentru a asculta actualizările bulletului. Am optat să mă descurc în același mod cu jucătorii, în cazul în care serverul trimite doar o gamă largă de locații pentru bullet într-un eveniment numit gloanțe-actualizare, iar clientul va crea sau distruge gloanțele pentru a păstra în sincronizare. Iata cum arata:

// Ascultă pentru evenimentele de actualizare a bulletului socket.on ('bullets-update', funcția (server_bullet_array) // Dacă nu există suficiente gloanțe pe client, creați-le pentru (var i = 0; i

Ar trebui să fie totul pe client. Presupun că știți unde să puneți aceste fragmente și cum să arătați totul împreună în acest moment, dar dacă vă confruntați cu probleme, amintiți-vă că puteți să vă uitați întotdeauna la rezultatul final pentru referință.

Acum, pe server.js, trebuie să urmărim și să simulează gloanțele. În primul rând, vom crea o matrice pentru a urmări gloanțele, în același mod în care avem unul pentru jucători:

var bullet_array = []; // Urmărește toate gloanțele pentru a le actualiza pe server 

Apoi, ascultam pentru evenimentul nostru bullet shoot:

// Ascultați pentru evenimentele shoot-bullet și adăugați-o la socket.on ('shoot-bullet', funcția (data) if (jucători [socket.id] == undefined) return; var new_bullet = date; .owner_id = socket.id; // Atașați id-ul jucătorului la bullet bullet_array.push (new_bullet););

Acum, simulează gloanțele de 60 de ori pe secundă:

// Actualizați gloanțele de 60 de ori pe cadru și trimiteți funcția de actualizări ServerGameLoop () pentru (var i = 0; i 1000 || bullet.y < -10 || bullet.y > 1000) bullet_array.splice (i, 1); Eu--;  setInterval (ServerGameLoop, 16); 

Ultimul pas este să trimiteți evenimentul de actualizare undeva în interiorul acelei funcții (dar cu siguranță în afara buclă pentru):

// Spuneți tuturor în cazul în care toate gloanțele sunt trimițând întreaga matrice io.emit ("bullets-update", bullet_array);

Acum puteți să-l testați! Dacă totul a mers bine, ar trebui să vedeți ghilimele care se sincronizează corect între clienți. Faptul că am făcut acest lucru pe server este mai mult de lucru, dar, de asemenea, ne oferă un control mult mai mult. De exemplu, atunci când primim un eveniment cu bullet tras, putem verifica dacă viteza glonțului este într-un anumit interval, altfel știm că acest jucător este înșelător.

5. Coliziunea bulletului

Acesta este ultimul mecanism de bază pe care îl vom implementa. Sperăm că până acum v-ați obișnuit cu procedura de planificare a implementării noastre, finalizând complet implementarea clientului înainte de a trece la server (sau invers). Aceasta este o metodă mult mai puțin predispusă la erori decât comutarea înainte și înapoi pe măsură ce o implementați.

Verificarea coliziunii este un mecanic crucial al gameplay-ului, așadar dorim ca acesta să fie înșelător. Îl vom implementa pe server în același mod pe care l-am făcut și pentru gloanțe. Va trebui să:

  • Verificați dacă un glonț este suficient de aproape pentru orice jucător de pe server.
  • Emiteți un eveniment tuturor clienților de fiecare dată când un anumit jucător este lovit.
  • Ascultați clientul la evenimentul lovit și faceți blițul navei când este lovit.

Puteți încerca să faceți acest lucru complet pe cont propriu. Pentru a face blițul jucătorului atunci când este lovit, setați pur și simplu alfa la 0:

player.sprite.alpha = 0;

Și se va restabili din nou la alfa complet (acest lucru se face în actualizarea playerului). Pentru ceilalți jucători, ați face un lucru similar, dar va trebui să aveți grijă să le aduceți alfa înapoi la unul cu ceva de genul acesta în funcția de actualizare:

pentru (var id in other_players) if (other_players [id] .alpha < 1) other_players[id].alpha += (1 - other_players[id].alpha) * 0.16;  else  other_players[id].alpha = 1;  

Singura parte dificilă pe care ar fi trebuit să o rezolvați este să vă asigurați că un glonț al unui jucător nu poate să-i lovească (altfel s-ar putea să intrăm întotdeauna cu glonțul dvs. de fiecare dată când vă declanșați).

Rețineți că în această schemă, chiar dacă un client încearcă să trișeze și refuză să recunoască mesajul pe care le trimite serverul, acesta va schimba doar ceea ce vede pe propriul ecran. Toți ceilalți jucători vor vedea în continuare că acel jucător a fost lovit. 

6. Mișcare mai ușoară

Dacă ați urmat toate etapele până la acest punct, aș vrea să vă felicit. Tocmai ați făcut un joc multiplayer de lucru! Dă-i drumul, trimite-i un prieten și urmări magia jucătorilor online multiplayeri care unesc!

Jocul este pe deplin funcțional, dar munca noastră nu se oprește acolo. Există câteva probleme care ar putea afecta experiența jucătorului pe care trebuie să o abordăm:

  • Miscarea celorlalti jucatori va arata cu adevarat incordata daca fiecare dintre ei nu are o conexiune rapida.
  • Gloanțele se pot simți că nu răspund, deoarece glonțul nu declanșează imediat. Așteaptă un mesaj înapoi de la server înainte de a apărea pe ecranul clientului.

Putem remedia primul prin interpolarea datelor noastre de poziție pentru navele clientului. Deci, chiar dacă nu primim actualizări destul de repede, putem să mutăm fără probleme nava spre locul în care ar trebui să fie, spre deosebire de teleportarea acolo. 

Gloanțele vor necesita puțin mai multă sofisticare. Vrem ca serverul să gestioneze gloanțele, pentru că în felul acesta este ușor de înșelat, dar de asemenea dorim să avem feedback imediat de a trage un glonț și de a vedea că trage. Cea mai bună cale este o abordare hibridă. Atât serverul, cât și clientul pot simula gloanțele, iar serverul trimite în continuare actualizări pentru poziția glonțului. Dacă nu sunt sincronizate, presupuneți că serverul are dreptate și suprascrie poziția glonțului clientului. 

Implementarea sistemului de bullet descris mai sus nu face obiectul acestui tutorial, dar este bine de știut că această metodă există.

Făcând o simplă interpolare pentru pozițiile navelor este foarte ușoară. În loc să setăm poziția direct pe evenimentul de actualizare în cazul în care primim noi date de poziție, salvăm pur și simplu poziția țintă:

// Actualizați pozițiile altor jucători dacă (id! = Socket.id) other_players [id] .target_x = play_data [id] .x; // Actualizați ținta, nu poziția actuală, pentru a putea interpola alte_playere [id] .target_y = players_data [id] .y; other_players [id] .target_rotation = jucători_data [id] .angle; 

Apoi, în cadrul funcției noastre de actualizare (încă în cadrul clientului), ne batem peste toți ceilalți jucători și îi împingem către acest obiectiv:

// Interpolați toți jucătorii unde ar trebui să fie (var id in other_players) var p = other_players [id]; dacă (p.target_x! = undefined) p.x + = (p.target_x - p.x) * 0.16; p.y + = (p.target_y - p.y) * 0,16; // Unghiul de interpolare evitând problema pozitivă / negativă var angle = p.target_rotation; var dir = (unghi - p rotation) / (Math.PI * 2); dir - = Math.round (dir); dir = dir * Math.PI * 2; p.rotation + = dir * 0,16; 

În acest fel, serverul dvs. poate să vă trimită actualizări de 30 de ori pe secundă, dar să redați jocul la 60 de fps și acesta va arăta perfect!

Concluzie

Pfiu! Tocmai am acoperit multe lucruri. Doar pentru a recapitula, ne-am uitat la modul de trimitere a mesajelor între un client și un server și cum să sincronizăm starea jocului prin faptul că serverul îl transmite tuturor jucătorilor. Acesta este cel mai simplu mod de a crea o experiență multiplayer online. 

De asemenea, am văzut cum vă puteți asigura jocul împotriva insectelor prin simularea părților importante de pe server și informarea clienților cu privire la rezultate. Cu cât aveți mai multă încredere în clientul dvs., cu atât jocul va fi mai sigur.

În cele din urmă, am văzut cum să depășim întârzierea prin interpolarea clientului. Lag compensarea este un subiect larg și este de o importanță crucială (unele jocuri deveni doar unplayable cu destul de înaltă suficientă). Interpolarea în așteptarea următoarei actualizări de la server este doar o modalitate de ao atenua. O altă modalitate este de a încerca și de a anticipa următoarele câteva cadre în avans și de a corecta odată ce veți primi datele reale de la server, dar, desigur, acest lucru poate fi foarte dificil.

Un mod complet diferit de atenuare a impactului decalajului este acela de a proiecta în jurul acestuia. Beneficiul de a avea navele să se transforme lent pentru a s

Cod