Setting up a multiplayer game with Socket.io
Del 1: Introduksjon
Foretrukne forutsetninger
I denne opplæringen skal vi fokusere på hvordan vi setter opp en server og kobler spillet ditt til den, slik at du kan aktivere flerspillerfunksjoner for prosjektet ditt via Socket.io-atferd.Vi vil dekke avanserte funksjoner og atferd i hyperPad, og det er sterkt anbefalt å gjennomgå noen andre opplæringer først, og ha en god forståelse av programvaren før du fortsetter.
Denne opplæringen forutsetter at du har en god forståelse av hyperPads kjernefunksjonalitet samt en minimal forståelse av skriftlig programmering, da vi vil fokusere på Javascript-scripting for å bygge en spillserver. Denne opplæringen vil også anta en grunnleggende forståelse av nettverksarbeid og forskjellene mellom en server og dens klienter.
Målsetningen med denne opplæringen er å lære deg hva du trenger å gjøre for å legge til flerspillerfunksjonalitet i spillet ditt; ikke å lage selve spillet. Vi vil utforske forholdet mellom Socket.io-klienter og serveren, og hvordan de kommuniserer med hverandre med Socket.io-atferd.
Krav
For å opprette og hoste serveren, trenger du enPC som kan kjøre Node.JS (Mac, Windows, etc.). Installasjonen av Node.JS og oppsettet for vårt formål vil bli dekket i denne opplæringen. Hvis du hoster en server i hjemmenettverket ditt, må du sannsynligvis endre gatewayens portvideresendinginnstillinger hvis du ønsker å ha innkommende forbindelser fra eksterne nettverk.
I denne opplæringen har vi allerede laget et enkelt demospill. I det har vi to spillere som spiller et fangstspill i en liten labyrint. Vi vil utforske hvordan Socket.io-atferd brukes i eksempelprosjektet, så det anbefales at du laster ned og åpner prosjektet i hyperPad.
Det fullførte hyperPad-eksempelprosjektet kan lastes ned her: Multiplayer tag tutorial.tap
Oversikt
I denne opplæringen vil vi gå over det grunnleggende i å opprette en server for spillet ditt. Serveren vil håndtere de fleste detaljene i spillet som poengsummene, spill-lobbyene, osv. Deretter vil vi lage atferd som sender informasjon fra spillet vårt til serveren og omvendt.
Her er den generelle flyten av spillet vårt:
1. Fra hovedmenyen vil vi koble til serveren vår og la brukeren bestemme seg for å enten opprette eller bli med i et spill.
a. Hvis du oppretter et spill, last inn venterommet.
b. Hvis du blir med i et spill, last ned en liste over tilgjengelige spillrom.
c. Når to spillere er i venterommet, lanseres spillet.
2. I et spill vil serveren tilfeldig plassere spillerne i en av fire områder og tildele en av dem til å være "Det".
a. Å merke den andre spilleren vil randomisere plasseringen deres, bytte "Det"-status og legge til ett poeng til spilleren som merket den andre.
b. Hvis det ikke skjer noen merker innen en viss tidsperiode, vil serveren trekke fra 1 poeng fra spilleren som for øyeblikket er "Det" og bytte "Det"-status før spillere blir tilfeldig plassert igjen i spawn-sonene.
3. Når en spiller har nådd et bestemt antall poeng, vil det lastes inn et overlegg som erklærer en vinner. Deretter vil spillerne koble fra rommet og returnere til hovedmenyen.
Del 2: Sette opp serveren
Å opprette en Socket.io server krever at vi kjører en Javascript-applikasjon ved hjelp av Socket.io-biblioteket, som lytter etter spillerforbindelser. Socket.io er et Javascript-nettverksbibliotek som forenkler mye av arbeidet med å bygge en nettverksapplikasjon for oss. Mer informasjon kommer.
Last ned og installer Node.JS
For å komme i gang, gå til Node.JS og last ned den til datamaskinen (Mac, Windows, osv.) du ønsker å hoste serveren på. Når nedlastingen er fullført, kjør installasjonsprogrammet og følg instruksjonene. Alle alternativer under installasjonen kan forlates som standard. Node.JS gir oss muligheten til å kjøre Javascript-applikasjoner på egenhånd uten behov for en nettleser.
Last ned og kjør eksempelserveren
Neste steg er å laste ned denne opplæringsmengden av Multiplayer Server Example på GitHub:
https://github.com/hyperPad/multiplayerServerExample
Klikk på knappen "Klon eller last ned" og velg "Last ned ZIP". Dette vil laste ned en kopi av koden for servereksempelet.
Pakke ut den nedlastede ZIP-en. Inne finner du noen små filer, men den mest bemerkelsesverdige filen her er "index.js"-filen som inneholder koden for serveren vår. Neste steg åpner du kommandolinjen/terminalen inne i servereksempel-mappen.
Type "npm install" og trykk enter, så la det gå. npm er en pakkebehandler som leser package.json-filen og laster ned nødvendige pakker for serveren vår. (Inkludert Socket.io!)
Når kommandoen er fullført, har vi alt vi trenger for å kjøre serveren. I terminalen skriver du "node ." og serveren starter.
Det er alles! Serveren vår lytter nå på port 3000 for innkommende Socket.io-forbindelser.
"node ." starter Node.JS for den nåværende katalogen, hvor den vil lete etter katalogindeksen og kjøre den. Som standard er dette "index.js" Javascript-filen, som størstedelen av serverens kode er plassert i og vil bli analysert i løpet av denne opplæringen.
La terminalen være åpen, da det å lukke den også vil lukke serveren. Du vil se den skrive ut meldinger når spillere kobler seg til eller fra, og når rom opprettes eller ødelegges, samt andre hendelser.
Merk: For å tillate innkommende forbindelser fra eksterne nettverk, må du sannsynligvis åpne port 3000 på hjemme-gatewayen din. Denne prosessen varierer fra nettverk til nettverk, men en guide kan vanligvis finnes ved å gjøre et internett-søk etter en portvideingguide for modem/routeren din. Du kan måtte lukke og starte opp Node.JS-serveren på nytt når du endrer innstillinger for portvideresending.
Del 3: Koble til serveren
Når du har grunnleggende oppsett av serveren, kan vi nå koble til den i et hyperPad-spill. Dette bør settes opp til å være det første som skjer i prosjektet ditt, uansett hvilken scene du er på (siden alt må kommunisere med serveren). Det er best å knytte denne atferden til et objekt på Global Layer, slik at det gjelder for alle scener.
I eksempelprosjektet inneholder Global Layer "Server"-etiketten det ovenstående. (Nedlastingen av prosjektet finner du i Del 1 av denne opplæringen under krave-delen.)
Disse to atferdene er alt du trenger for å koble til serveren. Først under den egendefinerte fanen tar du Socket.io Client-atferden og drar den ned. Her skriver du inn serverens URL under URL-fanen i egenskapsvinduet. På bildet over var serverens URL på "http://192.168.0.191:3000", inkludert protokoll og port. Du ønsker å endre dette for å matche serverens URL, ellers vil det sannsynligvis ikke fungere når du starter spillet.
Nå har vi serverinformasjonen vi ønsker, men vi må fortsatt koble til den. Så, alt vi trenger er "Connect to Socket"-atferden. Dra en ned, åpne egenskapene og velg klienten i den tomme boksen, og sett Funksjonsegenskapen til "Connect".
Nå, når prosjektet vårt lastes, vil vi automatisk koble til serveren vår.
Del 4: Opprette og bli med i rom
Neste steg er å opprette spill-lobbyene våre hvor spillere kan bli med og spille sammen.
Her er vår enkle hovedmenyskjerm. Alt man trenger å gjøre er å trykke på en av knappene for enten å starte et rom eller se etter tilgjengelige rom.
Opprette rom
La oss sjekke hvordan vi går frem for å kommunisere med serveren for å opprette rommet vårt.
Relativt enkelt, ikke sant? Så for å gå raskt over dette, når vi berører knappen vår, blir vi bedt om å skrive inn et romnavn. Når det er gjort, blir det navnet sendt til serveren der rommet opprettes og vi laster opp venterommet.
Fra dette punktet vil vi ofte bruke Emit to Socket. Det er fordi det er vår viktigste måte å sende data til serveren på. Tenk på det som den "nettbaserte versjonen" av Send melding og Motta melding (Emit to Socket utfører begge handlingene samtidig).
Nå må vi legge til noe informasjon til serveren slik at den oppretter rommet når den mottar meldingen fra Emit-atferden vår.
socket.on('createRoom', (roomName, callback) => {
Denne linjen oppretter en socket-hendelse (som en lambda-uttrykk) på serveren, som lytter etter Emits med hendelsen 'createRoom'.
Den første parameteren er verdien vi sender gjennom Emit to Socket-atferden, i dette tilfellet romnavnet som brukeren skrev inn. Her har vi navngitt den parameteren 'roomName'.
Den andre parameteren er en funksjon som vi kaller senere i socket-hendelsen for å signalisere klienten. Emit to Socket-atferden vil bare fortsette utførelsen hvis tilbakemeldingsfunksjonen blir kalt på serveren. Her har vi navngitt den parameteren 'callback'.
const room = {
id: uuid(),
name: roomName,
sockets: []
};
Dette vil opprette en instans av en struktur som vil inneholde viktig informasjon om et rom.
'id' vil være en unik identifikator generert av verktøyfunksjonen 'uuid()'. Dette vil hjelpe oss med å spesifikt identifisere dette rommet i forhold til en liste over mange andre opprettede rom senere.
'name' vil bli satt til navnet som brukeren skrev inn tidligere.
'sockets' vil bli initialisert som et tomt array. Senere vil dette holde oversikt over spiller-sockets som er tilkoblet rommet.
rooms[room.id] = room;
'rooms' er en global liste over rom som for øyeblikket er aktive. Siden vi oppretter et nytt rom, vil vi lagre det i listen etter ID.
joinRoom(socket, room);
Denne vil kalle den globale funksjonen 'joinRoom' (linje 29), som vil legge spiller-socketen til rommets 'sockets'-array. Siden spilleren opprettet rommet, vil dette også få dem til å bli med i det.
callback();
});
Til slutt kaller vi tilbakemeldingsfunksjonen slik at klienten varsles om at rommet er opprettet. Dette vil tillate Emit to Socket å fortsette utførelsen, som deretter vil laste opp venteromscenen. Dette markerer også slutten av 'createRoom' socket-hendelsen.
Bli med i rom
Å bli med i rom er litt annerledes da vi må få tilgang til informasjon fra en annen scene fra knappen.
For å bli med i rom, har vi plassert begynnelsen av atferdene våre på den globale lageren samtidig som vi kobler til serveren. På denne måten kan vi få tilgang til informasjon fra noen av scenene våre, i dette tilfellet romlisten vår. For knappen vil det å trykke på den ganske enkelt laste spilleren inn i romlisten-scenen.
Her har vi vår andre atferd for å kommunisere med serveren. Socket Event kan sees på som Motta melding siden den bare vil aktiveres når den angitte meldingen har blitt sendt ut fra serveren.
Den beste måten å tenke på når du bruker Socket Event og Emit to Socket er at Socket Event reagerer kun på informasjon som kommer fra serveren, mens Emit to Socket kalles i reaksjon på ting som gjøres lokalt.
Når det gjelder logikken vår, når vi kobler til serveren, vil vi Emit '/' getRoomNames'-forespørselen for å få eventuelle tilgjengelige romnavn.
Deretter setter vi en etikett som spilleren kan trykke på for å komme inn.
Dette er vår Romliste-scene. Her er hvor eventuelle tilgjengelige spillrom vil lastes og vises. Ved ganske enkelt å trykke på romnavnet vil spilleren lastes inn. Takket være våre tidligere atferder, vil listen automatisk laste eventuelle åpne rom og spawne romnavn-etikettene på skjermen. Hvis ingenting vises, har vi vår Oppdater liste-knapp som ganske enkelt gjentar atferdene for å laste dem igjen.
Her har vi logikken for å sette opp listen. Vi vil ikke gå for mye i detalj her fordi vi bare ønsker å vite hvordan dette er koblet til serveren vår.
For å starte, har vi Emit to Socket-atferd. Dette ber serveren om å sende informasjon om tilgjengelige rom. Derfra har vi en Hent Array Value-atferd. All data som kommer fra serveren vil bli sendt som en Array, og informasjonen som trengs vil være på de første verdiene. Så vi setter Get Array Value til å få verdien ved indeks 0. Derfra vil atferdene våre hente den informasjonen og opprette en etikett for hvert rom, og spawne dem på skjermen vår.
Neste steg vil vi sjekke objektet som vi trenger å trykke på, i scenen vår er det etiketten kalt Romnavn.
Denne teksten fungerer som knappen vår når jeg spawner inn, men vi trenger fortsatt å knytte rom-ID-en som vi ønsker å koble til. For å gjøre dette, må vi først hente rom-ID-en, og det gjør vi med Hent Attributt-atferden og sette den til dynamisk. Så emiterer vi til serveren at vi ønsker å bli med dette rommet og laste inn venterommet.
socket.on('joinRoom', (roomId, callback) => {
Dette er inngangspunktet for socket-hendelsen 'joinRoom'. Den første parameterens verdi vil være rom-ID-en vi ønsker å bli med i som ble sendt til serveren. Her har vi navngitt den parameteren 'roomId'.
const room = rooms[roomId];
joinRoom(socket, room);
Ved å bruke roomId som gis av klienten, kan vi finne den riktige rominstansen på serveren. Med det kan vi kalle den globale funksjonen 'joinRoom' (linje 29) med socketen til spilleren som ønsker å koble til, og rominstansen selv. Vi skal ta en titt på 'joinRoom'-funksjonen om noen øyeblikk.
callback();
});
Til slutt kaller vi tilbakemeldingsfunksjonen for å varsle klienten om å fortsette lastingen av venterom-scenen, som markerer slutten av 'joinRoom' socket-hendelsen.
Så, hva skjer egentlig i 'joinRoom'-funksjonen? La oss ta en titt.
room.socket.push(socket);
Som nevnt tidligere, holder 'room.socket'-medlem-Arrayet oversikt over sockets tilknyttet til et rom. Denne linjen gjør akkurat det ved å skyve socketen inn i arrayet.
socket.join(room.id, () => {
socket.roomId = room.id;
console.log(socket.id, "Bli med", room.id);
});
Dette er det offisielle anropet for å koble klienten til et rom. Først ber vi socketen om å bli med i et rom etter ID. Når det er fullført, blir følgende tilbakekallingsfunksjon kalt, hvor vi knytter rom-ID-en til socketen. Til slutt, logger vi i konsollen at en spiller har blitt med i et rom!
Venterommet
Venterommet er ganske enkelt en scene som vi laster spillerne inn i mens de venter på at andre spillere skal bli med, eller for å starte et spill.
Når vi går inn i rommet, vil vi sende en 'klar'-melding til serveren. Når serveren har mottatt to av disse, vil den sende en melding om at den starter spillet ('initGame'). Vi plukker opp denne meldingen med vår Socket Event, og dermed kan vi laste opp spillnivået vårt. Når det gjelder atferdene våre, er det omtrent det. Du kan legge inn en knapp som vil koble deg fra rommet og sende deg tilbake til hovedmenyen hvis du ønsker det.
På serversiden, la oss analysere koden for å se hva som skjer der.
Dette er 'klar'-hendelsen som kalles når en klient har blitt med i et rom og er klar til å koble til.
const room = rooms[socket.roomId];
Siden vi har knyttet rom-ID-en til socketen, klarer vi å få rommet for å sjekke om spillet kan starte.
if (room.sockets.length == 2) {
Her sjekker vi for å se om det nå er to spillere som venter i rommet. La oss si det er sant, og vi fortsetter å starte spillet.
for (const client of room.sockets) {
client.emit('initGame');
}
Nå som det er to spillere, itererer vi gjennom hver socket og sender ut 'initGame'-hendelsen, slik at hver klient laster opp nivåscenen, som vist tidligere.
Del 5: Spillopplevelse
Nå, for å komme til kjernen av ting. Dette er hvor 90% av arbeidet vårt vil gå. Nedenfor er spillnivået vi har designet for denne opplæringen.
Før vi kommer til det, har vi en etikett med tittelen "Spilllogikk", la oss åpne den og ta en titt.
Wow, det er mange atferder! Ikke bekymre deg, dette er ganske enkelt hvordan vi spawner spillerne inn i spillet. La oss ta en nærmere titt;
Vi begynner med en Emit to Socket-atferd som forteller serveren at spillet vårt har startet, med hendelsen 'startGame'. Deretter henter vi arrayet serveren returnerer, tar første verdi og med Get Dictionary Value henter vi de forskjellige attributtene objektet vårt trenger. Et annet tre gjør det samme, bortsett fra for motspilleren. Til slutt sender vi sendebeskjeden 'init' til spillerobjektet vårt for å få ting i gang.
La oss se hva som skjer på serversiden når vi sender ut 'startGame'-hendelsen.
Den første halvdelen av denne socket-hendelsen viser her hvordan vi setter noen innledende verdier på hver klient, deretter legger vi til hvilken som helst klient som ikke er den avsendende klienten i den lokale 'andre'-arrayen.
I den andre halvdelen opprettes en lokal ordliste-liste som heter 'ack'. I den har vi informasjon om oss selv, og de andre klientene. Deretter sender vi den informasjonen tilbake til klienten ved å sende ordboken 'ack' til tilbakemeldingsfunksjonen, som blir den resulterende verdien av den påkallende Emit til Socket-atferden.
Deretter vil en tidsforskydning på 5 sekunder bli gjort før vi endelig begynner runden, nå som alle har all informasjonen de trenger for å spille spillet. 'beginRound'-funksjonen (linje 99) styrer noe spillspesifikk logikk for dette prosjektet. Vi skal ikke gå for mye i detalj om den, men i utgangspunktet håndterer den hvor spillerne spawns, sjekker poengsummer, samt forteller klientene hvem som er "Det".
Som nevnt tidligere, kalles 'init'-meldingen på vår "Spilllogikk"-etikett når alt er klart. I spillerobjektet vårt skal vi nå se på atferdene der det mottar 'init'-meldingen.
Her kan du se vi har flere atferdstre på spilleren vår. Først vil vi starte med det øvre venstre treet.
Dette er atferden som praktisk talt starter alt annet i spillet vårt.
Først mottar vi 'init'-meldingen sendt fra "Spilllogikk"-etiketten. Deretter henter vi server-ID-en til objektet vårt og slår på en av Socket-hendelsene, samt setter opp spillskjermen vår slik at vi kan følge karakteren vår nøyaktig.
Bevegelsessynkronisering
Her er en av våre viktigste atferder. Dette lille treet er designet for å oppdatere spillerens posisjon på serveren hver gang vi flytter joystick. Du har sannsynligvis lagt merke til at det også refererer til noen ordverditter. Vi henter disse verdiene fra en selvstendig ordbok-aktig atferd som inneholder X og Y-posisjonene til spilleren vår.
La oss ta en titt på 'moved'-hendelsen på serveren.
data = JSON.parse(data);
Ordlistene, når de sendes fra en klient til en server, må analyseres for å lese dataene enkelt. Dette er fordi ordlister i hyperPad er kodet til en JSON-struktur når de sendes til serveren. Denne linjen analyserer JSON-strengstrukturen og lagrer ordlisten på nytt i samme lokale 'data'-variabel.
socket.x = data.x;
socket.y = data.y;
Her oppdaterer vi X og Y-posisjonene lagret på socketen med de nye verdiene gitt av klienten.
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
});
}
Neste tar vi en iterasjon gjennom alle klientene for å oppdatere dem om vår nye posisjon og andre detaljer, unntatt oss selv (dvs. Den avsendende klienten trenger ikke å vite sin egen posisjon).
Dette treet her håndterer selve kjernen i spillet vårt. Vi bruker en Socket-hendelse når serveren sjekker hvilken spiller som er merket som "Det", noe som blir sendt av 'beginRound' (linje 99) på serveren.
Deretter henter vi server-ID-en til objektet vårt fra arrayet og bruker Verdi av ordboken for å bryte det ned til de forskjellige datadelene den inneholder. Derfra henter vi poengsummen vår, hvis vi er merket som "Det", samt x- og y-posisjonene til objektet vårt, deretter bruker vi dem for å tilpasse objektets attributter. Resten av atferdene er for å sette opp og kontrollere brukergrensesnittet i spillet vårt.
Konklusjon
Det var mye å ta inn her, men forhåpentligvis, hvis du kom så langt, bør du ha en forståelse av hvordan du bruker Socket.io-atferdene for å skape flerspilleropplevelser for spillerne dine.
Prøv det selv! Ta et eksisterende spill du har opprettet, og prøv å gi det noen online-funksjonaliteter, som en høyere poengliste-scene som kobler til en server og ber om en liste over de 10 beste poengene med spillernavn som skal vises.
Det er vanskelig å lære et skriptspråk som Javascript i en enkelt artikkel. Heldigvis, hvis du har problemer, er det mange andre ressurser til hjelp for å skrive Javascript-applikasjoner for Node.JS og Socket.io;
Lær Javascript - https://developer.mozilla.org/bm/docs/Web/JavaScript
Lær Socket.io - https://socket.io/docs/
Lær Node.JS - https://nodejs.org/en/docs/

