Opsætning af et multiplayer-spil med Socket.io | hyperPad Documentation

Loading...

Logo
DocumentationVejledningerOpsætning af et multiplayer-spil med Socket.io

z-f2VkDQ.jpg

Del 1: Introduktion

Foretrukne Forudsætninger

I denne vejledning vil vi fokusere på, hvordan man opsætter en server og forbinder dit spil til den, så du kan aktivere multiplayerfunktioner til dit projekt via Socket.io-adfærd. Vi vil dække avancerede funktioner og adfærd i hyperPad, og det anbefales stærkt at gennemgå nogle andre vejledninger først og få en god følelse af programmet, før du fortsætter.

Denne vejledning forudsætter, at du har en god forståelse for hyperPads kernefunktionalitet såvel som en minimal forståelse af skriftlig programmering, da vi vil fokusere på Javascript-scripting for at opbygge en spilsserver. Denne vejledning forudsætter også en grundlæggende forståelse af netværk og forskellene mellem en server og dens klienter.

Det primære mål med denne vejledning er at lære dig, hvad du skal gøre for at tilføje multiplayerfunktionalitet til dit spil; ikke at oprette selve spillet. Vi vil udforske forholdet mellem Socket.io-klienter og serveren, og hvordan de kommunikerer med hinanden med Socket.io-adfærdene.

Krav

For at oprette og hoste serveren skal du bruge en computer, der kan køre Node.JS (Mac, Windows osv.). Installation af Node.JS og opsætning til vores formål vil blive dækket i denne vejledning. Hvis du hoster en server i dit hjemmenetværk, skal du muligvis ændre din gateways port-forward-indstillinger, hvis du ønsker at have indgående forbindelser fra udlandet til dit lokale netværk.

Til denne vejledning har vi allerede oprettet et enkelt demo-spil. I det har vi to spillere, der spiller en fangstleg i en lille labyrint. Vi vil undersøge, hvordan Socket.io-adfærd anvendes i eksempelprojektet, så det anbefales at downloade og åbne projektet i hyperPad.

Det færdige hyperPad eksempelprojekt kan downloades her: Multiplayer tag tutorial.tap

Oversigt

I denne vejledning vil vi gennemgå det grundlæggende i at oprette en server til dit spil. Serveren vil håndtere de fleste detaljer i spillet såsom scoring, spil-lobbyer osv. Derefter vil vi oprette adfærd, der sender information fra vores spil til serveren og omvendt.

Her er den generelle flow af vores spil:

1. Fra hovedmenuen vil vi oprette forbindelse til vores server og lade brugeren beslutte, om de vil oprette eller deltage i et spil.

a. Hvis du opretter et spil, indlæses venteværelset.

b. Hvis du deltager i et spil, indlæses en liste over tilgængelige spilleloker.

c. Når to spillere er i et venteværelse, startes spillet.

2. I et spil placerer serveren tilfældigt spillerne i et af de fire områder og tildeler en af dem til at være "Det".

a. Fang den anden spiller vil randomisere deres placeringer, bytte det status og tilføje et point til spilleren, der fangede den anden.

b. Hvis der ikke sker nogen tags inden for en given tidsramme, vil serveren fjerne 1 point fra den spiller, der i øjeblikket er Det, og bytte det status, før spillerne tilfældigt placeres i spawn-zonerne igen.

3. Når en spiller har nået et bestemt antal point, indlæses et overlay, der erklærer en vinder. Derefter vil spillerne blive frakoblet rummet og vende tilbage til hovedmenuen.

Del 2: Opsætning af serveren

Oprettelse af en Socket.io-server kræver, at vi kører en Javascript-applikation ved hjælp af Socket.io-biblioteket, der lytter efter spillerforbindelser. Socket.io er et Javascript-netværksbibliotek, der forenkler en masse af arbejdet med at bygge en netværksapplikation for os. Flere detaljer følger.

Download og installer Node.JS

For at komme i gang skal du gå til Node.JS og downloade det på den computer (Mac, Windows osv.), du ønsker at hoste serveren på. Når downloadingen er færdig, skal du køre installationsprogrammet og følge instruktionerne. Alle indstillinger under installationen kan efterlades som standard. Node.JS giver os muligheden for at køre Javascript-applikationer på egen hånd uden behov for en webbrowser.

Download og kør eksempelserveren

Næste skridt er at downloade dette vejlednings Multiplayer Server Example på GitHub:

https://github.com/hyperPad/multiplayerServerExample

Klik på knappen "Klon eller download" og vælg "Download ZIP". Dette vil downloade en kopi af koden til servereksemplet.

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

Udpak den downloadede ZIP. Indenfor finder du et par små filer, men den mest bemærkelsesværdige fil her er "index.js"-filen, som er koden til vores server. Åbn derefter dit kommandolinje/terminal inde i servereksempel-mappen.

Skriv "npm install" og tryk på enter, så lad det køre. npm er en pakkeadministrator, der læser package.json-filen og downloader de nødvendige pakker til vores server. (Inklusiv Socket.io!)

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

Når kommandoen er færdig, har vi alt, hvad vi behøver for at køre serveren. I terminalen skal du skrive "node ." så vil serveren starte.

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

Det er det! Vores server lytter nu på port 3000 for indkommende Socket.io-forbindelser.

"node ." starter Node.JS for den aktuelle mappe, hvor den vil lede efter mværkårets indeksfil og køre den. Som standard er dette "index.js" Javascript-filen, som det meste af vores servers kode er placeret i og vil blive analyseret i denne vejledning.

Lad terminalen være åben, da lukning også vil lukke serveren. Du vil se, at den udskriver meddelelser, når spillere opretter forbindelse til eller afbryder forbindelsen, og når rum oprettes eller ødelægges, samt andre hændelser.

Bemærk: For at tillade indkommende forbindelser fra udsiden af et hjemmenetværk, skal du muligvis åbne port 3000 på din hjemmegateway. Denne proces varierer fra netværk til netværk, men en vejledning kan normalt findes ved at foretage en internetsøgning efter en port-forwarding guide til dit hjems modem/router. Du skal muligvis lukke og genstarte Node.JS-serveren, når du ændrer port-forwarding-indstillinger.

Del 3: Forbindelse til serveren

Når du har de grundlæggende funktioner på serveren startet, kan vi nu forbinde til den i et hyperPad-spil. Dette bør opsættes til at være det første, der sker i dit projekt, uanset hvilken scene du er på (da alt skal kommunikere med serveren). Det er bedst at vedhæfte denne adfærd til et objekt på Global Layer, så den gælder for alle scener.

Untitled.jpg

I eksempelprojektet indeholder den Globale Lag "Server"-etiket ovenstående. (Download til projektet kan findes i Del 1 af denne vejledning under kravsektionen.)

Disse to adfærd er alt, hvad du behøver for at oprette forbindelse til serveren. For det første, under fanen "brugerdefineret", tag Socket.io Client-adfærden og slip den ned. Her vil du indtaste din servers URL under URL-fanen i dens egenskabsvindue. På det ovenstående billede lå vores servers URL på "http://192.168.0.191:3000", inklusive protokol og port. Du vil ændre dette til at matche din server-URL, eller det vil sandsynligvis ikke fungere, når du starter spillet.

Nu har vi de serveroplysninger, vi ønsker, men vi skal stadig oprette forbindelse til den. Så alt, hvad vi har brug for, er forbindelsen til Socket-adfærden. Slip en ned, åbne dens egenskaber og vælg klienten i den tomme boks, og indstil funktions-egenskaben til "Connect".

Nu, når vores projekt indlæses, vil vi automatisk oprette forbindelse til vores server.

mceclip1.png

Del 4: Oprettelse og deltagelse i rum

Næste skridt er at oprette vores spillelobbyer, hvor spillere kan deltage og spille sammen.

mceclip2.png

Her er vores enkle hovedmenu-skærm. Alt hvad man skal gøre er at trykke på en af knapperne for enten at starte et rum eller se efter tilgængelige rum.

Oprettelse af rum

Lad os tjekke, hvordan vi kommunikerer med serveren for at oprette vores rum.

mceclip3.png

Ret simpelt, ikke? For hurtigt at gennemgå dette, når vi trykker på vores knap, vil vi blive bedt om at skrive etRoom navn. Når det er gjort, udstedes navnet til serveren, hvor rummet oprettes, og vi indlæser venteværelset scene.

Fra nu af vil vi ofte bruge Emit til Socket. Det er fordi det er vores primære måde at sende data til serveren. Tænk på det som den "online version" af Broadcast Message og Receive Message (Emit til Socket udfører begge handlinger på én gang).

Nu skal vi tilføje nogle oplysninger til vores server, så det kan oprette rummet, når det modtager meddelelsen fra vores Emit-adfærd.

mceclip4.png

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

Denne linje opretter en socket-hændelse (som en lambda-udtryk) på serveren, der lytter efter Emits med hændelsen 'createRoom'.

Det første parameter er værdien, vi sender gennem Emit til Socket-adfærden, i dette tilfælde rum navnet, som brugeren skrev ind. Her har vi kaldt den parameter 'roomName'.

Det andet parameter er en funktion, som vi kalder senere i socket-hændelsen for at signalere klienten. Emit til Socket-adfærden vil kun fortsætte udførelsen, hvis callback-funktionen kaldes på serveren. Her har vi kaldt den parameter 'callback'.

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

Denne vil oprette en instans af en struktur, der vil indeholde væsentlige oplysninger om et rum.

'id' vil være en unik identifikator genereret af hjælpefunktionen 'uuid()'. Dette vil hjælpe os med specifikt at identificere dette rum i forhold til en liste over mange andre oprettede rum senere.

'name' vil blive indstillet til det navn, som brugeren tidligere skrev ind.

'sockets' vil blive initieret som et tomt array. Senere vil dette holde styr på de spiller-sockets, der er tilsluttet det rum.

rooms[room.id] = room;

'rooms' er en global liste over rum, der i øjeblikket er aktive. Da vi opretter et nyt rum, vil vi gemme det i listen ved dets ID.

joinRoom(socket, room);

Dette vil kalde den globale funktion 'joinRoom' (linje 29), som vil tilføje spiller-socket til rummets 'sockets'-array. Da spilleren oprettede rummet, vil dette også få dem til at deltage i det, også.

callback();
});

Til sidst kalder vi callback-funktionen, så klienten underrettes om, at rummet er oprettet. Dette vil tillade Emit til Socket at fortsætte udførelsen, hvilket vil indlæse venteværelset scenen næste. Dette markerer også slutningen på 'createRoom' socket-hændelsen.

Deltagelse i rum

Deltagelse i rum er lidt anderledes, da vi skal få adgang til oplysninger fra en anden scene fra knappen.

mceclip5.png

For at deltage i rum har vi placeret begyndelsen af vores adfærd på vores globale lag sammen med, hvor vi opretter forbindelse til serveren. På den måde kan vi tilgå oplysningerne fra en af vores scener, i dette tilfælde vores rum liste. For knappen vil tryk på den blot indlæse spilleren i rum listen scene.

Her har vi vores anden adfærd til at kommunikere med serveren. Socket Event kan tænkes som at modtage meddelelse, da den kun aktiveres, når den satte meddelelse er blevet sendt fra serveren.

Den bedste måde at tænke, når du bruger Socket Event og Emit til Socket, er, at Socket Event reagerer kun på information, der kommer fra serveren, mens Emit til Socket kaldes som reaktion på ting, der gøres lokalt.

Med hensyn til vores logik, når vi opretter forbindelse til serveren, vil vi Emit 'getRoomNames' anmodningen for at få eventuelle tilgængelige rum navne.

mceclip19.png

Derefter ville vi indstille en label, som spilleren kan trykke på for at komme ind.

mceclip6.png

Dette er vores Rum Liste scene. Her er hvor eventuelle tilgængelige spilleloker vil blive indlæst og vist. Ved blot at trykke på rum navnet vil spilleren blive indlæst. Takket være vores tidligere adfærd vil listen automatisk indlæse eventuelle åbne rum og spawne rum navn etiketter på skærmen. Skulle ingenting dukke op, har vi vores Opdater Liste-knap, der blot gentager adfærd for at indlæse dem igen.

mceclip7.png mceclip8.png

Her har vi logikken for opsætning af listen. Vi vil ikke gå for meget ind i detaljerne her, fordi vi kun vil vide, hvordan dette forbinder med vores server.

For at starte har vi vores Emit til Socket-adfærd. Dette kalder på serveren for at sende oplysninger om tilgængelige rum. Derfra har vi en Hent Array-værdi-adfærd. Alle data, der kommer fra serveren, vil blive sendt som en Array og de oplysninger, der er nødvendige, vil være på de første værdier. Så vi indstiller vores Hent Array Værdi til at få værdien ved indeks 0. Derfra vil vores adfærd udtrække disse data og oprette en label for hvert rum ved at spawne dem på vores skærm.

Næste, vi skal tjekke objektet, som vi har brug for at trykke på, i vores scene, det er etiketten kaldet Rum Navn.

mceclip9.png

Denne tekst fungerer som vores knap, når den er spawnet, men vi skal stadig vedhæfte rum-ID, som vi ønsker at forbinde til. For at gøre det, skal vi først få fat i rum-ID'et, og det gør vi med Hent Attribut-værket og indstille det til dynamisk. Derefter udsender vi til serveren, at vi vil deltage i dette rum og indlæse ind i venteværelset.

mceclip10.png

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

Dette er indgangspunktet for socket-hændelsen 'joinRoom'. Værdien for den første parameter ville være rum-ID'et, som vi ønsker at deltage i, der blev sendt til serveren. Her har vi kaldt parameteren 'roomId'.

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

Ved at bruge roomId givet af klienten kan vi finde den rigtige ruminstans på serveren. Med det på plads kalder vi den globale funktion 'joinRoom' (linje 29) med socket for den spiller, der ønsker at tilslutte sig, og ruminstansen selv. Vi vil tage et kig på 'joinRoom'-funktionen lige om lidt.

callback();
});

Til sidst kalder vi callback-funktionen for at underrette klienten om at fortsætte med at indlæse ventescenen, hvilket markerer slutningen af 'joinRoom' socket-hændelsen.

Så hvad foregår der præcist i 'joinRoom'-funktionen? Lad os tage et kig.

mceclip11.png

room.socket.push(socket);

Som nævnt tidligere holder 'room.socket' medlem-arrayet styr på sockets, der er tilsluttet i et rum. Denne linje gør netop det ved at skubbe socket ind i arrayet.

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

Dette er den officielle opkald for at forbinde klienten til et rum. Først fortæller vi socket'en at deltage i et rum ved dets ID. Når det er gennemført, kaldes følgende callback, hvor vi knytter rum-ID'et til socket'en. Til sidst logger vi i konsollen, at en spiller er tilsluttet et rum!

mceclip12.png

Venteværelset

Venteværelset er ganske enkelt en scene, som vi indlæser spillerne ind i, mens de venter på, at andre spillere skal deltage, eller for et spil skal begynde.

mceclip13.png

Når vi går ind i rummet, vil vi udsende en "klar" meddelelse til serveren. Når serveren har modtaget to af disse, sender den en meddelelse om, at den starter spillet ('initGame'). Vi opsamler denne meddelelse med vores Socket Event og indlæser derfor vores spillescene. På serverens side lad os analysere koden for at se, hvad der foregår der.

mceclip14.png

Dette er 'klar' hændelsen, der kaldes, når en klient er blevet tilsluttet et rum og er klar til at forbinde.

const room = rooms[socket.roomId];

Da vi er blevet knyttet rum-ID til socket'en, kan vi få fat i rummet for at tjekke, om spillet kan starte.

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

Her tjekker vi for at se, om der nu er to spillere, der venter i rummet. Lad os sige, at det nu er sandt, og vi fortsætter med at starte spillet.

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

Nu hvor der er to spillere, itererer vi gennem hver socket og udsender 'initGame'-begivenheden, så hver klient indlæser niveau-scenen, som før beskrevet.

Del 5: Spiloplevelse

Nu, for at komme ind i kernen af tingene. Dette er, hvor 90 % af vores arbejde vil gå hen. Nedenunder er vores spillescene, som vi designede til denne vejledning.

mceclip15.png

Inden vi kommer ind i det, har vi en label med titlen "Spil Logik", lad os åbne den op og tage et kig.

mceclip16.png

Wow, det er mange adfærd! Bekymre dig ikke, dette er ganske enkelt hvordan vi spawner vores spillere ind i spillet. Lad os se nærmere på;

mceclip17.png

Vi starter med en Emit til Socket-adfærd, der fortæller serveren, at vores spil er startet, med eventet 'startGame'. Vi henter derefter den array, serveren returnerer, tager den første værdi og med Hent Dictionary Værdi, henter vi de forskellige attributter, vores objekt vil have brug for. En separat træ gør det samme, bortset fra for modstanderen. Til sidst videresender vi meddelelsen 'init' til vores spiller objekt for at få det hele startet.

Lad os se, hvad der foregår på serversiden, når vi udsender 'startGame'-begivenheden.

mceclip20.png

Den første halvdel af denne socket-hændelse, du ser her, sætter nogle indledende værdier på hver klient og tilføjer derefter enhver klient, der ikke er den udstedende klient til den lokale 'andre' array.

mceclip21.png

I den anden halvdel oprettes en lokal dictionary-liste kaldet 'ack'. I den har vi oplysninger om os selv og de andre klienter. Vi sender derefter den information tilbage til klienten ved at sende vores dictionary 'ack' til callback-funktionen, som bliver den resulterende værdi af den kaldende Emit til Socket-adfærd.

Derefter foretages et timeout-opkald i 5 sekunder for endelig at begynde runden, nu hvor alle har alle de oplysninger, de behøver for at spille spillet. 'beginRound'-funktionen (linje 99) styrer noget spil-specifik logik til dette projekt. Vi vil ikke gå for meget i detaljen omkring det, men i bund og grund håndterer den, hvor spillerne skal spawnes, tjekker point, samt informerer klienterne om, hvem der er Det.

Som tidligere nævnt kaldes 'init'-meddelelsen på vores "Spil Logik"-label, når alt er klar til at gå. I spillerobjektet vil vi nu kigge ind i adfærd på det, hvor den modtager 'init'-meddelelsen.

mceclip22.png

Her kan du se, at vi har flere adfærdstræer på vores spiller. For det første vil vi starte med det øverste venstre træ.

mceclip23.png

Dette er adfærden, der praktisk talt starter alt andet i vores spil.

Først modtager vi 'init'-meddelelsen sendt fra "Spil Logik"-etiketten. Derfra henter vi server-ID'et for vores objekt og tænder en af vores Socket Events samt opsætter vores spilleskærm, så vi præcist kan følge vores karakter.

Bevægelsessynkronisering

mceclip24.png

Her er en af vores vigtigste adfærd. Dette lille træ er designet til at opdatere vores spillers position på serveren, hver gang vi bevæger joysticket. Du har sikkert bemærket, at det også refererer til nogle dictionaryværdier. Vi får disse værdier fra en selvstændig dictionary-adfærd, der indeholder X- og Y-positionerne for vores spiller.

Lad os tage et kig på 'moved'-begivenheden på serveren.

mceclip25.png

data = JSON.parse(data);

Dictionaries, når de sendes fra en klient til en server, skal parses for at kunne læse dataene nemt. Dette skyldes, at dictionaries i hyperPad er kodet til en JSON-struktur når de udsendes til en server. Denne linje parser JSON-strengstrukturen og gemmer dictionary-listen tilbage i den samme lokale 'data'-variabel.

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

Her opdaterer vi X- og Y-positionerne gemt på socket'en med de nye værdier givet af 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
});
}

Derefter itererer vi gennem alle klienterne for at opdatere dem om vores nye position og andre detaljer, undtagen os selv (dvs. Den udstedende klient har ikke brug for at vide deres egen position).

mceclip26.png

Dette træ her styrer størstedelen af vores spil. Vi bruger en Socket Event, når serveren tjekker, hvilken spiller der er mærket som Det, som udsendes af 'beginRound' (linje 99) på serveren.

Derefter henter vi objekternes server-ID fra arrayet og bruger Dictionary Value til at nedbryde det til de forskellige data, det indeholder. Derfra henter vi vores Score, om vi er mærket som Det samt x- og y-positionerne for vores objekt og anvender dem på objektets attributter. Den resterende adfærd er til at sætte og kontrollere brugergrænsefladen i vores spil.

Konklusion

Der var meget at tage ind her, men forhåbentlig, hvis du kom så langt, har du en forståelse for, hvordan du kan udnytte Socket.io-adfærd til at skabe multiplayeroplevelser for dine spillere.

Prøv det selv! Tag et eksisterende spil, du har oprettet, og prøv at give det nogle online funktioner, som f.eks. en highscore-liste-scene, der forbinder til en server og anmoder om en liste over de top 10 scores med spillernavne til visning.

Det er svært at lære et skriptsprog som Javascript i en enkelt artikel. Heldigvis, hvis du har problemer, er der masser af andre ressourcer til at hjælpe dig med at skrive Javascript-applikationer til 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/