Configurant un Jocs de Multijugador amb Socket.io
Part 1: Introducció
Prerequisits Preferits
En aquest tutorial, ens centrarem en com configurar un servidor i connectar el teu joc a ell per poder habilitar funcions de multijugador per al teu projecte mitjançant comportaments de Socket.io. Estarem cobrint característiques i comportaments avançats a hyperPad i es recomana fortament revisar alguns altres tutorials primer i tenir una bona comprensió del programari abans de continuar.
Este tutorial assumeix que tens una bona comprensió de les funcionalitats bàsiques de hyperPad així com un coneixement mínim de programació escrita, ja que ens centrarem en la programació Javascript per a la construcció d’un servidor de jocs. Aquest tutorial també assumeix un coneixement bàsic de xarxes i de les diferències entre un servidor i els seus clients.
L'objectiu principal d’aquest tutorial és ensenyar-te el que necessites fer per afegir funcionalitat de multijugador al teu joc; no per crear el joc en si. Explorarem la relació entre els clients de Socket.io i el servidor, i com es comuniquen entre ells mitjançant els comportaments de Socket.io.
Requisits
Per crear i allotjar el servidor, necessitaràs un ordinador capaç d’executar Node.JS (Mac, Windows, etc.). La instal·lació de Node.JS i la seva configuració per al nostre propòsit es cobrira en aquest tutorial. Si estàs allotjant un servidor a la teva xarxa domèstica, probablement hauràs de canviar la configuració de reinici de port del teu router si desitges tenir connexions entrades des de fora de la teva xarxa local.
Per a aquest tutorial, ja hem creat un joc de demostració senzill. En ell, hi ha dos jugadors jugant a un joc de captura en un petit laberint. Explorarem com es fan servir els comportaments de Socket.io en el projecte d'exemple, així que es recomana descarregar i obrir el projecte a hyperPad.
El projecte d'exemple complet de hyperPad es pot descarregar aquí: Tutorial de captura multijugador.tap
Visió General
En aquest tutorial, passarem pels conceptes bàsics per crear un servidor per al teu joc. El servidor gestionarà la majoria dels detalls del joc, com ara la puntuació, les sales de joc, etc. A continuació, crearem comportaments que enviaran informació del nostre joc al servidor i viceversa.
Aquí hi ha el flux general del nostre joc:
1. Des del menú principal, connectarem al nostre servidor i deixarem que l'usuari decideixi si crear o unir-se a un joc.
a. Si crea un joc, carregarem la sala d'espera.
b. Si s'uneix a un joc, carregarem una llista de sales de joc disponibles.
c. Quan dos jugadors estiguin a la sala d'espera, el joc s'iniciarà.
2. En un joc, el servidor col·locarà aleatòriament els jugadors en una de les quatre àrees i en assignarà un com "És A".
a. Etiquetar l'altre jugador aleatoritzarà les seves ubicacions, canviarà l'estatus de "És A" i afegirà un punt al jugador que ha etiquetat l'altre.
b. Si no hi ha etiquetes dins d'un cert període de temps, el servidor eliminarà 1 punt del jugador que actualment és "És A" i canviarà l'estatus de "És A" abans de col·locar els jugadors aleatòriament en les zones de reaparició de nou.
3. Un cop un jugador hagi arribat a un cert nombre de punts, es carregarà un superposició declarant un guanyador. A continuació, els jugadors es desconectaran de la sala i tornaran al menú principal.
Part 2: Configurant el servidor
Crear un servidor de Socket.io requereix executar una aplicació Javascript que utilitza la biblioteca Socket.io, escoltant les connexions dels jugadors. Socket.io és una biblioteca de xarxes Javascript que simplifica una gran part del funcionament intern de la creació d'una aplicació de xarxa per a nosaltres. More details to follow.
Descarregar i instal·lar Node.JS
Per començar, dirigeix-te a Node.JS i descarrega-ho a l'ordinador (Mac, Windows, etc.) al qual vulguis allotjar el servidor. Quan acabis de descarregar, executa l'instal·ladora i segueix les seves instruccions. Totes les opcions durant la instal·lació es poden deixar per defecte. Node.JS ens dona la capacitat d'executar aplicacions Javascript per si mateixes sense la necessitat d'un navegador web.
Descarregar i executar el servidor d'exemple
A continuació, descarrega l'exemple de servidor multijugador d'aquest tutorial a GitHub:
https://github.com/hyperPad/multiplayerServerExample
Fes clic al botó "Clonar o descarregar" i selecciona "Descarregar ZIP". Això descargarà una còpia del codi per a l'exemple de servidor.
Extreu el ZIP descarregat. Dins trobaràs alguns fitxers petits, però el fitxer més notable aquí és el fitxer "index.js" que és el codi per al nostre servidor. A continuació, obre la teva línia de comandes/terminal dins de la carpeta d'exemple del servidor.
Escriu "npm install" i prem enter, després deixa que s'executi. npm és un gestor de paquets que llegeix el fitxer package.json i descarrega els paquets necessaris per al nostre servidor. (Incloent Socket.io!)
Quan el comandament hagi acabat, tenim tot el que necessitem per executar el servidor. A la terminal, escriu "node ." i el servidor començarà.
Això és tot! El nostre servidor ara està escoltant al port 3000 per a connexions entrants de Socket.io.
"node ." llança Node.JS per al directori actual, on buscarà el fitxer d'índex del directori i l'executarà. Per defecte, aquest és el fitxer Javascript "index.js", que conté la major part del codi del nostre servidor i s'analitzarà al llarg d'aquest tutorial.
Manten la terminal oberta, ja que tancar-la també tancarà el servidor. Veureu que s’imprimeixen missatges quan els jugadors es connecten o desconnecten, i quan es creen o es destrueixen sales, entre altres esdeveniments.
Nota: Per permetre connexions entrants des de fora d'una xarxa domèstica, probablement hauràs d'obrir el port 3000 al teu router de casa. Aquest procés varia d'una xarxa a una altra, però normalment es pot trobar una guia en cercar per internet una guia de reinici de port per al teu mòdem/router de casa. Pot ser que necessitis tancar i reiniciar el servidor Node.JS quan canviïs les configuracions del reinici de port.
Part 3: Connectant-se al servidor
Un cop tinguis els fonaments bàsics del servidor iniciats, ara podem connectar-nos a ell en un joc de hyperPad. Això s’hauria de configurar com la primera cosa que passa al teu projecte independentment de quina escena estiguis (ja que tot necessita comunicar-se amb el servidor). És millor unir aquest comportament a un objecte a la capa Global perquè s'apliqui a totes les escenes.
En el projecte d'exemple, l'etiqueta de la capa Global "Servidor" conté el que es mostra a dalt. (La descàrrega del projecte es pot trobar a la Part 1 d'aquest tutorial sota la secció Requisits.)
Aquests dos comportaments són realment tot el que necessites per connectar-te al servidor. Primer, sota la pestanya personalitzada, agafa el comportament Socket.io Client i afegeix-lo. Aquí, introduiràs la URL del teu servidor sota la pestanya URL a la seva finestra de propietats. En la imatge anterior, la URL del nostre servidor era "http://192.168.0.191:3000", incloent protocol i port. Voldràs canviar això per coincidir amb la URL del teu servidor o probablement no funcionarà quan iniciïs el joc.
Ara tenim la informació del servidor que volem, però encara necessitem connectar-nos a ell. Així que, tot el que necessitem és el comportament Connect to Socket. Afegeix un i obre les seves propietats, selecciona el client a la caixa buida i estableix la propietat Funció a "Connectar".
Ara, quan el nostre projecte s'iniciï, ens connectarem automàticament al nostre servidor.
Part 4: Creant i unint-se a sales
El següent pas és crear les nostres sales de joc on els jugadors poden unir-se i jugar junts.
Aquí tenim la nostra senzilla pantalla de menú principal. Només cal tocar un dels botons per iniciar una sala o buscar sales disponibles.
Creant Sales
Vegem com ens comuniquem amb el servidor per crear la nostra sala.
És força senzill, oi? Per tant, per repassar-ho ràpidament, un cop toquem el botó se’ns demanarà que escrivim un nom per a la sala. Un cop ho fem, aquest nom es transmet al servidor on es crea la sala i carreguem la escena de la sala d'espera.
Des d'ara en endavant, utilitzarem Emit to Socket sovint. Això es deu al fet que és la nostra principal manera d'enviar dades al servidor. Pensa en això simplement com la versió "en línia" de Broadcast Message i Receive Message (Emit to Socket realitza ambdues accions alhora).
Ara, necessitem afegir una mica d'informació al nostre servidor perquè creï la sala un cop rep el missatge del nostre comportament Emit.
socket.on('createRoom', (roomName, callback) => {
Aquesta línia crea un esdeveniment socket (com una expressió lambda) al servidor, escoltant per Emits amb l'esdeveniment 'createRoom'.
El primer paràmetre és el valor que enviem a través del comportament Emit to Socket, en aquest cas el nom de la sala que l'usuari va escriure. Aquí, hem nomenat aquest paràmetre 'roomName'.
El segon paràmetre és una funció que cridem més tard a l'esdeveniment socket per senyalar el client. El comportament Emit to Socket només continuarà amb l'execució si la funció de devolució es crida al servidor. Aquí, hem nomenat aquest paràmetre 'callback'.
const room = {
id: uuid(),
name: roomName,
sockets: []
};
Això crearà una instància d'una estructura que contindrà informació essencial sobre una sala.
'id' serà un identificador únic generat per la funció utilitària 'uuid()'. Això ens ajudarà a identificar aquesta sala específicament contra una llista de moltes altres sales creades més tard.
'name' s'establirà en el nom que l'usuari va escriure anteriorment.
'sockets' s'inicialitzarà com un array buit. Més tard, aquest farà un seguiment dels sockets dels jugadors connectats a aquella sala.
rooms[room.id] = room;
'rooms' és una llista global de les sales que estan actualment actives. Com estem creant una nova sala, l'emmagatzemarem a la llista pel seu ID.
joinRoom(socket, room);
Això cridarà la funció global 'joinRoom' (linia 29), que afegirà el socket del jugador a l'array 'sockets' de la sala. Com que el jugador va crear la sala, això també farà que s'uneixi a ella, també.
callback();
});
Uniu-se a les sales
Unir-se a les sales és una mica diferent ja que haurem d'accedir a la informació d'una escena diferent des del botó.
Per a unir-se a sales, hem col·locat l'inici dels nostres comportaments a la nostra capa global juntament amb on connectem al servidor. D'aquesta manera, podem accedir a la informació des de qualsevol de les nostres escenes, en aquest cas la nostra llista de sales. Per al botó, tocar-lo carregaria simplement el jugador a l'escena de la llista de sales.
Aquí tenim el nostre segon comportament per comunicar-nos amb el servidor. L'esdeveniment Socket es pot pensar com a Receive Message ja que només s'activarà un cop el missatge establert hagi estat transmès des del servidor.
La millor manera de pensar quan s'utilitza Socket Event i Emit to Socket és que Socket Event reacciona només a la informació que ve del servidor, mentre que Emit to Socket es crida en reacció a les coses fetes localment.
Aleshores, establirem una etiqueta que el jugador podrà tocar per entrar.
Aquesta és la nostra escena de Llista de Sales. Aquí és on es carregaran i es mostraran totes les sales de joc disponibles. Només cal tocar el nom de la sala per carregar el jugador dins. Gràcies als nostres comportaments anteriors, la llista es carregarà automàticament qualsevol sala oberta i generarà les etiquetes dels noms de les sales a la pantalla. Si no apareix res, tenim el botó de Refresh List que simplement repeteix els comportaments per carregar-los una vegada més.
Aquí tenim la lògica per configurar la llista. No entrarem en massa detall aquí perquè només volem saber com això es connecta amb el nostre servidor.
Per començar, tenim el nostre comportament Emit to Socket. Això demana al servidor que enviï informació sobre les sales disponibles. A partir d'allà, tenim un comportament Get Array Value. Totes les dades que es reben del servidor es poden enviar com a array i la informació necessària estarà a les primeres valors. Així que, establirem el nostre Get Array Value per obtenir el valor a l'índex 0. A partir d'aquí, els nostres comportaments extreuran les dades i crearan una etiqueta per a cada sala, generant-les a la nostra pantalla.
A continuació, comprovarem l'objecte al qual hem de tocar, en la nostra escena és l'etiqueta anomenada Nom de la Sala.
Aquest text actua com al nostre botó quan es genera, però encara necessitem adjuntar l'ID de la sala a la qual volem connectar. Per fer-ho, primer hem d'agafar l'ID de la sala, i ho fem amb el comportament Get Attribute i l'establim a dinàmic. A continuació, emetem al servidor que volem unir-nos a aquesta sala i carregar a la sala d'espera.
socket.on('joinRoom', (roomId, callback) => {
Aquest és el punt d'entrada per a l'esdeveniment socket 'joinRoom'. El valor del primer paràmetre seria l'ID de la sala a la qual volem unir-nos que va ser emès al servidor. Aquí, hem nomenat el paràmetre 'roomId'.
const room = rooms[roomId];
joinRoom(socket, room);
Utilitzant l'roomId donat pel client, podem trobar la instància correcta de la sala al servidor. Amb això, invocarem la funció global 'joinRoom' (linia 29) amb el socket del jugador que desitja connectar-se, i la instància de la sala mateix. Ara, veurem la funció 'joinRoom' en un moment.
callback();
});
Finalment, invoquem la funció de devolució per notificar al client que continuï càrrega de l'escena de la sala d'espera, marcant el final de l'esdeveniment socket 'joinRoom'.
Així que, què està passant exactament en la funció 'joinRoom'? Vegem-ho.
room.socket.push(socket);
Com s'ha mencionat anteriorment, l'array 'room.socket' fa un seguiment dels sockets connectats en una sala. Aquesta línia fa exactament això afegint el socket a l'array.
socket.join(room.id, () => {
socket.roomId = room.id;
console.log(socket.id, "Uniu", room.id);
});
Aquesta és la crida oficial per connectar el client a una sala. Primer diem al socket que s'unix a una sala pel seu ID. Quan això s'ha completat, s'invoque la devolució següent, on adjuntem l'ID de la sala al socket. Finalment, registrem a la consola que un jugador s'ha unit a una sala!
La sala d'espera
La sala d'espera és simplement una escena a la qual carreguem els jugadors mentre esperen que altres jugadors s'unin, o que un joc comenci.
En entrar a la sala, emitirem un missatge 'ready' al servidor. Un cop el servidor hagi rebut dos d'aquests, enviarà un missatge que indica que està començant el joc ('initGame'). Captatem aquest missatge amb el nostre Socket Event i així, carregarem el nivell del nostre joc. Pel que fa als nostres comportaments, això és tot. Pots afegir un botó que et desconnecti de la sala i et retorni al menú principal si ho desitges.
Al costat del servidor, analitzem el codi per veure què està passant allà.
Això és l'esdeveniment 'ready' que es crida quan un client s'ha unit a una sala i està a punt de connectar-se.
const room = rooms[socket.roomId];
Com que hem adjuntat l'ID de la sala al socket, podem obtenir la sala per comprovar si el joc pot començar.
if (room.sockets.length == 2) {
Aquí comprovem si ja hi ha dos jugadors esperant a la sala. Suposem que això és cert, i continuem per iniciar el joc.
for (const client of room.sockets) {
client.emit('initGame');
}
Ara que hi ha dos jugadors, iterem a través de cada socket i emetem l'esdeveniment 'initGame' perquè cada client carregui l'escena de nivell, tal com s'ha mostrat anteriorment.
Part 5: Jugabilitat
Ara, per entrar en la matèria. Aquest és on es destinen el 90% del nostre treball. A continuació es mostra el nivell del nostre joc dissenyat per a aquest tutorial.
Abans d'entrar a això, però, tenim una etiqueta titulada "Lògica del Joc", obrim-la i vegem-ho.
Uau, això és un munt de comportaments! No et preocupis, simplement és com fem aparèixer els nostres jugadors al joc. Fem una mirada més a prop;
Comencem amb un comportament Emit to Socket que comunica al servidor que el nostre joc ha començat, amb l'esdeveniment 'startGame'. A continuació, agafem l'array que el servidor retorna, prenem el seu primer valor i, amb el Get Dictionary Value, agafem els diversos atributs que el nostre objecte necessitarà. Un arbre separat fa el mateix, excepte per al jugador oposat. Finalment, emetem el missatge 'init' al nostre objecte jugador per iniciar les coses.
Vegem què passa al costat del servidor quan emetem l'esdeveniment 'startGame'.
La primera meitat d'aquest esdeveniment socket que veus aquí estableix alguns valors inicials a cada client, després afegint qualsevol client que no sigui el client emissor a l'array local 'others'.
En la segona meitat, es crea una llista de diccionari local anomenada 'ack'. En ell, tenim informació sobre nosaltres mateix, i els altres clients. A continuació, enviem aquesta informació de tornada al client passant el nostre diccionari 'ack' a la funció de devolució, que es converteix en el valor resultant del comportament Emit to Socket que s'emetrà.
A continuació, es fa una crida de temporització de 5 segons per començar finalment la ronda, ara que tothom té tota la informació que necessita per jugar el joc. La funció 'beginRound' (linia 99) controla una lògica específica del joc per a aquest projecte. No entrarem en massa detalls sobre això, però essencialment gestiona on fer aparèixer els jugadors, controla puntuacions i, a més, informa als clients qui és l'"Eligible".
Com s'ha esmentat abans, el missatge 'init' es crida a la nostra etiqueta "Lògica del Joc" quan tot està a punt. Al objecte jugador, ara mirarem els comportaments per on rep el missatge 'init'.
Aquí podeu veure que tenim diversos arbres de comportaments al nostre jugador. En primer lloc, començarem amb l'arbre de la part superior esquerra.
Aquest és el comportament que pràcticament activa tot el que hi ha al nostre joc.
Primer, rebem el missatge 'init' enviat des de l'etiqueta "Lògica del Joc". A partir d'aquí, agafem l'ID del servidor del nostre objecte i activem un dels Socket Events així com configurant la nostra pantalla de joc perquè puguem seguir amb precisió el nostre personatge.
Sincronització de Moviment
Aquí tenim un dels nostres comportaments més importants. Aquest petit arbre està dissenyat per actualitzar la posició del nostre jugador al servidor cada vegada que movem la palanca. Probablement has notat que també fa referència a alguns valors del diccionari. Aconseguim aquests valors d'un comportament de diccionari autònom que conté les posicions X i Y del nostre jugador.
Vegem l’esdeveniment 'moved' al servidor.
data = JSON.parse(data);
Els diccionaris, quan es fan servir per enviar-se d'un client a un servidor, han de ser analitzats per llegir les dades fàcilment. Això es deu al fet que els diccionaris a hyperPad es codifiquen en una estructura JSON quan s'emeten a un servidor. Aquesta línia analitza l'estructura de cadena JSON i emmagatzema la llista del diccionari de nou a la mateixa variable local 'data'.
socket.x = data.x;
socket.y = data.y;
Aquí, actualitzem les posicions X i Y emmagatzemades al socket amb els nous valors donats pel client.
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
});
}
A continuació, iterem a través de tots els clients per actualitzar-los sobre la nostra nova posició i altres detalls, excepte nosaltres mateixos (és a dir, el client emissor no necessita conèixer la seva pròpia posició).
Aquest arbre controla la major part del nostre joc. Utilitzem un Socket Event quan el servidor està verificació quin jugador està etiquetat com "És A", que es transmet mitjançant 'beginRound' (linia 99) al servidor.
Després, agafem l'ID del servidor del nostre objecte de l'array i utilitzem el Dictionary Value per desglossar-lo en les diverses peces de dades que conté. A partir d'aquí, agafem la nostra puntuació, el si estem etiquetats com "Es A", així com les posicions x i y del nostre objecte i les apliquem als atributs de l'objecte. La resta dels comportaments són per configurar i controlar la interfície d'usuari al nostre joc.
Conclusió
Hi havia molt a absorbir aquí, però esperem que si vas arribar fins aquí, hauràs d'entendre com utilitzar els comportaments de Socket.io per crear experiències de multijugador per als teus jugadors.
Prova-ho tu mateix! Agafa un joc existent que hagis creat i intenta donar-li alguna funcionalitat en línia, com ara una escena de tauler de puntuacions que connecti amb un servidor i sol·liciti una llista dels 10 millors puntuacions amb noms de jugadors per mostrar.
És difícil ensenyar un llenguatge de guions com Javascript en un sol article. Afortunadament, si tens problemes hi ha molts altres recursos per ajudar-te a escriure aplicacions Javascript per a Node.JS i Socket.io;
Aprendre Javascript - https://developer.mozilla.org/bm/docs/Web/JavaScript
Aprendre Socket.io - https://socket.io/docs/
Aprendre Node.JS - https://nodejs.org/en/docs/

