Configurer un jeu multijoueur avec Socket.io | hyperPad Documentation

Loading...

Logo

z-f2VkDQ.jpg

Partie 1 : Introduction

Pré-requis recommandés

Dans ce didacticiel, nous allons nous concentrer sur la manière de configurer un serveur et de connecter votre jeu à celui-ci afin de pouvoir activer des fonctions multijoueurs pour votre projet via les comportements Socket.io. Nous aborderons des fonctionnalités et des comportements avancés dans hyperPad et il est fortement recommandé de consulter d'autres didacticiels au préalable et d'avoir une bonne maîtrise du logiciel avant de continuer.

Ce didacticiel suppose que vous comprenez bien les fonctionnalités de base de hyperPad ainsi qu'une compréhension minimale de la programmation écrite, car nous nous concentrerons sur la rédaction de scripts en Javascript pour construire un serveur de jeu. Ce didacticiel supposera également une compréhension de base des réseaux et des différences entre un serveur et ses clients.

Le principal objectif de ce didacticiel est de vous enseigner ce que vous devez faire pour ajouter la fonctionnalité multijoueur à votre jeu ; et non pas de créer le jeu lui-même. Nous allons explorer la relation entre les clients Socket.io et le serveur, et comment ils communiquent entre eux via les comportements Socket.io.

Exigences

Pour créer et héberger le serveur, vous aurez besoin d'un ordinateur capable d'exécuter Node.JS (Mac, Windows, etc). L'installation de Node.JS et sa configuration pour notre but seront couvertes dans ce didacticiel. Si vous hébergez un serveur sur votre réseau domestique, vous devrez probablement modifier les paramètres de redirection de port de votre passerelle si vous souhaitez avoir des connexions entrantes depuis l'extérieur de votre réseau local.

Pour ce didacticiel, nous avons déjà créé un petit jeu démo. Dans celui-ci, nous avons deux joueurs jouant à un jeu de tag dans un petit labyrinthe. Nous allons explorer comment les comportements Socket.io sont utilisés dans le projet exemple, il est donc recommandé de télécharger et d'ouvrir le projet dans hyperPad.

Le projet exemple hyperPad terminé peut être téléchargé ici : Multiplayer tag tutorial.tap

Vue d’ensemble

Dans ce didacticiel, nous allons passer en revue les bases de la création d'un serveur pour votre jeu. Le serveur gérera la plupart des détails du jeu tels que le scoring, les salons de jeu, etc. Ensuite, nous allons créer des comportements qui envoient des informations de notre jeu au serveur et vice-versa.

Voici le déroulement général de notre jeu :

1. À partir du menu principal, nous allons nous connecter à notre serveur et laisser l'utilisateur décider de créer ou de rejoindre un jeu.

a. Si l'on crée un jeu, charger la salle d'attente.

b. Si l'on rejoint un jeu, charger une liste des salles de jeu disponibles.

c. Lorsque deux joueurs sont dans une salle d'attente, le jeu commence.

2. Dans un jeu, le serveur place aléatoirement les joueurs dans l'une des quatre zones et en désigne un comme étant "It".

a. Taguer l'autre joueur va randomiser leurs emplacements, échanger le statut de "It" et attribuer un point au joueur qui a tagué l'autre.

b. Si aucun tag n'a lieu pendant une certaine durée, le serveur retirera 1 point au joueur qui est actuellement "It" et échangera le statut de "It" avant de replacer les joueurs dans les zones de réapparition à nouveau.

3. Une fois qu'un joueur a atteint un certain nombre de points, il chargera un overlay déclarant un gagnant. Ensuite, les joueurs se déconnecteront de la salle et retourneront au menu principal.

Partie 2 : Configuration du serveur

Créer un serveur Socket.io nécessite de lancer une application Javascript utilisant la bibliothèque Socket.io, écoutant les connexions des joueurs. Socket.io est une bibliothèque de mise en réseau en Javascript qui simplifie beaucoup les rouages de la construction d'une application en réseau pour nous. Plus de détails à suivre.

Télécharger et installer Node.JS

Pour commencer, rendez-vous sur Node.JS et téléchargez-le sur l'ordinateur (Mac, Windows, etc.) sur lequel vous souhaitez héberger le serveur. Une fois le téléchargement terminé, exécutez l'installateur et suivez ses instructions. Toutes les options lors de l'installation peuvent être laissées par défaut. Node.JS nous donne la possibilité d'exécuter des applications Javascript indépendamment du besoin d'un navigateur web.

Téléchargement et exécution de l'exemple de serveur

Ensuite, téléchargez l'exemple de serveur multijoueur de ce didacticiel sur GitHub :

https://github.com/hyperPad/multiplayerServerExample

Cliquez sur le bouton "Clone or download" et sélectionnez "Download ZIP". Cela téléchargera une copie du code pour l'exemple de serveur.

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

Extraire le fichier ZIP téléchargé. À l'intérieur, vous trouverez quelques petits fichiers, mais le fichier le plus notable ici est le fichier "index.js" qui contient le code de notre serveur. Ensuite, ouvrez votre ligne de commande/terminal dans le dossier de l'exemple de serveur.

Tapez "npm install" et appuyez sur Entrée, puis laissez-le s'exécuter. npm est un gestionnaire de paquets qui lit le fichier package.json et télécharge les paquets nécessaires pour notre serveur. (Y compris Socket.io !)

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

Lorsque la commande est terminée, nous avons tout ce dont nous avons besoin pour exécuter le serveur. Dans le terminal, tapez "node ." et le serveur va démarrer.

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

C'est tout ! Notre serveur écoute maintenant sur le port 3000 pour des connexions entrantes Socket.io.

"node ." lance Node.JS pour le répertoire actuel, où il cherchera le fichier index du répertoire et l'exécutera. Par défaut, il s'agit du fichier Javascript "index.js", qui contient la majorité du code de notre serveur et sera analysé tout au long de ce didacticiel.

Laissez le terminal ouvert, car le fermer fermera également le serveur. Vous verrez apparaître des messages lorsque des joueurs se connectent ou se déconnectent, et lorsque des salles sont créées ou détruites, et d'autres événements.

Remarque : Pour permettre des connexions entrantes depuis l'extérieur d'un réseau domestique, vous devrez probablement ouvrir le port 3000 sur votre passerelle domestique. Ce processus varie d'un réseau à l'autre, mais un guide peut généralement être trouvé en effectuant une recherche sur internet pour un guide de redirection de port pour le modem/routeur de votre domicile. Vous devrez peut-être fermer et redémarrer le serveur Node.JS lorsque vous modifiez les paramètres de redirection de port.

Partie 3 : Connexion au serveur

Une fois que vous avez les bases du serveur démarrées, nous pouvons maintenant nous y connecter dans un jeu hyperPad. Cela devrait être configuré comme la première chose qui se passe dans votre projet peu importe la scène sur laquelle vous êtes (puisque tout doit communiquer avec le serveur). Il est préférable de l'attacher à un objet sur la couche globale afin que cela s'applique à toutes les scènes.

Untitled.jpg

Dans le projet exemple, l'étiquette de la couche global "Serveur" contient ce qui précède. (Le téléchargement du projet peut être trouvé dans la Partie 1 de ce didacticiel sous la section Exigences.)

Ces deux comportements sont vraiment tout ce dont vous avez besoin pour vous connecter au serveur. D'abord, sous l'onglet personnalisé, saisissez le comportement Client Socket.io et déposez-le. Ici, vous allez saisir l'URL de votre serveur sous l'onglet URL dans sa fenêtre de propriétés. Dans l'image ci-dessus, l'URL de notre serveur était "http://192.168.0.191:3000", y compris le protocole et le port. Vous voudrez changer cela pour qu'il corresponde à l'URL de votre serveur ou cela ne fonctionnera probablement pas lorsque vous commencerez le jeu.

Maintenant que nous avons les informations du serveur, mais nous devons encore nous y connecter. Donc, tout ce dont nous avons besoin est le comportement de connexion à Socket. Déposez-en un, ouvrez ses propriétés et sélectionnez le client dans la case vide et définissez la propriété Fonction sur "Connecter".

Maintenant, lorsque notre projet se charge, nous allons automatiquement nous connecter à notre serveur.

mceclip1.png

Partie 4 : Création et adhésion aux salles

La prochaine étape consiste à créer nos salons de jeu où les joueurs peuvent se regrouper et jouer ensemble.

mceclip2.png

Voici notre écran de menu principal simple. Tout ce qu'il faut faire, c'est taper sur l'un des boutons pour soit démarrer une salle, soit rechercher des salles disponibles.

Création de salles

Voyons comment nous procédons pour communiquer avec le serveur afin de créer notre salle.

mceclip3.png

Assez simple, n'est-ce pas ? Donc pour résumer rapidement, une fois que nous touchons notre bouton, on nous demandera de taper un nom de salle. Une fois cela fait, ce nom est émis au serveur où la salle est créée et nous chargeons la scène de la salle d'attente.

À partir de maintenant, nous utiliserons souvent Émettre vers Socket. C'est parce que c'est notre principal moyen d'envoyer des données au serveur. Pensez-y simplement comme à la version "en ligne" de Diffuser un message et Recevoir un message (Émettre vers Socket effectue les deux actions à la fois).

Maintenant, nous devons ajouter quelques informations à notre serveur afin qu'il crée la salle une fois qu'il reçoit le message de notre comportement Émettre.

mceclip4.png

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

Cette ligne crée un événement socket (sous forme d'expression lambda) sur le serveur, écoutant les Émissions avec l'événement 'createRoom'.

Le premier paramètre est la valeur que nous passons à travers le comportement Émettre vers Socket, dans ce cas le nom de la salle que l'utilisateur a tapé. Ici, nous avons nommé ce paramètre 'roomName'.

Le deuxième paramètre est une fonction que nous appellerons plus tard dans l'événement socket pour signaler au client. Le comportement Émettre vers Socket continuera d'exécuter uniquement si la fonction callback est appelée sur le serveur. Ici, nous avons nommé ce paramètre 'callback'.

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

Cela va créer une instance d'une structure qui contiendra des informations essentielles sur une salle.

'id' sera un identifiant unique généré par la fonction utilitaire 'uuid()'. Cela nous aidera à identifier spécifiquement cette salle par rapport à une liste de nombreuses autres salles créées plus tard.

'name' sera défini sur le nom que l'utilisateur a tapé plus tôt.

'sockets' sera initialisé comme un tableau vide. Plus tard, cela va suivre les sockets des joueurs connectés à cette salle.

rooms[room.id] = room;

'rooms' est une liste globale des salles actuellement actives. Puisque nous créons une nouvelle salle, nous allons l'enregistrer dans la liste par son ID.

joinRoom(socket, room);

Cela appellera la fonction globale 'joinRoom' (ligne 29), qui ajoutera le socket du joueur à la 'sockets' du salon. Comme le joueur a créé la salle, cela va également leur faire rejoindre celle-ci.

callback();
});

Enfin, nous invoquons la fonction callback afin que le client soit informé que la salle est créée. Cela permettra au comportement Émettre vers Socket de continuer l'exécution, ce qui chargera ensuite la scène de la salle d'attente. Cela marque également la fin de l'événement socket 'createRoom'.

Adhésion aux salles

Rejoindre des salles est un peu différent car nous devrons accéder aux informations d'une scène différente depuis le bouton.

mceclip5.png

Pour rejoindre des salles, nous avons placé le début de nos comportements sur notre couche globale avec où nous nous connectons au serveur. De cette façon, nous pouvons accéder aux informations depuis n'importe laquelle de nos scènes, dans ce cas notre liste de salles. Pour le bouton, en le tapant, cela chargera simplement le joueur dans la scène de la liste des salles.

Ici, nous avons notre deuxième comportement pour communiquer avec le serveur. Événement Socket peut être considéré comme Recevoir un message car il ne s'activera qu'une fois que le message défini a été diffusé depuis le serveur.

La meilleure façon de réfléchir en utilisant Événement Socket et Émettre vers Socket est que l'Événement Socket ne réagit qu'aux informations venant du serveur, tandis que Émettre vers Socket est appelé en réaction à des choses faites localement.

En ce qui concerne notre logique, une fois que nous sommes connectés au serveur, nous allons Émettre la demande 'getRoomNames' pour obtenir tous les noms de salles disponibles.

mceclip19.png

Ensuite, nous définirons une étiquette sur laquelle le joueur peut tapoter pour entrer.

mceclip6.png

C'est notre scène de liste de salles. Ici, toutes les salles de jeu disponibles seront chargées et affichées. En tapant simplement le nom de la salle, cela chargera le joueur. Grâce à nos comportements précédents, la liste chargera automatiquement toutes les salles ouvertes et générera les étiquettes de nom des salles à l'écran. Si rien ne s'affiche, nous avons notre bouton Actualiser la liste qui répète simplement les comportements pour les charger à nouveau.

mceclip7.png mceclip8.png

Ici, nous avons la logique pour configurer la liste. Nous n'allons pas entrer dans trop de détails ici, car nous voulons seulement savoir comment cela se connecte avec notre serveur.

Pour commencer, nous avons notre comportement Émettre vers Socket. Cela sollicite le serveur pour qu'il envoie des informations sur les salles disponibles. À partir de là, nous avons un comportement Obtenir Valeur du Tableau. Toutes les données qui proviennent du serveur seront envoyées sous forme de Tableau et les informations nécessaires seront sur les premières valeurs. Donc, nous définissons notre Obtenir Valeur du Tableau pour obtenir la valeur à l'index 0. Ensuite, nos comportements extrairont ces données et créeront une étiquette pour chaque salle, les générant à l'écran.

Ensuite, nous allons vérifier l'objet que nous devons taper, dans notre scène, il s'agit de l'étiquette appelée Nom de Salle.

mceclip9.png

Ce texte agit comme notre bouton lorsqu'il est généré, mais nous devons encore attacher l'ID de la salle à laquelle nous voulons nous connecter. Pour cela, nous devons d'abord récupérer l'ID de la salle, et nous faisons cela avec le comportement Obtenir Attribut et le définissons comme dynamique. Ensuite, nous émettons au serveur que nous souhaitons rejoindre cette salle et chargeons-nous dans la salle d'attente.

mceclip10.png

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

Ceci est le point d'entrée pour l'événement socket 'joinRoom'. La valeur du premier paramètre serait l'ID de la salle que nous souhaitons rejoindre, c'est-à-dire celui qui a été émis au serveur. Ici, nous avons nommé le paramètre 'roomId'.

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

En utilisant l'roomId donné par le client, nous pouvons trouver l'instance de salle correcte sur le serveur. Avec cela, nous invoquons la fonction globale 'joinRoom' (ligne 29) avec le socket du joueur souhaitant se connecter et l'instance de la salle elle-même. Nous allons prendre un moment pour examiner la fonction 'joinRoom' juste après.

callback();
});

Enfin, nous invoquons la fonction callback pour notifier le client de continuer à charger la scène de la Salle d'attente, marquant la fin de l'événement socket 'joinRoom'.

Alors, que se passe-t-il exactement dans la fonction 'joinRoom' ? Regardons cela de plus près.

mceclip11.png

room.socket.push(socket);

Comme mentionné précédemment, le tableau 'room.socket' suit les sockets connectés dans une salle. Cette ligne fait exactement cela en ajoutant le socket dans le tableau.

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

Ceci est l'appel officiel pour connecter le client à une salle. D'abord, nous disons au socket de rejoindre une salle par son ID. Une fois que cela est fait, la fonction de rappel suivante est invoquée, où nous attachons l'ID de la salle au socket. Enfin, nous enregistrons dans la console qu'un joueur a rejoint une salle !

mceclip12.png

La Salle d'attente

La salle d'attente est simplement une scène dans laquelle nous chargeons les joueurs pendant qu'ils attendent d'autres joueurs pour rejoindre ou pour qu'un jeu commence.

mceclip13.png

Lors de l'entrée dans la salle, nous allons émettre un message 'ready' au serveur. Une fois que le serveur a reçu deux de ces messages, il enverra un message disant qu'il commence le jeu ('initGame'). Nous récupérons ce message avec notre Événement Socket et ainsi, chargeons notre niveau de jeu. Pour nos comportements, c'est à peu près tout. Vous pouvez ajouter un bouton qui vous déconnecte de la salle et vous renvoie au menu principal si vous le souhaitez.

Du côté serveur, analysons le code pour voir ce qui se passe là-bas.

mceclip14.png

Ceci est l'événement 'ready' qui est appelé lorsqu'un client a rejoint une salle et est prêt à se connecter.

const room = rooms[socket.roomId];

Puisque nous avons attaché l'ID de la salle au socket, nous sommes en mesure d'obtenir la salle pour vérifier si le jeu peut commencer.

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

Ici, nous vérifions s'il y a maintenant deux joueurs attendant dans la salle. Disons que c'est désormais vrai, et nous continuons à démarrer le jeu.

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

Maintenant qu'il y a deux joueurs, nous itérons à travers chaque socket et émettons l'événement 'initGame' afin que chaque client charge la scène Niveau, comme montré précédemment.

Partie 5 : Gameplay

À présent, entrons dans le vif du sujet. C'est là où 90 % de notre travail sera. Ci-dessous se trouve notre niveau de jeu conçu pour ce didacticiel.

mceclip15.png

Avant d'entrer dans les détails, nous avons une étiquette intitulée "Logique de Jeu", voyons cela de plus près.

mceclip16.png

Wow, cela fait beaucoup de comportements ! Ne vous inquiétez pas, c'est simplement comment nous faisons apparaître nos joueurs dans le jeu. Regardons de plus près ;

mceclip17.png

Nous commençons avec un comportement Émettre vers Socket qui signale au serveur que notre jeu a commencé, avec l'événement 'startGame'. Nous prenons ensuite le tableau que le serveur retourne, prenons sa première valeur et avec le comportement Obtenir Valeur dans le Dictionnaire, nous obtenons les divers attributs dont notre objet aura besoin. Un arbre distinct fait de même, mais pour l'adversaire. Enfin, nous diffusons le message 'init' à notre objet joueur pour lancer les choses.

Voyons ce qui se passe du côté serveur lorsque nous émettons l'événement 'startGame'.

mceclip20.png

La première moitié de cet événement socket que vous voyez ici consiste à définir certaines valeurs initiales sur chaque client, puis à ajouter tout client qui n'est pas le client émetteur au tableau local 'others'.

mceclip21.png

Dans la seconde moitié, une liste de dictionnaires locale appelée 'ack' est créée. À l'intérieur, nous avons des informations sur nous-mêmes et les autres clients. Nous renvoyons ensuite ces informations au client en passant notre dictionnaire 'ack' à la fonction de rappel, qui devient la valeur résultante du comportement Émettre vers Socket qui l'appelle.

Par la suite, un appel de temporisation de 5 secondes est effectué pour enfin commencer le tour, maintenant que tout le monde a toutes les informations dont ils ont besoin pour jouer au jeu. La fonction 'beginRound' (ligne 99) contrôle la logique spécifique au jeu pour ce projet. Nous n'allons pas aller trop en détail à ce sujet, mais essentiellement cela gère où faire apparaître les joueurs, vérifier les scores et indiquer aux clients qui est "It".

Comme mentionné précédemment, le message 'init' est appelé sur notre étiquette "Logique de Jeu" lorsque tout est prêt à être en marche. Dans l'objet joueur, nous allons maintenant examiner les comportements de celui-ci où il reçoit le message 'init'.

mceclip22.png

Ici, vous pouvez voir que nous avons plusieurs arbres de comportement sur notre joueur. D'abord, nous allons commencer par l'arbre en haut à gauche.

mceclip23.png

C'est le comportement qui lance pratiquement tout le reste de notre jeu.

Tout d'abord, nous recevons le message 'init' envoyé depuis l'étiquette "Logique de Jeu". À partir de là, nous récupérons l'ID du serveur de notre objet et activons l'un de nos Événements Socket ainsi que la configuration de notre écran de jeu afin que nous puissions suivre correctement notre personnage.

Synchronisation des mouvements

mceclip24.png

Ceci est l'un de nos comportements les plus importants. Ce petit arbre est conçu pour mettre à jour la position de notre joueur sur le serveur chaque fois que nous déplaçons le joystick. Vous avez probablement remarqué qu'il fait également référence à certains valeurs du dictionnaire. Nous récupérons ces valeurs d'un comportement de dictionnaire autonome qui contient les positions X et Y de notre joueur.

Jetons un œil à l'événement 'moved' sur le serveur.

mceclip25.png

data = JSON.parse(data);

Les dictionnaires, lorsqu'ils sont envoyés d'un client à un serveur, doivent être analysés afin de lire facilement les données. C'est parce que les dictionnaires dans hyperPad sont encodés en une structure JSON lorsqu'ils sont émis à un serveur. Cette ligne analyse la structure de chaîne JSON et stocke la liste de dictionnaires dans la même variable locale 'data'.

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

Ici, nous mettons à jour les positions X et Y stockées sur le socket avec les nouvelles valeurs fournies par le 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
});
}

Ensuite, nous itérons à travers tous les clients pour les mettre à jour de notre nouvelle position et d'autres détails, excluant notre propre client (c'est-à-dire que le client émetteur n'a pas besoin de connaître sa propre position).

mceclip26.png

Ce comportement contrôle en grande partie notre jeu. Nous utilisons un Événement Socket lorsque le serveur vérifie quel joueur est désigné comme "It", qui est émis par 'beginRound' (ligne 99) sur le serveur.

Ensuite, nous récupérons l'ID de serveur de notre objet à partir du tableau et utilisons la Valeur du Dictionnaire pour le décomposer en différents morceaux de données. À partir de là, nous récupérons notre Score, si nous sommes désignés comme "It", ainsi que les positions x et y de notre objet, puis les appliquons aux attributs de l'objet. Le reste des comportements sert à définir et contrôler l'interface utilisateur dans notre jeu.

Conclusion

Il y avait beaucoup de choses à assimiler ici, mais j'espère que si vous êtes arrivé si loin, vous devriez avoir une compréhension de la manière d'utiliser les comportements Socket.io pour créer des expériences multijoueurs pour vos joueurs.

Essayez vous-même ! Prenez un jeu existant que vous avez créé et essayez de lui ajouter une certaine fonctionnalité en ligne, comme une scène de classement de scores élevée qui se connecte à un serveur et demande une liste des 10 meilleurs scores avec les noms des joueurs à afficher.

Il est difficile d'enseigner un langage de script tel que Javascript dans un seul article. Heureusement, si vous rencontrez des problèmes, il existe de nombreuses autres ressources pour vous aider à écrire des applications Javascript pour Node.JS et Socket.io ;

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

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

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