Postavljanje Višekorisničke Igre uz Socket.io
1. Uvod
Preferirani preduslovi
U ovom vodiču fokusirat ćemo se na to kako postaviti server i povezati svoju igru s njim kako biste omogućili višekorisničke funkcije za svoj projekt putem Socket.io ponašanja. Pokrit ćemo napredne značajke i ponašanja u hyperPadu i toplo preporučujemo da prvo pregledate još neke vodiče i steknete dobar osjećaj za softver prije nego što nastavite.
Ovaj vodič pretpostavlja da imate dobro razumijevanje osnovne funkcionalnosti hyperPada, kao i minimalno razumijevanje pisanog programiranja, jer ćemo se fokusirati na Javascript skriptiranje za izgradnju servera igre. Ovaj vodič će također pretpostaviti osnovno razumijevanje umreživanja i razlika između servera i njegovih klijenata.
Primarni cilj ovog vodiča je naučiti vas što trebate učiniti kako biste dodali višekorisničku funkcionalnost u svoju igru; ne da biste stvorili samu igru. Istražit ćemo odnos između Socket.io klijenata i servera, i kako međusobno komuniciraju s Socket.io ponašanjima.
Zahtjevi
Za stvaranje i hostovanje servera, trebat će vam računar koji može pokrenuti Node.JS (Mac, Windows itd.). Instalacija Node.JS i njegovo podešavanje za našu svrhu bit će pokriveni u ovom vodiču. Ako hostujete server u svojoj kućnoj mreži, vjerojatno ćete trebati promijeniti postavke prosljeđivanja portova na svom usmjerivaču ako želite imati dolazne veze izvan svoje lokalne mreže.
Za ovaj vodič, već smo stvorili jednostavnu demo igru. U njoj imamo dva igrača koji igraju igru lovice u malom labirintu. Istražit ćemo kako se Socket.io ponašanja koriste u primjeru projekta, tako da se preporučuje da preuzmete i otvorite projekt u hyperPadu.
Završeni hyperPad primjer projekta može se preuzeti ovdje: Multiplayer tag tutorial.tap
Pregled
U ovom vodiču ćemo proći kroz osnove stvaranja servera za vašu igru. Server će se brinuti za većinu detalja u igri kao što su bodovi, igrališta itd. Zatim ćemo stvoriti ponašanja koja šalju informacije iz naše igre na server i obrnuto.
Evo općeg toka naše igre:
1. Iz glavnog izbornika, povezat ćemo se s našim serverom i omogućiti korisniku da odluči hoće li stvoriti ili se pridružiti igri.
a. Ako kreirate igru, učitajte čekaonicu.
b. Ako se pridružite igri, učitajte popis dostupnih igara.
c. Kada su dva igrača u čekaonici, igra kreće.
2. U igri, server nasumično smješta igrače u jednu od četiri oblasti i dodjeljuje jednog od njih kao "It".
a. Označavanje drugog igrača nasumično će izmijeniti njihove lokacije, zamijeniti status "It" i dodati bod igraču koji je označio drugog.
b. Ako ne dođe do označavanja unutar određenog vremenskog razdoblja, server će oduzeti 1 bod igraču koji je trenutno "It" i zamijeniti status "It" prije nego što ponovno nasumično smjesti igrače u pojavni područja.
3. Kada igrač dosegne određeni broj bodova, učitat će se preklopni prozor koji proglašava pobjednika. Zatim će se igrači odspojiti iz sobe i vratiti se u glavni izbornik.
2. Postavljanje Servera
Stvaranje Socket.io servera zahtijeva od nas da pokrenemo Javascript aplikaciju koristeći Socket.io biblioteku, slušajući povezivanja igrača. Socket.io je Javascript mrežna biblioteka koja pojednostavljuje mnoge osnove izgradnje umrežene aplikacije za nas. Više detalja slijedi.
Preuzimanje i Instalacija Node.JS
Da biste započeli, idite na Node.JS i preuzmite ga na računar (Mac, Windows itd.) na kojem želite hostovati server. Kada završite sa preuzimanjem, pokrenite instalater i slijedite upute. Sve opcije tijekom instalacije mogu ostati zadane. Node.JS nam omogućuje pokretanje Javascript aplikacija samostalno bez potrebe za web preglednikom.
Preuzimanje i Pokretanje Primjera Servera
Sljedeće, preuzmite primjer Višekorisničkog Servera ovog vodiča na GitHub-u:
https://github.com/hyperPad/multiplayerServerExample
Kliknite na dugme "Clone or download" i odaberite "Download ZIP". Ovo će preuzeti kopiju koda za primjer servera.
Raspakirajte preuzeti ZIP. Unutra ćete pronaći nekoliko malih datoteka, ali najistaknutija datoteka ovdje je "index.js" datoteka koja je kod našeg servera. Zatim otvorite svoj komandni red/terminal unutar foldera primjera servera.
Upišite "npm install" i pritisnite enter, a zatim neka se izvrši. npm je upravitelj paketa koji čita package.json datoteku i preuzima potrebne pakete za naš server. (Uključujući Socket.io!)
Kada se komanda završi, imamo sve što je potrebno za pokretanje servera. U terminalu upišite "node ." i server će početi raditi.
To je to! Naš server sada sluša na portu 3000 za dolazne Socket.io veze.
"node ." pokreće Node.JS za trenutni direktorij, gdje će tražiti direktorijsku index datoteku i pokretati je. Po defaultu, to je "index.js" Javascript datoteka, koja sadrži većinu koda našeg servera, a analizirat ćemo je u ovom vodiču.
Ostavite terminal otvoren, jer će njegovo zatvaranje također zatvoriti server. Vidjet ćete poruke kada se igrači povezuju ili odspajaju, kada se sobe kreiraju ili uništavaju i druge događaje.
Napomena: Za omogućavanje dolaznih veza izvan kućne mreže, vjerojatno ćete morati otvoriti port 3000 na vašem kućnom usmjerivaču. Ovaj postupak se razlikuje od mreže do mreže, ali obično se može pronaći vodič pretraživanjem interneta za vodič za prosljeđivanje portova za modem/usmjerivač vaše kuće. Možda ćete morati zatvoriti i ponovno pokrenuti Node.JS server kada promijenite postavke prosljeđivanja portova.
3. Povezivanje na Server
Kada ste postavili osnove servera, sada se možemo povezati s njim u hyperPad igri. Ovo treba biti postavljeno kao prva stvar koja se dešava u vašem projektu, bez obzira na to u kojoj sceni se nalazite (budući da sve treba komunicirati s serverom). Najbolje je pričvrstiti ovo ponašanje na objekt na Globalnom sloju kako bi se primijenilo preko svih scena.
U primjeru projekta, Globalni sloj oznake "Server" sadrži navedeno. (Preuzimanje projekta može se pronaći u 1. dijelu ovog vodiča pod odjeljkom Zahtjevi.)
Ova dva ponašanja su zapravo sve što vam treba za povezivanje sa serverom. Prvo, pod prilagođenim tabom, uzmite Socket.io Client ponašanje i spustite ga. Ovdje ćete unijeti URL svog servera pod URL tabom u njegovom prozoru svojstava. Na gornjoj slici, URL našeg servera bio je "http://192.168.0.191:3000", uključujući protokol i port. Želite promijeniti ovo da odgovara URL-u vašeg servera ili to vjerojatno neće raditi kada pokrenete igru.
Sada imamo informacije o serveru koje želimo, ali se još uvijek trebamo povezati s njim. Dakle, sve što trebamo jest ponašanje Connect to Socket. Spustite jedno, otvorite njegove postavke i odaberite klijenta u praznom okviru te postavite svojstvo Funkcija na "Connect".
Sada, kada se naš projekt učita, automatski ćemo se povezati s našim serverom.
4. Kreiranje i Pridruživanje Soba
Sljedeći korak je stvoriti naše igračke čekaonice gdje se igrači mogu pridružiti i igrati zajedno.
Evo našeg jednostavnog glavnog izbornika. Sve što treba učiniti je dodirnuti jedan od dugmadi za pokretanje sobe ili traženje dostupnih soba.
Kreiranje Soba
Pogledajmo kako komuniciramo sa serverom za stvaranje naše sobe.
Ima li to jednostavno? Da bismo to brzo pregledali, kada dodirnemo naš gumb, bit ćemo upitani da upišemo ime sobe. Kada to završimo, to ime se šalje serveru gdje se soba kreira, a mi učitamo scenu čekaonice.
Od sada ćemo često koristiti Emit to Socket. To je zato što je to naš glavni način slanja podataka serveru. Razmišljajte o tome kao o "online verziji" Broadcast Message i Receive Message (Emit to Socket obavlja obje radnje istovremeno).
Sada, trebamo dodati neke informacije našem serveru kako bi stvorio sobu kada primi poruku od našeg Emit ponašanja.
socket.on('createRoom', (roomName, callback) => {
Ova linija stvara socket događaj (kao lambda izraz) na serveru, slušajući Emits s događajem 'createRoom'.
Prvi parametar je vrijednost koju proslijeđujemo kroz Emit to Socket ponašanje, u ovom slučaju ime sobe koje je korisnik upisao. Ovdje smo to nazvali 'roomName'.
Drugi parametar je funkcija koju kasnije pozivamo u socket događaju da signalizira klijenta. Emit to Socket ponašanje će nastaviti izvršavanje samo ako se callback funkcija pozove na serveru. Ovdje smo taj parametar nazvali 'callback'.
const room = {
id: uuid(),
name: roomName,
sockets: []
};
Ovo će stvoriti instancu strukture koja će sadržavati osnovne informacije o sobi.
'id' će biti jedinstveni identifikator generiran pomoću pomoćne funkcije 'uuid()'. Ovo će nam pomoći da specifično identificiramo ovu sobu naspram popisa mnogih drugih stvorenih soba kasnije.
'name' će se postaviti na ime koje je korisnik ranije upisao.
'sockets' će se inicijalizirati kao prazan niz. Kasnije će se ovdje pratiti soketi igrača povezani s tom sobom.
rooms[room.id] = room;
'rooms' je globalna lista soba koje su trenutno aktivne. Budući da stvaramo novu sobu, pohranit ćemo je u listu prema njenom ID-u.
joinRoom(socket, room);
Ovo će pozvati globalnu funkciju 'joinRoom' (linija 29), koja će dodati socket igrača u 'sockets' niz sobe. Budući da je igrač stvorio sobu, to će ih također pridružiti.
callback();
});
Konačno, pozivamo callback funkciju kako bismo obavijestili klijenta da je soba stvorena. Ovo omogućuje Emit to Socket ponašanju da nastavi izvršavanje, što će učitati scenu Čekaonice dalje. Ovo također označava kraj 'createRoom' socket događaja.
Pridruživanje Soba
Pridruživanje sobama je pomalo drugačije jer ćemo morati pristupiti informacijama iz druge scene putem dugmeta.
Za pridruživanje sobama, stavili smo početak naših ponašanja na naš globalni sloj zajedno s gdje se povezujemo na server. Na taj način možemo pristupiti informacijama iz bilo koje od naših scena, u ovom slučaju s popisa soba. Za gumb, dodirivanje će jednostavno učitati igrača u scenu popisa soba.
Ovdje imamo naše drugo ponašanje za komunikaciju sa serverom. Socket Event može se smatrati primanjem poruke jer će se aktivirati samo kada se postavljena poruka emitira s servera.
Najbolji način razmišljanja kada koristite Socket Event i Emit to Socket je da Socket Event reagira samo na informacije koje dolaze sa servera, dok se Emit to Socket naziva kao reakcija na lokalne radnje.
Što se tiče naše logike, kada se povežemo na server, emitirat ćemo 'getRoomNames' zahtjev kako bismo dobili dostupna imena soba.
Zatim bismo postavili oznaku na koju igrač može dodirnuti da uđe.
Ovo je naša scena Popis Soba. Ovdje će se učitati i prikazati sve dostupne igre. Jednostavno dodirivanjem imena sobe učitava igrača. Zahvaljujući našim prethodnim ponašanjima, popis će automatski učitati sve otvorene sobe i stvoriti oznake imena soba na ekranu. Ako ništa ne prikazuje, imamo naš gumb Osvježi listu koji jednostavno ponavlja ponašanja za ponovno učitavanje.
Ovdje imamo logiku za postavljanje popisa. Nećemo ulaziti previše u detalje ovdje jer samo želimo znati kako to komunicira sa našim serverom.
Da započnemo, imamo naše Emit to Socket ponašanje. Ovo zahtijeva od servera da pošalje informacije o dostupnim sobama. Iz toga imamo ponašanje Get Array Value. Svi podaci koji dolaze sa servera bit će poslani kao niz i potrebne informacije će biti u prvim vrijednostima. Tako, postavili smo naš Get Array Value da dobije vrijednost na indeksu 0. Odavde će naša ponašanja izvući te podatke i stvoriti oznaku za svaku sobu, prikazujući ih na našem ekranu.
Zatim, provjeravamo objekt na koji trebamo dodirnuti, u našoj sceni to je oznaka pod nazivom Ime Sobe.
Ovaj tekst djeluje kao naš gumb kada se pojavljuje, ali još uvijek moramo povezati ID sobe na koju se želimo povezati. Da bismo to učinili, prvo moramo dobiti ID sobe, a to radimo s Get Attribute ponašanjem i postavljamo ga kao dinamičnog. Tada emitujemo serveru da se želimo pridružiti ovoj sobi i učitati u čekaonicu.
socket.on('joinRoom', (roomId, callback) => {
Ovo je ulazna tačka za socket događaj 'joinRoom'. Vrijednost prvog parametra bi bila ID sobe kojoj se želimo pridružiti koja je emitirana serveru. Ovdje smo to nazvali 'roomId'.
const room = rooms[roomId];
joinRoom(socket, room);
Korištenjem roomId koji je dao klijent, možemo pronaći odgovarajuću instancu sobe na serveru. S tim, pozivamo globalnu funkciju 'joinRoom' (linija 29) sa socketom igrača koji se želi povezati i instancom sobe. Pogledat ćemo 'joinRoom' funkciju malo kasnije.
callback();
});
Konačno, pozivamo callback funkciju da obavijestimo klijenta da nastavi učitavati scenu Čekaonice, čime se označava završetak 'joinRoom' socket događaja.
Što se, dakle, zapravo događa u 'joinRoom' funkciji? Pogledajmo.
room.socket.push(socket);
Kao što je ranije spomenuto, 'room.socket' član niz održava evidenciju o socketima povezanim u sobi. Ova linija to radi jednostavno dodajući socket u niz.
socket.join(room.id, () => {
socket.roomId = room.id;
console.log(socket.id, "Pridružen", room.id);
});
Ovo je službeni poziv za povezivanje klijenta sa sobom. Prvo kažemo socketu da pridruži sobi prema njenom ID-u. Kada je to završeno, slijedi poziv na povratak, pri čemu pridajemo ID sobe socketu. Na kraju, ispisujemo u konzolu da je igrač pridružen sobi!
Čekaonica
Čekaonica je jednostavno scena u koju učitavamo igrače dok čekaju da se drugi igrači pridruže ili da igra počne.
Prilikom ulaska u sobu, emitirat ćemo poruku 'ready' serveru. Kada server primi dvije od ovih poruka, poslat će poruku da započinje igru ('initGame'). Tu poruku primamo našim Socket Event-om i tako učitavamo naš nivo igre. Što se tiče naših ponašanja, to je otprilike to. Možete dodati gumb koji će vas odspojiti iz sobe i vratiti vas u glavni izbornik ako želite.
Na serverskoj strani, analizirajmo kod da vidimo šta se tamo događa.
Ovo je 'ready' događaj koji se poziva kada se klijent pridružio sobi i spreman je povezati se.
const room = rooms[socket.roomId];
Budući da smo pridružili ID sobe socketu, možemo dobiti sobu kako bismo provjerili može li igra početi.
if (room.sockets.length == 2) {
Ovdje provjeravamo da li su sada dva igrača u sobi. Recimo da je to sada istina, a mi nastavljamo započeti igru.
for (const client of room.sockets) {
client.emit('initGame');
}
Now that there are two players, we iterate through each socket and emit the 'initGame' event so each client loads the Level scene, as shown earlier.
5. Igra
Sada, da pređemo na suštinu stvari. Ovo je mesto gde će 90% našeg posla biti obavljeno. Ispod je nivo naše igre koji smo dizajnirali za ovaj vodič.
Pre nego što uđemo u to, imamo oznaku nazvanu "Igračka Logika", hajde da otvorimo i pogledamo.
Wow, to je mnogo ponašanja! Ne brinite, to je jednostavno kako spawnujemo naše igrače u igri. Pogledajmo bliže;
Počinjemo sa Emit to Socket ponašanjem koje obaveštava server da je naša igra počela, sa događajem 'startGame'. Zatim uzimamo niz koji server vraća, uzimamo prvu vrijednost i sa Get Dictionary Value, uzimamo razne atribute koji će naš objekt trebati. Odvojeno stablo radi isto, osim za protivničkog igrača. Na kraju, emitujemo poruku 'init' našem igračkom objektu da započnemo sve.
Pogledajmo šta se dešava na serverskoj strani kada emitujemo 'startGame' događaj.
Prva polovina ovog socket događaja koju ovde vidite postavlja neke inicijalne vrednosti za svakog klijenta, a zatim dodaje bilo kojeg klijenta koji nije emitovao klijent u lokalni 'ostali' niz.
U drugoj polovini, kreira se lokalni rečnik pod nazivom 'ack'. U njemu imamo informacije o našim, i drugim klijentima. Potom šaljemo te informacije nazad klijentu prolazeći naš rečnik 'ack' kroz funkciju callback, koja postaje rezultantna vrednost pozivajući Emit to Socket ponašanje.
Nakon toga, poziva se vremenski prekid od 5 sekundi za konačno početak runde, sada kada svi imaju sve informacije koje su im potrebne za igranje igre. 'beginRound' funkcija (linija 99) kontroliše neku logiku specifičnu za ovu igru. Nećemo previše ulaziti u detalje o tome, ali suštinski se bavi gde spawneruju igrače, proveravanjem rezultata, kao i obaveštavanjem klijenata ko je 'It'.
Kao što je ranije pomenuto, 'init' poruka se poziva na našoj oznaci "Igračka Logika" kada je sve spremno za rad. U igračkom objektu, sada ćemo pogledati ponašanja na njemu koja prima 'init' poruku.
Ovde možete videti da imamo nekoliko stabala ponašanja na našem igraču. Prvo, počećemo sa gornjim levim stablom.
Ovo je ponašanje koje praktično pokreće sve ostalo u našoj igri.
Prvo, primamo 'init' poruku poslanu sa "Igračka Logika" oznake. Odatle uzimamo server ID našeg objekta i uključujemo jedno od naših Socket Events, kao i postavljanje našeg ekrana igre kako bismo mogli tačno pratiti našeg karaktera.
Sinhronizacija Kretanja
Ovo je jedno od naših najvažnijih ponašanja. Ova mala stabla dizajnirana su za ažuriranje pozicije našeg igrača na serveru svaki put kada pomeramo joystick. Verovatno ste primetili da takođe referenciramo neke vrednosti rečnika. Dobijamo te vrednosti iz samostalnog ponašanja rečnika koje sadrži X i Y pozicije našeg igrača.
Pogledajmo 'moved' događaj na serveru.
data = JSON.parse(data);
Rečnici, kada se šalju od klijenta do servera, moraju se analizirati kako bismo lako čitali podatke. To je zato što se rečnici u hyperPadu kodiraju u JSON strukture kada se emituju serveru. Ova linija analizira JSON string strukturu i vraća spisak rečnika nazad u istu lokalnu 'data' varijablu.
socket.x = data.x;
socket.y = data.y;
Ovdje ažuriramo X i Y pozicije pohranjene na socketu s novim vrijednostima koje je dao klijent.
for (const client of room.sockets) {
if (client == socket) {
continue;
}
client.emit(socket.id, {
x: socket.x,
y: socket.y,
score: socket.score,
isIt: socket.isIt
});
}
Zatim, iteriramo kroz sve klijente da ih obavestimo o našoj novoj poziciji i drugim detaljima, izuzev nas samih (tj. Emiter klijent ne mora znati svoju vlastitu poziciju).
Ovo stablo ovde kontroliše veći deo naše igre. Koristimo Socket Event kada server proverava koji je igrač označen kao 'It', što se emituje od 'beginRound' (linija 99) na serveru.
Zatim, uzimamo ID servera našeg objekta iz niza i koristimo Dictionary Value kako bismo ga razdvojili u razne delove podataka koje sadrži. Odatle dobijamo naš rezultat, ako smo označeni kao 'It', kao i x i y pozicije našeg objekta, a zatim ih primenjujemo na atribute objekta. Ostala ponašanja se odnose na postavljanje i kontrolu UI u našoj igri.
Zaključak
Bilo je puno informacija ovde, ali nadamo se da ako ste stigli do ovde, trebali biste imati razumevanje kako koristiti Socket.io ponašanja za stvaranje višekorisničkih iskustava za svoje igrače.
Pokušajte sami! Uzmite postojeću igru koju ste stvorili i pokušajte joj dodati neke online funkcionalnosti, kao što je scena liste visokih rezultata koja se povezuje na server i zahteva da preuzme listu najboljih 10 rezultata sa imenima igrača za prikaz.
Teško je naučiti skriptni jezik kao što je Javascript u jednom članku. Na sreću, ako imate problema, postoji mnogo drugih resursa koji vam mogu pomoći u pisanju Javascript aplikacija za Node.JS i Socket.io;
Naučite Javascript - https://developer.mozilla.org/bm/docs/Web/JavaScript
Naučite Socket.io - https://socket.io/docs/
Naučite Node.JS - https://nodejs.org/en/docs/

