Impostazione di un gioco multiplayer con Socket.io | hyperPad Documentation

Loading...

Logo

z-f2VkDQ.jpg

Parte 1: Introduzione

Prerequisiti Preferiti

In questo tutorial, ci concentreremo su come impostare un server e connettere il tuo gioco ad esso in modo da poter abilitare le funzioni multiplayer per il tuo progetto tramite i comportamenti di Socket.io. Tratteremo funzionalità avanzate e comportamenti in hyperPad ed è fortemente consigliato rivedere prima alcuni altri tutorial e avere una buona familiarità con il software prima di continuare.

Questo tutorial presume che tu abbia una buona comprensione delle funzionalità di base di hyperPad, così come una comprensione minima della programmazione scritta, poiché ci concentreremo sulla scrittura di script in Javascript per costruire un server di gioco. Questo tutorial presupporrà anche alcune conoscenze di base sul networking e le differenze tra un server e i suoi client.

Il principale obiettivo di questo tutorial è insegnarti cosa devi fare per aggiungere funzionalità multiplayer al tuo gioco; non per creare il gioco stesso. Esploreremo la relazione tra i client di Socket.io e il server e come comunicano tra loro attraverso i comportamenti di Socket.io.

Requisiti

Per creare e ospitare il server, avrai bisogno di un computer capace di eseguire Node.JS (Mac, Windows, ecc.). L'installazione di Node.JS e la sua configurazione per il nostro scopo saranno trattate in questo tutorial. Se stai ospitando un server nella tua rete domestica, dovrai probabilmente modificare le impostazioni di port-forwarding del tuo gateway se desideri avere connessioni in entrata dall'esterno della tua rete locale.

Per questo tutorial, abbiamo già creato un semplice gioco dimostrativo. In questo, abbiamo due giocatori che giocano a una partita di acchiapparello in un piccolo labirinto. Esploreremo come i comportamenti di Socket.io sono utilizzati nel progetto di esempio, quindi è consigliato scaricare e aprire il progetto in hyperPad.

Il progetto di esempio hyperPad completato può essere scaricato qui: Tutorial multiplayer tag.tap

Panoramica

In questo tutorial, passeremo attraverso le basi della creazione di un server per il tuo gioco. Il server gestirà la maggior parte dei dettagli del gioco come punteggi, lobby di gioco, ecc. Quindi creeremo comportamenti che inviano informazioni dal nostro gioco al server e viceversa.

Ecco il flusso generale del nostro gioco:

1. Dal menu principale, ci connetteremo al nostro server e lasceremo all'utente decidere se creare o unirsi a un gioco.

a. Se si crea un gioco, verrà caricata la sala d'attesa.

b. Se ci si unisce a un gioco, verrà caricata una lista delle stanze di gioco disponibili.

c. Quando due giocatori si trovano in una sala d'attesa, il gioco inizia.

2. In un gioco, il server posizionerà casualmente i giocatori in una delle quattro aree e assegnerà a uno di essi il ruolo di "It".

a. Acchiappare l'altro giocatore randomizzerà le loro posizioni, cambierà lo stato di It e aggiungerà un punto al giocatore che ha acchiappato l'altro.

b. Se non avvengono acchiappamenti entro un certo periodo di tempo, il server rimuoverà 1 punto dal giocatore che attualmente è It e cambierà lo stato di It posizionando nuovamente i giocatori nelle zone di spawn.

3. Una volta che un giocatore ha raggiunto un certo numero di punti, verrà caricata un'overlay che dichiara un vincitore. Poi, i giocatori si disconnetteranno dalla stanza e torneranno al menu principale.

Parte 2: Impostare il Server

Creare un server Socket.io richiede di eseguire un'applicazione Javascript utilizzando la libreria Socket.io, in ascolto per le connessioni dei giocatori. Socket.io è una libreria di networking in Javascript che semplifica molti degli aspetti sottostanti nella costruzione di un'applicazione in rete per noi. Maggiori dettagli seguiranno.

Scaricare e Installare Node.JS

Per iniziare, vai su Node.JS e scaricalo sul computer (Mac, Windows, ecc.) su cui desideri ospitare il server. Al termine del download, esegui l'installatore e segui le sue istruzioni. Tutte le opzioni durante l'installazione possono essere lasciate di default. Node.JS ci dà la possibilità di eseguire applicazioni Javascript da sole senza bisogno di un browser web.

Scaricamento ed Esecuzione dell'Esempio di Server

Successivamente, scarica l'Esempio di Server Multiplayer di questo tutorial su GitHub:

https://github.com/hyperPad/multiplayerServerExample

Clicca sul pulsante "Clone or download" e seleziona "Download ZIP". Questo scaricherà una copia del codice per l'esempio del server.

chrome_2019-08-05_14-22-14.png

Estrai il file ZIP scaricato. All'interno troverai alcuni piccoli file, ma il file più notable qui è il file "index.js" che contiene il codice per il nostro server. Successivamente, apri il tuo prompt dei comandi/terminal dentro la cartella dell'esempio del server.

Digita "npm install" e premi invio, quindi lascia che esegua. npm è un gestore di pacchetti che legge il file package.json e scarica i pacchetti necessari per il nostro server. (Incluso Socket.io!)

cmd_2019-08-05_14-55-19.png

Quando il comando è terminato, abbiamo tutto il necessario per eseguire il server. Nel terminale, digita "node ." e il server si avvierà.

cmd_2019-08-05_15-02-38.png

Questo è tutto! Il nostro server ora è in ascolto sulla porta 3000 per le connessioni Socket.io in arrivo.

"node ." avvia Node.JS per la directory corrente, dove cercherà il file indice della directory e lo eseguirà. Per impostazione predefinita, questo è il file Javascript "index.js", in cui è situata la maggior parte del codice del nostro server e che sarà analizzata durante questo tutorial.

Lascia il terminale aperto, poiché chiuderlo chiuderà anche il server. Vedrai stampare messaggi quando i giocatori si connettono o si disconnettono e quando le stanze vengono create o distrutte, e altri eventi.

Nota: Per consentire connessioni in arrivo dall'esterno di una rete domestica, dovrai probabilmente aprire la porta 3000 sul tuo gateway domestico. Questo processo varia da rete a rete, ma una guida può solitamente essere trovata effettuando una ricerca su internet per una guida al port-forwarding per il modem/router della tua casa. Potrebbe essere necessario chiudere e riavviare il server Node.JS quando modifichi le impostazioni di port-forwarding.

Parte 3: Connessione al Server

Una volta che hai avviato le basi del server, possiamo ora connetterci ad esso in un gioco hyperPad. Questo dovrebbe essere impostato per essere la prima cosa che accade nel tuo progetto indipendentemente dalla scena in cui ti trovi (poiché tutto deve comunicare con il server). È meglio allegare questo comportamento a un oggetto sul Livello Globale in modo che si applichi a tutte le scene.

Untitled.jpg

Nel progetto di esempio, l'etichetta "Server" del Livello Globale contiene quanto sopra. (Il download per il progetto può essere trovato nella Parte 1 di questo tutorial nella sezione Requisiti.)

Questi due comportamenti sono davvero tutto ciò di cui hai bisogno per connetterti al server. Prima di tutto, sotto la scheda personalizzata, prendi il comportamento Client di Socket.io e posizionalo. Qui, inserirai l'URL del tuo server sotto la scheda URL nella finestra delle proprietà. Nell'immagine sopra, l'URL del nostro server era "http://192.168.0.191:3000", incluso protocollo e porta. Vuoi cambiare questo per farlo corrispondere al tuo URL del server o probabilmente non funzionerà quando avvii il gioco.

Ora abbiamo le informazioni del server di cui abbiamo bisogno, ma dobbiamo ancora connetterci ad esso. Quindi, tutto ciò che ci serve è il comportamento Connetti a Socket. Posiziona uno, apri le sue proprietà e seleziona il client nella casella vuota e imposta la proprietà Funzione su "Connetti".

Ora, quando il nostro progetto si carica, ci connetteremo automaticamente al nostro server.

mceclip1.png

Parte 4: Creazione e Unione a Stanze

Il passo successivo è creare le nostre lobby di gioco dove i giocatori possono unirsi e giocare insieme.

mceclip2.png

Ecco il nostro semplice schermo del Menu Principale. Tutto ciò che devi fare è toccare uno dei pulsanti per iniziare una stanza o cercare stanze disponibili.

Creazione delle Stanze

Controlliamo come comunicare con il server per creare la nostra stanza.

mceclip3.png

Piuttosto semplice, vero? Quindi, per rivedere rapidamente, una volta che tocchiamo il nostro pulsante ci verrà chiesto di digitare un nome per la stanza. Una volta fatto, quel nome verrà emesso al server dove la stanza verrà creata e caricheremo la scena della sala d'attesa.

Da qui in poi, useremo spesso Emit to Socket. Questo perché è il nostro modo principale di inviare dati al server. Pensalo semplicemente come la versione "online" di Broadcast Message e Receive Message (Emit to Socket esegue entrambe le azioni contemporaneamente).

Ora, dobbiamo aggiungere alcune informazioni al nostro server in modo che crei la stanza non appena riceve il messaggio dal nostro comportamento Emit.

mceclip4.png

socket.on('createRoom', (roomName, callback) => {

Questa riga crea un evento socket (come espressione lambda) sul server, in ascolto per Emits con l'evento 'createRoom'.

Il primo parametro è il valore che passiamo attraverso il comportamento Emit to Socket, in questo caso il nome della stanza digitato dall'utente. Qui abbiamo nominato quel parametro 'roomName'.

Il secondo parametro è una funzione che chiamiamo più tardi nell'evento socket per segnalare al client. Il comportamento Emit to Socket continuerà l'esecuzione solo se la funzione di callback viene chiamata sul server. Qui, abbiamo nominato quel parametro 'callback'.

const room = {
id: uuid(),
name: roomName,
sockets: []
};

Questo creerà un'istanza di una struttura che conterrà informazioni essenziali su una stanza.

'id' sarà un identificatore unico generato dalla funzione utility 'uuid()'. Questo ci aiuterà a identificare specificamente questa stanza rispetto a un elenco di molte altre stanze create in seguito.

'name' sarà impostato sul nome che l'utente ha digitato in precedenza.

'sockets' sarà inizializzato come un array vuoto. In seguito, questo terrà traccia dei socket dei giocatori connessi a quella stanza.

rooms[room.id] = room;

'rooms' è un elenco globale delle stanze attualmente attive. Poiché stiamo creando una nuova stanza, la memorizzeremo nell'elenco tramite il suo ID.

joinRoom(socket, room);

Questo chiamerà la funzione globale 'joinRoom' (riga 29), che aggiungerà il socket del giocatore all'array 'sockets' della stanza. Poiché il giocatore ha creato la stanza, questo lo farà unire ad essa, anche.

callback();
});

Infine, invochiamo la funzione di callback in modo che il client venga informato che la stanza è stata creata. Questo permetterà a Emit to Socket di continuare l'esecuzione, il che caricherà la scena della Sala d'Attesa successivamente. Questo segna anche la fine dell'evento socket 'createRoom'.

Unirsi alle Stanze

Unirsi alle stanze è un po' diverso poiché dovremo accedere alle informazioni da una scena diversa rispetto al pulsante.

mceclip5.png

Per unirci alle stanze, abbiamo posizionato l'inizio dei nostri comportamenti sul nostro livello globale insieme a dove ci connettiamo al server. In questo modo possiamo accedere alle informazioni da tutte le nostre scene, in questo caso la nostra lista di stanze. Per il pulsante, toccarlo semplicemente caricherà il giocatore nella scena della lista delle stanze.

Qui abbiamo il nostro secondo comportamento per comunicare con il server. L'Evento Socket può essere pensato come Ricevere Messaggi poiché attiverà solo una volta che il messaggio impostato sarà stato trasmesso dal server.

Il modo migliore per pensare all'utilizzo di Socket Event ed Emit to Socket è che Socket Event reagisce solo alle informazioni provenienti dal server, mentre Emit to Socket è chiamato in reazione a ciò che viene fatto localmente.

Per quanto riguarda la nostra logica, una volta connessi al server, emetteremo la richiesta 'getRoomNames' per ottenere eventuali nomi di stanze disponibili.

mceclip19.png

Quindi, imposteremo un'etichetta su cui il giocatore può toccare per entrare.

mceclip6.png

Questa è la nostra scena della Lista Stanze. Qui è dove verranno caricate e visualizzate eventuali stanze di gioco disponibili. Basta toccare il nome della stanza per caricare il giocatore. Grazie ai nostri comportamenti precedenti, l'elenco caricherà automaticamente tutte le stanze aperte e genererà le etichette dei nomi delle stanze sullo schermo. Se non compare nulla, abbiamo il nostro pulsante Aggiorna Lista che ripete semplicemente i comportamenti per caricarle di nuovo.

mceclip7.png mceclip8.png

Qui abbiamo la logica per impostare l'elenco. Non entreremo troppo nei dettagli qui perché vogliamo solo sapere come questo si collega con il nostro server.

Per iniziare, abbiamo il nostro comportamento Emit to Socket. Questo richiede al server di inviare informazioni sulle stanze disponibili. Da lì, abbiamo un comportamento Get Array Value. Tutti i dati che provengono dal server verranno inviati come un Array e le informazioni necessarie saranno nei primi valori. Quindi, impostiamo il nostro Get Array Value per ottenere il valore all'indice 0. Da lì, i nostri comportamenti estrarranno quei dati e creeranno un'etichetta per ogni stanza, generandole sul nostro schermo.

Successivamente, andremo a controllare l'oggetto che dobbiamo toccare, nella nostra scena è l'etichetta chiamata Nome Stanza.

mceclip9.png

Questo testo funge da nostro pulsante quando viene generato, ma dobbiamo ancora allegare l'ID della stanza a cui vogliamo connetterci. Per fare ciò, dobbiamo prima ottenere l'ID della stanza, e lo facciamo con il comportamento Get Attribute e impostandolo su dinamico. Quindi, emettiamo al server che vogliamo unirci a questa stanza e caricare nella sala d'attesa.

mceclip10.png

socket.on('joinRoom', (roomId, callback) => {

Questo è il punto di ingresso per l'evento socket 'joinRoom'. Il valore del primo parametro sarebbe l'ID della stanza a cui vogliamo unirci che è stato emesso al server. Qui, abbiamo nominato il parametro 'roomId'.

const room = rooms[roomId];
joinRoom(socket, room);

Usando l'roomId fornito dal client, possiamo trovare l'istanza della stanza corretta sul server. Con questo, invochiamo la funzione globale 'joinRoom' (riga 29) con il socket del giocatore che desidera connettersi e l'istanza della stanza stessa. Daremo un'occhiata alla funzione 'joinRoom' tra poco.

callback();
});

Infine, invochiamo la funzione di callback per informare il client di continuare a caricare la scena della Sala d'Attesa, marcando la fine dell'evento socket 'joinRoom'.

Quindi, cosa sta succedendo esattamente nella funzione 'joinRoom'? Diamo un'occhiata.

mceclip11.png

room.socket.push(socket);

Come accennato in precedenza, l'array membro 'room.socket' tiene traccia dei socket connessi in una stanza. Questa riga fa proprio questo, spingendo il socket nell'array.

socket.join(room.id, () => {
socket.roomId = room.id;
console.log(socket.id, "Unito a", room.id);
});

Questa è la chiamata ufficiale per connettere il client a una stanza. Prima diciamo al socket di unirsi a una stanza tramite il suo ID. Quando ciò è completato, viene invocata la callback successiva, dove alleghiamo l'ID della stanza al socket. Infine, registriamo nella console che un giocatore si è unito a una stanza!

mceclip12.png

La Sala d'Attesa

La sala d'attesa è semplicemente una scena in cui carichiamo i giocatori mentre aspettano che altri giocatori si uniscano o che inizi un gioco.

mceclip13.png

All'ingresso della stanza, emetteremo un messaggio 'ready' al server. Una volta che il server ha ricevuto due di questi, invierà un messaggio che sta per iniziare il gioco ('initGame'). Catturiamo quel messaggio con il nostro Evento Socket e quindi, carichiamo il nostro livello di gioco. Per i nostri comportamenti, è tutto. Puoi aggiungere un pulsante che ti disconnette dalla stanza e ti riporta al menu principale se vuoi.

Dal lato server, analizziamo il codice per vedere cosa sta succedendo lì.

mceclip14.png

Questo è l'evento 'ready' che viene chiamato quando un client si è unito a una stanza ed è pronto a connettersi.

const room = rooms[socket.roomId];

Poiché abbiamo allegato l'ID della stanza al socket, siamo in grado di ottenere la stanza per controllare se il gioco può iniziare.

if (room.sockets.length == 2) {

Qui controlliamo se ora ci sono due giocatori in attesa nella stanza. Supponiamo che ora sia vero, e procediamo ad avviare il gioco.

for (const client of room.sockets) {
client.emit('initGame');
}

Ora che ci sono due giocatori, iteriamo attraverso ogni socket ed emettiamo l'evento 'initGame' in modo che ogni client carichi la scena di Livello, come mostrato in precedenza.

Parte 5: Giocabilità

Ora, possiamo entrare nel vivo delle cose. Questo è dove andrà il 90% del nostro lavoro. Di seguito è la nostra scena di gioco progettata per questo tutorial.

mceclip15.png

Prima di addentrarci in questo però, abbiamo un'etichetta intitolata "Logica di Gioco", diamo un'occhiata.

mceclip16.png

Wow, ci sono tanti comportamenti! Non preoccuparti però, questo è semplicemente come facciamo a generare i nostri giocatori nel gioco. Diamo un'occhiata più da vicino;

mceclip17.png

Iniziamo con un comportamento Emit to Socket che informa il server che il nostro gioco è iniziato, con l'evento 'startGame'. Quindi prendiamo l'array che il server restituisce, prendiamo il suo primo valore e con Get Dictionary Value, prendiamo i vari attributi di cui il nostro oggetto avrà bisogno. Un albero separato fa lo stesso, tranne che per il giocatore avversario. Infine, trasmettiamo il messaggio 'init' al nostro oggetto giocatore per iniziare le cose.

Vediamo cosa sta succedendo sul lato server quando emettiamo l'evento 'startGame'.

mceclip20.png

La prima metà di questo evento socket che vedi qui sta impostando alcuni valori iniziali su ciascun client, quindi aggiungendo qualsiasi client che non è il client che emette nell'array locale 'others'.

mceclip21.png

Nella seconda metà, viene creata una lista di dizionari locale chiamata 'ack'. In essa abbiamo informazioni su di noi, e gli altri client. Inviamo quindi queste informazioni indietro al client passando il nostro dizionario 'ack' alla funzione di callback, che diventa il valore risultante del comportamento Emit to Socket.

Successivamente, viene effettuata una chiamata di timeout di 5 secondi per iniziare finalmente il turno, ora che tutti hanno tutte le informazioni di cui hanno bisogno per giocare. La funzione 'beginRound' (riga 99) controlla alcune logiche specifiche del gioco per questo progetto. Non entreremo troppo nei dettagli, ma fondamentalmente gestisce dove spawnare i giocatori, controlla i punteggi e dice ai client chi è "It".

Come accennato in precedenza, il messaggio 'init' viene chiamato sulla nostra etichetta "Logica di Gioco" quando tutto è pronto. Ora, nell'oggetto giocatore, daremo un'occhiata ai comportamenti su di esso dove riceve il messaggio 'init'.

mceclip22.png

Qui puoi vedere che abbiamo diversi alberi di comportamento sul nostro giocatore. Per cominciare, riceviamo il messaggio 'init' inviato dall'etichetta "Logica di Gioco". Da lì, prendiamo l'ID del server del nostro oggetto e attiviamo uno dei nostri Eventi Socket, nonché impostiamo la nostra schermata di gioco in modo da poter seguire accuratamente il nostro personaggio.

Sincronizzazione del Movimento

mceclip24.png

Qui c'è uno dei nostri comportamenti più importanti. Questo piccolo albero è progettato per aggiornare la posizione del nostro giocatore sul server ogni volta che muoviamo la levetta. Probabilmente hai notato che sta anche facendo riferimento a dei valori di dizionario. Otteniamo quei valori da un comportamento dizionario a sé stante che contiene le posizioni X e Y del nostro giocatore.

Vediamo l'evento 'moved' sul server.

mceclip25.png

data = JSON.parse(data);

I dizionari, quando inviati da un client a un server, devono essere analizzati per poter leggere i dati facilmente. Questo perché i dizionari in hyperPad vengono codificati in una struttura JSON quando emessi a un server. Questa riga analizza la struttura JSON e memorizza l'elenco del dizionario di nuovo nella stessa variabile locale 'data'.

socket.x = data.x;
socket.y = data.y;

Qui, aggiorniamo le posizioni X e Y memorizzate sul socket con i nuovi valori forniti dal 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
});
}

Successivamente, iteriamo attraverso tutti i client per aggiornarli sulla nostra nuova posizione e altri dettagli, escludendo noi stessi (cioè, il client che emette non ha bisogno di conoscere la propria posizione).

mceclip26.png

Questo albero qui controlla la maggior parte del nostro gioco. Utilizziamo un Evento Socket quando il server controlla quale giocatore è contrassegnato come It, il quale è emesso da 'beginRound' (riga 99) sul server.

Quindi, otteniamo l'ID del server del nostro oggetto dall'array e utilizziamo il Dictionary Value per suddividerlo nei vari pezzi di dati che contiene. Da lì, otteniamo il nostro punteggio, se siamo contrassegnati come It, così come le posizioni x e y del nostro oggetto e le applichiamo agli attributi dell'oggetto. Il resto dei comportamenti serve per impostare e controllare l'interfaccia utente nel nostro gioco.

Conclusione

C'era molto da assorbire qui, ma speriamo che se sei arrivato fin qui dovresti avere una comprensione di come utilizzare i comportamenti di Socket.io per creare esperienze multiplayer per i tuoi giocatori.

Provalo tu stesso! Prendi un gioco esistente che hai creato e prova a dargli qualche funzionalità online, come una scena di classifica che si connette a un server e richiede un elenco dei primi 10 punteggi con i nomi dei giocatori da visualizzare.

È difficile insegnare un linguaggio di scripting come Javascript in un solo articolo. Fortunatamente, se hai problemi, ci sono molte altre risorse per aiutarti a scrivere applicazioni Javascript per Node.JS e Socket.io;

Impara Javascript - https://developer.mozilla.org/bm/docs/Web/JavaScript

Impara Socket.io - https://socket.io/docs/

Impara Node.JS - https://nodejs.org/en/docs/