O omrežnem modelu v igrah za začetnike

O omrežnem modelu v igrah za začetnike
Zadnja dva tedna sem delal na spletnem motorju za svojo igro. Pred tem nisem vedel ničesar o omrežju v igrah, zato sem prebral veliko člankov in izvedel veliko poskusov, da sem razumel vse koncepte in lahko napisal svoj lasten omrežni mehanizem.

V tem vodniku bi rad z vami delil različne koncepte, ki se jih morate naučiti, preden napišete lasten motor za igre, ter najboljše vire in članke za njihovo učenje.

Na splošno obstajata dve glavni vrsti omrežnih arhitektur: enakovredni in odjemalec-strežnik. V arhitekturi peer-to-peer (p2p) se podatki prenašajo med poljubnimi pari povezanih igralcev, v arhitekturi odjemalec-strežnik pa se podatki prenašajo samo med igralci in strežnikom.

Čeprav se arhitektura enakovrednega še vedno uporablja v nekaterih igrah, je odjemalec-strežnik standard: lažje ga je implementirati, zahteva manjšo širino kanala in omogoča lažjo zaščito pred goljufanjem. Zato se bomo v tej vadnici osredotočili na arhitekturo odjemalec-strežnik.

Predvsem nas najbolj zanimajo avtoritarni strežniki: v takih sistemih ima strežnik vedno prav. Na primer, če igralec misli, da je na koordinatah (10, 5), strežnik pa mu pove, da je na (5, 3), mora stranka zamenjati svoj položaj s tistim, ki ga je sporočil strežnik, in ne vice obratno. Uporaba avtoritativnih strežnikov olajša prepoznavanje goljufov.

Omrežni igralni sistemi imajo tri glavne komponente:

  • Transportni protokol: kako se podatki prenašajo med odjemalci in strežnikom.
  • Aplikacijski protokol: kaj se prenaša od odjemalcev do strežnika in od strežnika do odjemalcev in v kakšni obliki.
  • Logika aplikacije: kako se preneseni podatki uporabljajo za posodobitev stanja odjemalcev in strežnika.

Zelo pomembno je razumeti vlogo vsakega dela in z njim povezane izzive.

Transportni protokol

Prvi korak je izbira protokola za prenos podatkov med strežnikom in odjemalci. Za to obstajata dva internetna protokola: TCP и UDP. Lahko pa ustvarite svoj transportni protokol na podlagi enega od njih ali uporabite knjižnico, ki jih uporablja.

Primerjava TCP in UDP

Tako TCP kot UDP temeljita na IP. IP omogoča prenos paketa od vira do prejemnika, vendar ne zagotavlja, da bo poslani paket prej ali slej dosegel prejemnika, da ga bo dosegel vsaj enkrat in da bo zaporedje paketov prispelo v pravilnem naročilo. Poleg tega lahko paket vsebuje le omejeno količino podatkov, podano z vrednostjo MTU.

UDP je le tanka plast na IP. Zato ima enake omejitve. V nasprotju s tem ima TCP veliko funkcij. Zagotavlja zanesljivo, urejeno povezavo med dvema vozliščema s preverjanjem napak. Zato je TCP zelo priročen in se uporablja v številnih drugih protokolih, npr. HTTP, FTP и SMTP. Toda vse te lastnosti imajo svojo ceno: zamuda.

Da bi razumeli, zakaj lahko te funkcije povzročijo zakasnitev, moramo razumeti, kako deluje TCP. Ko oddajno vozlišče pošlje paket prejemnemu vozlišču, pričakuje, da bo prejelo potrditev (ACK). Če ga po določenem času ne prejme (zaradi izgube paketa ali potrditve ali iz kakšnega drugega razloga), ga ponovno pošlje. Poleg tega TCP zagotavlja, da so paketi prejeti v pravilnem vrstnem redu, tako da dokler ni prejet izgubljeni paket, vseh drugih paketov ni mogoče obdelati, tudi če jih je sprejemni gostitelj že prejel.

Toda kot si verjetno lahko predstavljate, je zakasnitev v igrah za več igralcev zelo pomembna, zlasti v žanrih, polnih akcije, kot je FPS. Zato veliko iger uporablja UDP s svojim lastnim protokolom.

Izvorni protokol, ki temelji na UDP, je lahko iz različnih razlogov učinkovitejši od TCP. Nekatere pakete lahko na primer označi kot zaupanja vredne, druge pa kot nezaupljive. Zato mu ni vseeno, ali nezaupanja vreden paket doseže prejemnika. Ali pa lahko obdela več podatkovnih tokov, tako da izgubljeni paket v enem toku ne upočasni preostalih tokov. Na primer, lahko obstaja nit za vnos igralca in druga nit za sporočila v klepetu. Če se sporočilo klepeta, ki ni nujno, izgubi, to ne bo upočasnilo vnosa, ki je nujen. Ali pa lahko lastniški protokol implementira zanesljivost drugače kot TCP, da bi bil bolj učinkovit v okolju video iger.

Torej, če je TCP tako zanič, potem bomo ustvarili lasten transportni protokol, ki temelji na UDP?

Je malo bolj zapleteno. Čeprav je TCP skoraj neoptimalen za igralne omrežne sisteme, lahko deluje precej dobro za vašo specifično igro in vam prihrani dragoceni čas. Na primer, zakasnitev morda ni težava pri potezni igri ali igri, ki jo je mogoče igrati samo v omrežjih LAN, kjer sta zakasnitev in izguba paketov veliko nižji kot v internetu.

Številne uspešne igre, vključno z World of Warcraft, Minecraft in Terraria, uporabljajo TCP. Vendar pa večina FPS-jev uporablja lastne protokole, ki temeljijo na UDP, zato bomo o njih več govorili spodaj.

Če se odločite za uporabo TCP, se prepričajte, da je onemogočen Naglov algoritem, ker medpomni pakete pred pošiljanjem, kar pomeni, da poveča zakasnitev.

Če želite izvedeti več o razlikah med UDP in TCP v kontekstu iger za več igralcev, si lahko preberete članek Glenna Fiedlerja UDP vs. TCP.

Lasten protokol

Torej želite ustvariti svoj transportni protokol, vendar ne veste, kje začeti? Imate srečo, ker je Glenn Fiedler o tem napisal dva čudovita članka. V njih boste našli veliko pametnih misli.

Prvi člen Omrežje za programerje iger 2008, lažji od drugega, Gradnja omrežnega protokola za igro 2016. Priporočam, da začnete s starejšim.

Upoštevajte, da je Glenn Fiedler velik zagovornik uporabe protokola po meri, ki temelji na UDP. In ko boste prebrali njegove članke, boste verjetno sprejeli njegovo mnenje, da ima TCP resne pomanjkljivosti v video igrah, in boste želeli implementirati svoj protokol.

Če pa ste novi v omrežju, si naredite uslugo in uporabite TCP ali knjižnico. Za uspešno implementacijo lastnega transportnega protokola se morate vnaprej veliko naučiti.

Omrežne knjižnice

Če potrebujete nekaj učinkovitejšega od TCP, vendar se ne želite ukvarjati z implementacijo lastnega protokola in spuščanjem v veliko podrobnosti, lahko uporabite omrežno knjižnico. Veliko jih je:

Nisem preizkusil vseh, vendar imam raje ENet, ker je enostaven za uporabo in zanesljiv. Poleg tega ima jasno dokumentacijo in vadnico za začetnike.

Transportni protokol: Zaključek

Če povzamemo: obstajata dva glavna transportna protokola: TCP in UDP. TCP ima veliko uporabnih funkcij: zanesljivost, ohranjanje vrstnega reda paketov, odkrivanje napak. UDP vsega tega nima, TCP pa ima po svoji naravi povečano zakasnitev, kar je za nekatere igre nesprejemljivo. To pomeni, da lahko za zagotovitev nizke zakasnitve ustvarite svoj lasten protokol, ki temelji na UDP, ali uporabite knjižnico, ki implementira transportni protokol na UDP in je prilagojena video igram za več igralcev.

Izbira med TCP, UDP in knjižnico je odvisna od več dejavnikov. Prvič, glede na potrebe igre: ali potrebuje nizko zakasnitev? Drugič, glede na zahteve aplikacijskega protokola: ali potrebuje zanesljiv protokol? Kot bomo videli v naslednjem delu, je mogoče ustvariti aplikacijski protokol, za katerega je povsem primeren nezaupanja vreden protokol. Končno morate upoštevati tudi izkušnje razvijalca omrežnega motorja.

Imam dva nasveta:

  • Čim bolj abstrahirajte transportni protokol od preostale aplikacije, tako da ga je mogoče preprosto zamenjati brez ponovnega pisanja celotne kode.
  • Ne optimizirajte preveč. Če niste strokovnjak za mreženje in niste prepričani, ali potrebujete transportni protokol po meri, ki temelji na UDP, lahko začnete s TCP ali knjižnico, ki zagotavlja zanesljivost, nato pa preizkusite in izmerite zmogljivost. Če se pojavijo težave in ste prepričani, da je vzrok transportni protokol, je morda čas, da ustvarite svoj transportni protokol.

Na koncu tega dela priporočam branje Uvod v programiranje iger za več igralcev avtorja Briana Hooka, ki pokriva številne teme, o katerih se tukaj razpravlja.

Aplikacijski protokol

Zdaj, ko lahko izmenjujemo podatke med odjemalci in strežnikom, se moramo odločiti, katere podatke bomo prenesli in v kakšni obliki.

Klasična shema je, da odjemalci pošljejo vhod ali dejanja strežniku, strežnik pa odjemalcem pošlje trenutno stanje igre.

Strežnik ne pošlje celotnega stanja, ampak filtrirano stanje z entitetami, ki se nahajajo v bližini igralca. To počne iz treh razlogov. Prvič, celotno stanje je lahko preveliko, da bi se prenašalo pri visoki frekvenci. Drugič, stranke zanimajo predvsem vizualni in zvočni podatki, saj je večina logike igre simulirana na strežniku igre. Tretjič, v nekaterih igrah igralcu ni treba poznati določenih podatkov, na primer položaja sovražnika na drugi strani zemljevida, sicer lahko zavoha pakete in natančno ve, kam naj se premakne, da ga ubije.

Serializacija

Prvi korak je pretvorba podatkov, ki jih želimo poslati (vnos ali stanje igre), v obliko, primerno za prenos. Ta proces se imenuje serializacija.

Misel, ki takoj pride na misel, je uporaba človeku berljivega formata, kot je JSON ali XML. Toda to bo popolnoma neučinkovito in bo zapravilo večino kanala.

Priporočljivo je, da namesto tega uporabite binarno obliko, ki je veliko bolj kompaktna. To pomeni, da bodo paketi vsebovali le nekaj bajtov. Tu je treba razmisliti o problemu vrstni red bajtov, ki se lahko razlikujejo v različnih računalnikih.

Za serializacijo podatkov lahko uporabite knjižnico, na primer:

Prepričajte se le, da knjižnica ustvarja prenosne arhive in skrbi za endianness.

Alternativna rešitev je, da jo implementirate sami; ni posebno težko, še posebej, če za svojo kodo uporabljate pristop, osredotočen na podatke. Poleg tega vam bo omogočil izvajanje optimizacij, ki pri uporabi knjižnice niso vedno možne.

Glenn Fiedler je napisal dva članka o serializaciji: Branje in pisanje paketov и Strategije serializacije.

Stiskanje

Količina prenesenih podatkov med odjemalci in strežnikom je omejena s pasovno širino kanala. Stiskanje podatkov vam bo omogočilo prenos več podatkov v vsakem posnetku, povečanje pogostosti posodabljanja ali preprosto zmanjšanje zahtev za kanal.

Bitna embalaža

Prva tehnika je pakiranje bitov. Sestavljen je iz uporabe točno tistega števila bitov, ki so potrebni za opis želene vrednosti. Na primer, če imate enum, ki ima lahko 16 različnih vrednosti, potem lahko namesto celega bajta (8 bitov) uporabite samo 4 bite.

Glenn Fiedler pojasnjuje, kako to izvesti v drugem delu članka Branje in pisanje paketov.

Pakiranje bitov deluje še posebej dobro pri vzorčenju, kar bo tema naslednjega razdelka.

Vzorčenje

Vzorčenje je tehnika stiskanja z izgubo, ki uporablja samo podmnožico možnih vrednosti za kodiranje vrednosti. Najlažji način za izvedbo diskretizacije je zaokroževanje števil s plavajočo vejico.

Glenn Fiedler (spet!) v svojem članku pokaže, kako vzorčenje uporabiti v praksi Stiskanje posnetkov.

Algoritmi stiskanja

Naslednja tehnika bodo algoritmi stiskanja brez izgub.

Tu so po mojem mnenju trije najbolj zanimivi algoritmi, ki jih morate poznati:

  • Huffmanovo kodiranje z vnaprej izračunano kodo, ki je izjemno hitra in lahko daje dobre rezultate. Uporabljali so ga za stiskanje paketov v omrežnem motorju Quake3.
  • zlib je splošni algoritem stiskanja, ki nikoli ne poveča količine podatkov. Kako lahko vidite tukaj, je bil uporabljen v različnih aplikacijah. Morda je odveč za posodabljanje stanj. Lahko pa je uporabno, če morate strankam s strežnika poslati sredstva, dolga besedila ali teren.
  • Kopiranje dolžin - To je verjetno najpreprostejši algoritem stiskanja, vendar je zelo učinkovit za določene vrste podatkov in se lahko uporablja kot korak pred obdelavo pred zlib. Posebej primeren je za stiskanje terena, sestavljenega iz ploščic ali vokselov, v katerih se ponavlja veliko sosednjih elementov.

Delta kompresija

Zadnja tehnika stiskanja je delta stiskanje. Sestoji iz dejstva, da se prenašajo le razlike med trenutnim stanjem igre in zadnjim stanjem, ki ga prejme odjemalec.

Prvič je bil uporabljen v omrežnem motorju Quake3. Tu sta dva članka, ki pojasnjujeta, kako ga uporabljati:

V drugem delu svojega članka ga je uporabil tudi Glenn Fiedler Stiskanje posnetkov.

Šifriranje

Poleg tega boste morda morali šifrirati prenos informacij med odjemalci in strežnikom. Razlogov za to je več:

  • zasebnost/zaupnost: sporočila lahko prebere samo prejemnik in nobena druga oseba, ki voha po omrežju, jih ne bo mogla prebrati.
  • avtentikacija: oseba, ki želi igrati vlogo igralca, mora poznati njegov ključ.
  • Preprečevanje goljufanja: zlonamernim igralcem bo veliko težje ustvariti lastne goljufajoče pakete, morali bodo reproducirati šifrirno shemo in najti ključ (ki se spremeni z vsako povezavo).

Močno priporočam uporabo knjižnice za to. Predlagam uporabo libodij, ker je še posebej preprost in ima odlične vadnice. Še posebej zanimiva je vadnica o izmenjava ključev, ki omogoča ustvarjanje novih ključev z vsako novo povezavo.

Protokol uporabe: Zaključek

S tem se zaključi naš prijavni protokol. Menim, da je stiskanje popolnoma neobvezno in je odločitev o njegovi uporabi odvisna le od igre in zahtevane pasovne širine. Šifriranje je po mojem mnenju obvezno, vendar v prvem prototipu lahko brez njega.

Logika aplikacije

Zdaj lahko posodobimo stanje v odjemalcu, vendar lahko naletimo na težave z zakasnitvijo. Po dokončanju vnosa mora igralec počakati, da se stanje igre posodobi s strežnika, da vidi, kakšen vpliv je imelo na svet.

Poleg tega je med dvema posodobitvama stanja svet popolnoma statičen. Če je stopnja posodabljanja stanja nizka, bodo gibi zelo sunkoviti.

Obstaja več tehnik za zmanjšanje vpliva te težave, ki jih bom obravnaval v naslednjem razdelku.

Tehnike glajenja zakasnitve

Vse tehnike, opisane v tem razdelku, so podrobno obravnavane v seriji Hitra igra za več igralcev Gabriel Gambetta. Zelo priporočam branje te odlične serije člankov. Vključuje tudi interaktivno predstavitev, ki vam omogoča, da vidite, kako te tehnike delujejo v praksi.

Prva tehnika je uporaba vhodnega rezultata neposredno, ne da bi čakali na odgovor strežnika. Se imenuje napovedovanje na strani odjemalca. Ko pa odjemalec prejme posodobitev od strežnika, mora preveriti, ali je bila njegova napoved pravilna. Če temu ni tako, mora samo spremeniti svoje stanje glede na to, kar je prejel od strežnika, ker je strežnik avtoritaren. Ta tehnika je bila prvič uporabljena v Quakeu. Več o tem si lahko preberete v članku Pregled kode Quake Engine Fabien Sanglars [prevod na Habré].

Drugi niz tehnik se uporablja za gladko premikanje drugih entitet med dvema posodobitvama stanja. Obstajata dva načina za rešitev tega problema: interpolacija in ekstrapolacija. V primeru interpolacije se vzameta zadnji dve stanji in prikaže se prehod iz enega v drugega. Njegova slabost je, da povzroči majhno zamudo, ker stranka vedno vidi, kaj se je zgodilo v preteklosti. Pri ekstrapolaciji gre za napovedovanje, kje bi morale biti zdaj entitete, na podlagi zadnjega stanja, ki ga je prejel odjemalec. Njegova slabost je, da če entiteta popolnoma spremeni smer gibanja, bo med napovedjo in dejanskim položajem velika napaka.

Najnovejša, najnaprednejša tehnika, uporabna samo v FPS, je kompenzacija zamika. Pri uporabi kompenzacije zaostanka strežnik upošteva zamude odjemalca, ko ta strelja na tarčo. Na primer, če je igralec izvedel strel v glavo na svojem zaslonu, v resnici pa je bila njegova tarča zaradi zamude na drugi lokaciji, potem bi bilo nepravično zavrniti igralcu pravico do ubijanja zaradi zamude. Zato strežnik previje čas nazaj do trenutka, ko je igralec sprožil, da simulira, kar je igralec videl na svojem zaslonu, in preveri trk med njegovim strelom in tarčo.

Glenn Fiedler je (kot vedno!) leta 2004 napisal članek Omrežna fizika (2004), v katerem je postavil temelje za sinhronizacijo fizikalnih simulacij med strežnikom in odjemalcem. Leta 2014 je napisal novo serijo člankov Fizika mreženja, ki je opisal druge tehnike za sinhronizacijo fizikalnih simulacij.

Na wikiju Valve sta tudi dva članka, Izvorno omrežje za več igralcev и Metode kompenzacije zakasnitve v načrtovanju in optimizaciji protokola odjemalca/strežnika v igri ki upoštevajo nadomestilo za zamude.

Preprečevanje goljufanja

Obstajata dve glavni tehniki za preprečevanje goljufanja.

Prvič: otežiti goljufom pošiljanje zlonamernih paketov. Kot že omenjeno, je dober način za izvedbo tega šifriranje.

Drugič: avtoritarni strežnik bi moral prejemati samo ukaze/vnos/dejanja. Odjemalec ne bi smel imeti možnosti spreminjanja stanja na strežniku drugače kot s pošiljanjem vnosa. Nato mora strežnik vsakič, ko prejme vnos, preveriti, ali je veljaven, preden ga uporabi.

Logika uporabe: zaključek

Priporočam, da implementirate način za simulacijo visokih zakasnitev in nizkih stopenj osveževanja, tako da lahko preizkusite obnašanje svoje igre v slabih pogojih, tudi če se odjemalec in strežnik izvajata na istem računalniku. To bo zelo poenostavilo izvajanje tehnik glajenja zakasnitve.

Drugi koristni viri

Če želite raziskati druge vire o omrežnih modelih, jih najdete tukaj:

Vir: www.habr.com

Dodaj komentar