Appuyez sur Entrée pour voir vos résultats ou Echap pour annuler.

Tutorial : Création d’un jeu multi-joueurs basique avec Phaser et Eureca.io

L’objectif de ce tutorial est de vous montrer comment créer un jeux HTML5 multijoueurs basique.

Pour celà, je vais utiliser Phaser pour le développement du jeu, et Eureca.io pour les communications client/serveur.

Ce tutorial suppose que vous avez déjà des connaissances de base en développement de jeux HTML5 (avec Phaser de préférence)

Il suppose également que vous avez quelques connaissances de nodejs, et que vous l’ayez déjà installé sur votre PC.

Je vais partir d’un code fourni en exemple sur la page officielle de Phaser, c’est une jeu de tank simple.

 

Voici une vidéo qui montre le résultat final que nous allons obtenir à la fin du tutorial

 

c’est parti 🙂

 

Première étape : refactoring du code

Quelques modifications et simplifications sont nécessaire pour rendre le jeu adaptable au multi-joueurs.

Ici j’ai factorisé le code des Tanks du joueur humain et ennemi dans une unique classe nommée Tank (je suis parti de la classe EnemyTank fournie dans le code original), car en mode multi-joueurs, on ne distinguera pas le Tank Ennemi du Tank humain, il dériveront de la même classe ; après tout, le Tank ennemi n’est autre qu’un humain de l’autre coté du réseau 😉

La création et « l’update » ont été déplacé dans le code de la classe Tank

J’ai également ajouté une methode Tank.kill(), qui permer de supprimer un Tank de la scène du jeu, et déplacé la méthode fire() dans Tank.fire()

 

J’ai aussi supprimer la gestion des dommages sur un Tank quand il est touché, histoire de simplifier le code

et enfin, j’ai modifié la manière dans les controles claviers sont effectués :

le code original utilise phaser.input (via game.input.keyboard.createCursorKeys())
mais dans notre cas, le client ne devra pas gérer les entrée clavier directement, j’expliquerais celà plus loin.

J’ai donc créé des objets Tank.input et Tank.cursor pour gérer les commandes clavier.
ce qui donne ce code modifié de la méthode update()

 

 

Vous pouvez télécharger le code modifié ici, ainsi vous pourrez suivre les prochaines étapes du tutorial.

Télécharger le code modifié du jeu de Tanks

 

 

Maintenant que nous avons un code simple qui fonctionne, nous allons le transformer en un jeu multi-joueurs;

Je vais utiliser Eureca.io, une librairie pour le RPC que j’ai dévelopé pour des usages internes à Ezelia, et que j’ai décidé de rendre publique (Code source disponnible ici https://github.com/Ezelia/eureca.io)
Les même principes qui seront utilisé ici sont applicables à n’importe quelle autre librairie réseau (socket.io, engine.io …etc), mais nous allons voir comment Eureca.io simplifie le travail 😉

 

Le serveur web

Nous commençons par créer un simple serveur web pour notre jeu
pour celà nous allons installer la librairie expressjs pour nodejs agin de gérer plus simplement la partie web, de plus expressjs est compatible avec Eureca.io.
expressjs est très efficace aussi pour la création de pages web dynamiques sous nodejs, au cas ou vous souhaitez habiller le site web qui héberge votre jeu.

 

maintenant on va créer un fichier server.js dans le répertoire racine du jeu, avec le contenu suivant

reste à lancer le serveur en tapant la commande

 

ouvrez votre navigateur et allez à l’adresse : http://localhost:8000/

Si le jeu fonctionne vous êtes prêt pour l’étape suivante

Vous pouvez télécharger le code de cette étape ici 🙂

Tanks Game Etape 1

 

Installation et preparation de eureca.io

Nous allons commencer à jouer avec Eureca.io maintenant

Eureca.io peut aussi bien utiliser engine.io ou sockjs comme couche de transport réseau, par defaut c’est engine.io qui est utilisée.

afin d’utiliser eureca.io avec la configuration par defaut, nous avons besoins d’installer eureca.io et engine.io.

 

Maintenant, nous allons modifier le code de notre serveur pour ajouter quelques routines eureca.io.

juste avant : server.listen(8000)

on créé une instance d’eureca.io Server, et nous l’attachons au serveur HTTP comme le montre ce code :

Ensuite on ajoute des Event listeners pour detecter les connexions/deconnexion de clients.

Coté client nous allons faire aussi des modifications

d’abord, il faut ajouter la ligne suivante dans le fichier index.html avant le script tanks.js

cette ligne rend eureca.io accessible au client.

maintenant on édite le fichier tanks.js pour ajouter le code suivant au tout début du fichier

ici nous avons créé une methode eurecaClientSetup, qui instantie le client eureca.io, attend que ce dernier soit prêt (établissement de connexion avec le serveur) puis appel la methode de création du jeu (create());

la méthode create(), était initiallement appellée par Phaser.Game() lors de l’instanciation du jeu, nous devons modifier ceci afin que ce soit la méthode d’instanciation devienne eurecaClientSetup et non create()

Important : quand on créé un jeu multijoueurs, il est souvent nécessaire de s’assurer que le serveur est disponnible avant de lancer le code du jeu, c’est exactement ce que fait eurecaClientSetup ici

une dernière chose

vous avez due noter la présence de la variable « ready » initialisée à Falce par defaut ; ce flag nous permet de savoir si l’initialisation client/serveur est terminée, et que le jeu est créé.
nous utilisons ce flag pour empêcher phaser d’appeler la methode update avant que l’appel à create() ne soit fait.

nous avons donc besoin d’ajouter ceci à la méthode update()

 

Le code de cette étape est disponible ici :

Télécharger Tanks Game Etape 2

relancez server.js (node server.js) puis allez à l’adresse http://localhost:8000/

le jeu devrait se lancer, et le serveur devrait détecter des connexions client.

rafraichissez la page du navigateur ; vous allez voir que le serveur detecte une déconnexion/reconnexion du client.

si celà marche, on peut passer à l’étape suivante.

 

Spawn/Kill de joueurs distants

pour notre jeux basique, le serveur doit garder une trace de tout les clients connectés.
pour distinguer les clients, nous avons également besoin d’un identifiant unique pour chaque joueur (nous utiliserons un ID unique généré par eureca.io)
cet identifiant sera partagé entre les clients et le serveur ; il leur permettera de synchroniser les données des joueurs, et faire la correspondance entre les clients distants et leur Tanks.

Voici l’implémentation que nous allons réaliser

  • Quand un client se connecte, le serveur lui créé un id unique (c’est le session ID d’eureca.io qui sera utilisé)
  • Le serveur envoi cet id unique au client.
  • Le client créé la scène du jeu avec le Tank du joueur et assigne l’id unique à ce Tank.
  • Le client notifie le serveur que tout est pret de son coté (c’est ce qu’on appel un « handshake »)
  • Le serveur reçoit cette notification, et appel la méthode spawn() sur tout les clients connectés
  • Chaque client créé une instance pour chaque Tank pas encore créé.

 

  • Quand un client se déconnecte, le serveur l’identifie et le retire de la liste des clients connectés
  • Le serveur appel la methode kill() chez tout les clients connectés, en passant l’id du client qui vient de quitter le jeu.
  • Chaque client execute le kill() et supprime l’instance du Tank identifié.

 

Voici ce que celà donne en pratique

Coté client

Les instances (client et serveur) d’Eureca.io ont un namespace appelé « exports » ; toutes les méthodes définies sous ce namespace deviennent accessible en RPC.

Nous allons voir comment celà marche concrètement.

nous modifions la méthode eurecaClientSetup comme ceci.

dans le code ci-dessus, nous avons trois méthodes qui seront appellable depuis le serveur : setId, kill et spawnEnemy.
Notez aussi que notre client appel une méthode coté serveur : eurecaServer.handshake().

 

Coté Serveur

La première des choses à faire coté serveur est de lui dire que les méthodes client (setId, kill and spawnEnemy), sont de confiance et qu’elles peuvent être appellées, autrement, eureca.io refusera ces appels.

quand on fait du client/serveur, le serveur ne dois jamais faire confiance au client.

le code suivant demande à Eureca.io de faire confiance aux methodes et de créer un objet clientList qui stockera les données des clients.

On modifie ensuite les méthodes onConnect et onDisconnect

notez comment le serveur appel simplement les méthodes que nous avons définies coté client : remote.setId(conn.id) et remote.kill(conn.id);

 

 

Si vous avez suivi, le client devait aussi appeller une méthode coté serveur, nous devons donc la déclarer.

 

Maintenant, lancer le serveur et ouvrez une première fenêtre du navigateur sur http://localhost:8000/

déplacez le tank puis ouvrez une deuxième fenêtre sur http://localhost:8000/

vous devez voir un deuxième Tank aparraitre dans la première fenêtre.

fermez la deuxième fenêtre … et le tank dois disparraitre.

c’est déjà pas mal hein ? 🙂 mais ce n’est pas encore du multijoueurs

Comme vous le remarquez, les déplacements des tanks ne sont pas encore répliqués, et c’est ce qu’on va faire dans la prochaine étape.

 

au passage, vous trouvez ci-dessous le code de cett étape 😀

Télécharger Tanks Game Etape 3

 

Gestion des commandes clavier / Synchronisation des états

Dans un jeu multijoueurs, le serveur doit controler les états des clients, la seul entitée de confiance dans une telle configuration c’est le serveur (il existe d’autres configuration comme le P2P par exemple … mais ce n’est pas le sujet de ce tutorial)
une implémentation idéal d’un jeu client/serveur, consisterait en une simulation coté client ET serveur des mouvement, ensuite le serveur envoi un état au client qui corrige/compense sa simulation locale.

dans notre exemple nous alons synchroniser un ensemble minimum d’information et « faire confiance » à la simulation client. (en gros, il n y aura pas de simulation coté serveur)

Voici l’implémentation :

  • Quand un joueur fait une commande (déplacement ou tir), celle-ci ne sera pas géré directement par le client.
  • La commande sera envoyée au serveur, le serveur transmettera cette commande à tout les clients connectés, y compris celui qui vient de lui envoyer la commande.
  • Chaque client applique la commande à l’instance locale du Tank en question.
  • Le Tank gère la commande reçues du serveur comme si c’était une commande locale

en plus de ça, à chaque fois qu’une commande est envoyée, nous allons aussi transmettre la position du Tank, cette information sera utilisée par le client pour se synchroniser avec le serveur.

on passe au code !

Coté client

On edite encore une fois eurecaClientSetup pour ajouter une nouvelle méthode coté client.

rappel: les méthodes du namespace exports sont appellables depuis le serveur.

la méthode updateState va mettre à jour Tank.cursor avec la commande transmise par un joueur.

elle va aussi rectifier la position du Tank et son angle si besoin. (synchronisation)

 

nous devons gérer les commandes transmises dans Tank.update.

pour celà on édite Tank.prototype.update pour remplacer la ligne :

par ce code

ici nous vérifions que le joueur a entré une commande (client ou touche clavier), si c’est le cas, nous envoyons cette commande au serveur via eurecaServer.handleKeys.

la méthode handleKeys coté serveur va transmettre la commande reçue à tout les client, comme on va le voir juste après.

 

Coté serveur

Nous devons d’abord autoriser la nouvelle méthode ajoutée coté client (updateStates)

 

ensuite on déclare la méthode handleKeys

 

puis une petite modification à la méthode handshake existante

 

Si vous avez bien suivi toutes les étapes (ou que vous avez téléchargé le code final ci-dessous 😀 )

Vous pouvez lancer le serveur et ouvrir deux ou plusieurs fenêtres de votre navigateur.

Maintenant quand vous déplacez un tank ou que vous tirez, les mouvements et les tirs sont répliqué partout !

Pour aller plus loin

Vous avez maintenant un code basique et des notions d’une implémentation multi-joueurs.

Pour vous exercez vous pouvez essayez de gérer les dommages sur un tank, les kill et respawn … vous pouvez également tenter de mieux gérer la synchronisation des mouvement, par exemple au lieu de changer brusquement la position du tank, le faire déplacer doucement vers la position qu’il est censé avoir …etc

si vous avez aimé ce tutorial je vous invite à le partager

et bien entendu, vos commentaires et suggestions sont les bienvenus.

 

Télécharger Le code final

Vous avez aimé ce tutorial ?
Supportez eureca.io ! Flattr this