Een multiplayergame opzetten met Socket.io | hyperPad Documentation

Loading...

Logo

z-f2VkDQ.jpg

Deel 1: Inleiding

Voorkeursvereisten

In deze tutorial richten we ons op hoe je een server opzet en je game ermee verbindt, zodat je multiplayer-functies voor je project kunt inschakelen via Socket.io-gedragingen. We zullen geavanceerde functies en gedragingen in hyperPad behandelen en het is ten zeerste aan te raden om eerst enkele andere tutorials te bekijken en een goed gevoel voor de software te krijgen voordat je verder gaat.

Deze tutorial gaat ervan uit dat je een goed begrip hebt van de kernfunctionaliteit van hyperPad, evenals een minimale kennis van geschreven programmering, aangezien we ons zullen concentreren op Javascript-scripting voor het bouwen van een game server. Deze tutorial gaat ook uit van een basisbegrip van netwerken en de verschillen tussen een server en zijn clients.

Het primaire doel van deze tutorial is om je te leren wat je moet doen om multiplayer-functionaliteit aan je game toe te voegen; niet om de game zelf te maken. We zullen de relatie tussen Socket.io-clients en de server verkennen, en hoe ze met elkaar communiceren met de Socket.io-gedragingen.

Vereisten

Om de server te maken en te hosten heb je een computer nodig die Node.JS (Mac, Windows, etc.) kan draaien. De installatie van Node.JS en de configuratie voor ons doel worden in deze tutorial behandeld. Als je een server in je thuissysteem host, moet je waarschijnlijk de poortdoorstuurinstellingen van je gateway wijzigen als je inkomende verbindingen van buiten je lokale netwerk wilt hebben.

Voor deze tutorial hebben we al een eenvoudige demo-game gemaakt. Hierin hebben we twee spelers die een spelletje tag spelen in een klein doolhof. We zullen verkennen hoe de Socket.io-gedragingen worden gebruikt in het voorbeeldproject, dus het is aan te raden om het project te downloaden en te openen in hyperPad.

Het voltooide hyperPad-voorbeeldproject kan hier worden gedownload: Multiplayer tag tutorial.tap

Overzicht

In deze tutorial zullen we de basisprincipes van het creëren van een server voor je game behandelen. De server zal de meeste details in de game beheren, zoals scoring, game-lobby's, enz. Vervolgens zullen we gedragingen creëren die informatie van onze game naar de server verzenden en vice versa.

Hier is de algemene flow van onze game:

1. Vanaf het hoofdmenu zullen we verbinding maken met onze server en de gebruiker laten beslissen of hij een game wil maken of deelnemen aan een game.

a. Als je een game maakt, laadt de wachtruimte.

b. Als je aan een game deelneemt, laad een lijst met beschikbare gamerooms.

c. Wanneer er twee spelers in een wachtruimte zijn, wordt de game gestart.

2. In een game plaatst de server de spelers willekeurig in een van de vier gebieden en wijst er een aan als "It".

a. Het taggen van de andere speler zal hun locaties randomiseren, de "It"-status verwisselen en een punt toevoegen aan de speler die de andere heeft getagd.

b. Als er gedurende een bepaalde periode geen tags plaatsvinden, zal de server 1 punt van de speler die momenteel "It" is, afnemen en de "It"-status verwisselen voordat de spelers weer willekeurig in de spawnzones worden geplaatst.

3. Zodra een speler een bepaald aantal punten heeft bereikt, wordt er een overlay geladen die de winnaar verklaart. Vervolgens zullen de spelers zich loskoppelen van de kamer en terugkeren naar het hoofdmenu.

Deel 2: De server opzetten

Het creëren van een Socket.io-server vereist dat we een Javascript-applicatie uitvoeren met behulp van de Socket.io-bibliotheek, die luistert naar spelersverbindingen. Socket.io is een Javascript-netwerkbibliotheek die veel van de interne werking van het bouwen van een netwerktoepassing voor ons vereenvoudigt. Meer details volgen.

Download en installeer Node.JS

Om te beginnen ga je naar Node.JS en downloadt het op de computer (Mac, Windows, enz.) waarop je de server wilt hosten. Als je klaar bent met downloaden, voer je de installer uit en volg je de instructies. Alle opties tijdens de installatie kunnen standaard blijven. Node.JS geeft ons de mogelijkheid om Javascript-applicaties zelfstandig uit te voeren zonder een webbrowser.

Het downloaden en uitvoeren van de voorbeeldserver

Vervolgens download je het Multiplayer Server Example van deze tutorial op GitHub:

https://github.com/hyperPad/multiplayerServerExample

Klik op de knop "Clone or download" en selecteer "Download ZIP". Dit zal een kopie van de code voor het servervoorbeeld downloaden.

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

Pak het gedownloade ZIP-bestand uit. In de map vind je een paar kleine bestanden, maar het meest opmerkelijke bestand hier is het "index.js"-bestand, dat de code voor onze server is. Open nu je opdrachtregel/terminal binnen de servervoorbeeldmap.

Typ "npm install" en druk op enter, laat het dan draaien. npm is een pakketbeheerder die het package.json-bestand leest en de noodzakelijke pakketten voor onze server downloadt. (Inclusief Socket.io!)

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

Wanneer de opdracht is voltooid, hebben we alles wat we nodig hebben om de server uit te voeren. Typ in de terminal "node ." en de server zal starten.

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

Dat is het! Onze server luistert nu op poort 3000 voor inkomende Socket.io-verbindingen.

"node ." start Node.JS voor de huidige map, waar het de index-bestand van de map zal zoeken en deze uitvoeren. Standaard is dit het "index.js" Javascript-bestand, waarin het grootste deel van onze servercode zich bevindt en dat in deze tutorial wordt geanalyseerd.

Laat de terminal open, want als je het sluit, sluit je ook de server. Je zult berichten zien afdrukken wanneer spelers zich verbinden of loskoppelen, en wanneer kamers worden gemaakt of vernietigd, en andere evenementen.

Opmerking: Om inkomende verbindingen van buiten een thuisnetwerk toe te staan, moet je waarschijnlijk poort 3000 op je thuisgateway openen. Dit proces varieert van netwerk tot netwerk, maar meestal kan er een handleiding worden gevonden door te zoeken naar een handleiding voor poortdoorsturen voor je modem/router. Je moet mogelijk de Node.JS-server sluiten en opnieuw starten als je de poortdoorstuurinstellingen wijzigt.

Deel 3: Verbinden met de server

Als je de basis van de server hebt gestart, kunnen we nu verbinden met deze in een hyperPad-game. Dit moet worden ingesteld als het eerste dat gebeurt in je project, ongeacht welke scène je bent (aangezien alles moet communiceren met de server). Het is het beste om deze gedraging aan een object op de Global Layer te koppelen, zodat het op alle scènes van toepassing is.

Untitled.jpg

In het voorbeeldproject bevat het Global Layer-label "Server" het bovenstaande. (De download voor het project kan worden gevonden in Deel 1 van deze tutorial onder de sectie Vereisten.)

Deze twee gedragingen zijn letterlijk alles wat je nodig hebt om verbinding te maken met de server. Ten eerste, onder het aangepaste tabblad, pak de Socket.io Client-gedraging en sleep deze naar beneden. Hier voer je de URL van je server in onder het tabblad URL in het eigenschappenvenster. In de bovenstaande afbeelding was de URL van onze server "http://192.168.0.191:3000", inclusief protocol en poort. Je wilt dit veranderen om je server-URL overeen te laten komen, anders werkt het waarschijnlijk niet wanneer je het spel start.

Nu hebben we de serverinformatie die we willen, maar we moeten er nog steeds verbinding mee maken. Dus, wat we nu nog nodig hebben, is de Gedraging Verbinden met Socket. Slepen en open de eigenschappen en selecteer de client in het lege vak en stel de Eigenschap Functie in op "Verbinden".

Nu, wanneer ons project wordt geladen, maken we automatisch verbinding met onze server.

mceclip1.png

Deel 4: Kamers maken en deelnemen

De volgende stap is het maken van onze game-lobby's waar spelers zich kunnen voegen en samen kunnen spelen.

mceclip2.png

Hier is ons eenvoudige hoofdmenu-scherm. Iedereen hoeft maar op een van de knoppen te tikken om een kamer te starten of naar beschikbare kamers te zoeken.

Kamers maken

Laten we bekijken hoe we kunnen communiceren met de server om onze kamer te maken.

mceclip3.png

Vrij eenvoudig, toch? Dus om dit snel door te nemen, zodra we op onze knop tikken, worden we gevraagd een kamernaam in te voeren. Zodra dat gedaan is, wordt die naam uitgezonden naar de server waar de kamer wordt gemaakt en laden we de wachtruimtescène.

Van hieraf zullen we vaak Emit naar Socket gebruiken. Dat komt omdat het onze belangrijkste manier is om gegevens naar de server te verzenden. Beschouw het gewoon als de "online versie" van Broadcast Message en Receive Message (Emit to Socket voert beide acties tegelijkertijd uit).

Nu moeten we enkele informatie aan onze server toevoegen, zodat deze de kamer kan maken zodra deze het bericht van onze Emit-gedraging ontvangt.

mceclip4.png

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

Deze regel creëert een socket-evenement (als lambda-expressie) op de server, die luistert naar Emits met het evenement 'createRoom'.

De eerste parameter is de waarde die we doorgeven via de Emit naar Socket-gedraging, in dit geval de kamernaam die de gebruiker typte. Hier hebben we die parameter 'roomName' genoemd.

De tweede parameter is een functie die we later in het socket-evenement aanroepen om de client te signaleren. De Emit naar Socket-gedraging zal alleen doorgaan met uitvoeren als de callbackfunctie op de server wordt aangeroepen. Hier hebben we die parameter 'callback' genoemd.

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

Dit zal een instantie van een structuur creëren die essentiële informatie over een kamer zal bevatten.

'id' zal een unieke identificatie zijn die wordt gegenereerd door de hulpfunctie 'uuid()'. Dit helpt ons om deze kamer specifiek te identificeren tegenover een lijst van vele andere gemaakte kamers later.

'name' zal worden ingesteld op de naam die de gebruiker eerder typt.

'sockets' zal worden geïnitialiseerd als een lege array. Later zal deze de speler-sockets bijhouden die verbonden zijn met die kamer.

rooms[room.id] = room;

'rooms' is een globale lijst van kamers die momenteel actief zijn. Aangezien we een nieuwe kamer aanmaken, zullen we deze opslaan in de lijst op basis van zijn ID.

joinRoom(socket, room);

Dit zal de globale functie 'joinRoom' (regel 29) aanroepen, die de speler-socket aan de 'sockets'-array van de kamer zal toevoegen. Aangezien de speler de kamer heeft gemaakt, zal dit ook betekenen dat ze zich daarbij joinen.

callback();
});

Tot slot roepen we de callbackfunctie aan zodat de client wordt op de hoogte gesteld dat de kamer is gemaakt. Dit zal de Emit naar Socket-gedraging toestaan om door te gaan met de uitvoering, wat de volgende keer de wachtruimtescène laadt. Dit markeert ook het einde van het 'createRoom' socket-evenement.

Kamers binnengaan

Kamers binnengaan is iets anders omdat we informatie vanuit een andere scène bij de knop moeten ophalen.

mceclip5.png

Voor joinrooms hebben we het begin van onze gedragingen op onze globale laag geplaatst, samen met waar we verbinding maken met de server. Op deze manier kunnen we de informatie vanuit al onze scènes ophalen, in dit geval onze kamer lijst. Voor de knop zal het tikken er simpelweg voor zorgen dat de speler in de kamerlijstscène komt.

Hier hebben we onze tweede gedraging voor communicatie met de server. De Socket Event kan worden gezien als Receive Message, aangezien deze alleen wordt geactiveerd wanneer de ingestelde boodschap is uitgezonden door de server.

De beste manier om te denken bij het gebruik van Socket Event en Emit naar Socket is dat de Socket Event alleen reageert op informatie van de server, terwijl Emit naar Socket wordt aangeroepen in reactie op lokaal uit te voeren dingen.

Wat betreft onze logica, zodra we verbinding maken met de server, zullen we de 'getRoomNames'-aanroep verzenden om eventuele beschikbare kamernamen te krijgen.

mceclip19.png

Dan zouden we een label instellen dat de speler kan tikken om binnen te gaan.

mceclip6.png

Dit is onze Kamerlijst-scène. Hier worden alle beschikbare gamerooms geladen en weergegeven. Door simpelweg op de kamernaam te tikken, wordt de speler erin geladen. Dankzij onze voorgaande gedragingen zal de lijst automatisch alle open kamers laden en de kamernamelabels op het scherm spawnen. Als er niets verschijnt, hebben we onze Vernieuw Lijst-knop die simpelweg de gedragingen herhaalt om ze opnieuw te laden.

mceclip8.png

Hier hebben we de logica voor het instellen van de lijst. We gaan hier niet te veel in detail, omdat we alleen willen weten hoe dit verbinding maakt met onze server.

Om te beginnen hebben we onze Emit naar Socket-gedraging. Dit vraagt de server om informatie over beschikbare kamers te verzenden. Van daaruit hebben we een Get Array Value-gedraging. Alle gegevens die van de server komen, worden als een Array verzonden en de benodigde informatie staat op de eerste waarden. Dus we stellen onze Get Array Value in om de waarde op index 0 te krijgen. Van daaruit zullen onze gedragingen die gegevens extraheren en een label creëren voor elke kamer, die ze op ons scherm spawnen.

Vervolgens gaan we het object controleren dat we moeten aanraken; in onze scène is dit het label met de naam Kamernaam.

mceclip9.png

Deze tekst fungeert als onze knop wanneer deze wordt gespawnd, maar we moeten nog steeds de kamer-ID bevestigen waarmee we willen verbinden. Om dat te doen, moeten we eerst de kamer-ID ophalen, en dat doen we met de Get Attribute-gedraging en deze instellen als dynamisch. Vervolgens zenden we naar de server dat we deze kamer willen binnenkomen en in de wachtruimte willen laden.

mceclip10.png

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

Dit is het ingangspunt voor het socket-evenement 'joinRoom'. De waarde van de eerste parameter zou de kamer-ID zijn die we willen joinen die naar de server is verzonden. Hier hebben we die parameter 'roomId' genoemd.

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

Met behulp van de roomId die door de client is gegeven, kunnen we de juiste kamerinstantie op de server vinden. Hiermee roepen we de globale functie 'joinRoom' (regel 29) op met de socket van de speler die wil verbinden, en de kamer zelf. We zullen zo meteen naar de 'joinRoom'-functie kijken.

callback();
});

Tot slot roepen we de callbackfunctie aan om de client op de hoogte te stellen dat deze verder kan met het laden van de Wachtruimtescène, wat het einde van het 'joinRoom' socket-evenement markeert.

Dus, wat gebeurt er precies in de 'joinRoom'-functie? Laten we eens kijken.

mceclip11.png

room.socket.push(socket);

Zoals eerder vermeld, houdt de 'room.socket'-array bij welke sockets in een kamer zijn verbonden. Deze regel doet dat door de socket in de array toe te voegen.

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

Dit is de officiële aanroep om de client met een kamer te verbinden. Eerst vertellen we de socket te joinen met een kamer op basis van de ID. Wanneer dat is voltooid, wordt de volgende callback aangeroepen, waarin we de kamer-ID aan de socket hechten. Ten slotte loggen we in de console dat een speler een kamer is binnengekomen!

mceclip12.png

De Wachtruimte

De wachtruimte is simpelweg een scène waarin we de spelers laden terwijl ze wachten op andere spelers om verbinding te maken, of voor een game om te beginnen.

mceclip13.png

Bij binnenkomst in de kamer zullen we een 'ready'-bericht naar de server zenden. Zodra de server er twee van deze heeft ontvangen, zal deze een bericht sturen dat hij de game begint ('initGame'). We vangen dat bericht op met onze Socket Event en laden zo onze game-niveau. Voor onze gedragingen is dat ongeveer het. Je kunt een knop toevoegen die je uit de kamer laat loskoppelen en je terug naar het hoofdmenu stuurt als je dat wilt.

Aan de serverkant, laten we de code analyseren om te zien wat daar aan de hand is.

mceclip14.png

Dit is het 'ready'-evenement dat wordt aangeroepen wanneer een client zich in een kamer heeft gevoegd en klaar is om verbinding te maken.

const room = rooms[socket.roomId];

Aangezien we de kamer-ID aan de socket hebben gehecht, kunnen we de kamer ophalen om te controleren of de game kan beginnen.

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

Hier controleren we of er nu twee spelers in de kamer wachten. Stel dat dat nu waar is, dan gaan we verder met het starten van de game.

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

Nu er twee spelers zijn, itereren we door elke socket en zenden het 'initGame'-evenement zodat elke client de Niveau-scène laadt, zoals eerder getoond.

Deel 5: Gameplay

Nu, om tot de kern van de zaak te komen. Dit is waar 90% van ons werk zal plaatsvinden. Hieronder is ons game-niveau dat we voor deze tutorial hebben ontworpen.

mceclip15.png

Voordat we daar echter in duiken, hebben we een label genaamd "Game Logic". Laten we dat openen en eens bekijken.

mceclip16.png

Wauw, dat is een hoop gedragingen! Maak je geen zorgen, dit is gewoon hoe we onze spelers in de game spawnen. Laten we eens wat dieper kijken;

mceclip17.png

We beginnen met een Emit naar Socket-gedraging die de server vertelt dat onze game is begonnen, met het evenement 'startGame'. Vervolgens pakken we de array die de server retourneert, nemen de eerste waarde en met de Get Dictionary Value pakken we de verschillende attributen die ons object nodig heeft. Een aparte boom doet hetzelfde, behalve voor de opponent speler. Ten slotte zenden we het bericht 'init' naar ons spelerobject om de zaken op gang te brengen.

Laten we kijken wat er aan de serverkant gebeurt wanneer we het 'startGame'-evenement uitzenden.

mceclip20.png

De eerste helft van dit socket-evenement die je hier ziet, stelt enkele initiële waarden voor elke client in, en voegt vervolgens elke client die niet de zender van de emit is, toe aan de lokale 'others'-array.

mceclip21.png

In de tweede helft wordt er een lokale woordenlijst-lijst genaamd 'ack' aangemaakt. Hierin hebben we informatie over onszelf en de andere client(s). We sturen vervolgens die informatie terug naar de client door onze woordenlijst 'ack' aan de callback-functie door te geven, die de resulterende waarde wordt van de aanroepende Emit naar Socket-gedraging.

Daarna wordt er een timeout-aanroep van 5 seconden gedaan om uiteindelijk de ronde te beginnen, nu iedereen alle informatie heeft die ze nodig hebben om de game te spelen. De 'beginRound'-functie (regel 99) regelt enige game-specifieke logica voor dit project. We willen hier niet te diep op ingaan, maar in wezen gaat het om waar de spelers moeten worden gespawned, het controleren van scores, en het vertellen aan de clients wie "It" is.

Zoals eerder vermeld, wordt het 'init'-bericht op ons "Game Logic"-label aangeroepen wanneer alles klaar is om te gaan. In het spelerobject zullen we nu kijken naar de gedragingen daarop wanneer het het 'init'-bericht ontvangt.

mceclip22.png

Hier zie je dat we verschillende gedragingen hebben op onze speler. Laten we beginnen met de bovenste linkerboom.

mceclip23.png

Dit is de gedraging die in wezen alles in onze game opstart.

Eerst ontvangen we het 'init'-bericht dat van het "Game Logic"-label is verzonden. Van daaruit halen we de server-ID van ons object op en schakelen we een van onze Socket Events in, evenals het instellen van ons game-scherm zodat we onze karakter nauwkeurig kunnen volgen.

Beweging Synchronisatie

mceclip24.png

Hier is een van onze belangrijkste gedragingen. Deze kleine boom is ontworpen om de positie van onze speler op de server bij te werken elke keer dat we de joystick bewegen. Je hebt waarschijnlijk gemerkt dat het ook naar een aantal woordenlijstwaarden verwijst. We halen die waarden uit een standalone woordenlijstgedraging die de X- en Y-posities van onze speler bevat.

Laten we eens kijken naar het 'moved'-evenement op de server.

mceclip25.png

data = JSON.parse(data);

Woordenlijsten, wanneer verzonden van een client naar een server, moeten worden geparsed om de gegevens gemakkelijk te kunnen lezen. Dit komt omdat woordenlijsten in hyperPad worden gecodeerd in een JSON-structuur wanneer ze naar een server worden uitgezonden. Deze regel parseert de JSON-stringstructuur en slaat de woordenlijst terug in dezelfde lokale 'data'-variabele op.

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

Hier werken we de X- en Y-posities die op de socket zijn opgeslagen bij met de nieuwe waarden die door de client zijn gegeven.

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
});
}

Vervolgens itereren we door alle clients om hen op de hoogte te stellen van onze nieuwe positie en andere details, waarbij we onszelf uitsluiten (d.w.z. de zender van de client hoeft zijn eigen positie niet te weten).

mceclip26.png

Deze boom regelt het grootste deel van onze game. We gebruiken een Socket Event wanneer de server controleert welke speler is getagd als "It", wat wordt uitgezonden door 'beginRound' (regel 99) op de server.

Daarna halen we de server-ID van ons object op uit de array en gebruiken we de Dictionary Value om deze op te splitsen in de verschillende gegevens die deze bevat. Van daaruit pakken we onze Score, of we zijn getagd als "It", evenals de x- en y-posities van ons object en passen deze toe op de attributen van het object. De rest van de gedragingen is voor het instellen en controleren van de UI in onze game.

Conclusie

Er was veel te verwerken, maar hopelijk, als je dit tot hier hebt gehaald, heb je een begrip van hoe je de Socket.io-gedragingen kunt gebruiken om multiplayer-ervaringen voor je spelers te creëren.

Probeer het zelf! Neem een bestaande game die je hebt gemaakt en probeer deze wat online functionaliteit te geven, zoals een highscore-leaderboardscène die verbinding maakt met een server en een lijst van de top 10 scores met spelersnamen aanvraagt om weer te geven.

Het is moeilijk om een scripttaal zoals Javascript in één artikel te onderwijzen. Gelukkig zijn er hulpprogramma's beschikbaar als je problemen ondervindt bij het schrijven van Javascript-applicaties voor Node.JS en Socket.io;

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

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

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