Créez un jeux en ligne (MMO) en HTML5 avec Phaser et Socket.IO – partie 3

thumbnail partie 3 tutorial

Sommaire

Introduction

Dans l’article précédent on a vu comment fonctionne un jeux en ligne à base de socket, on a pu connecter plusieurs joueurs dans le jeux et réussi à synchroniser les déplacements des joueurs.

Dans cet article on va voir comment intégrer un système de chat, on va voir comment créer l’interface graphique nécessaire pour le module de chat, ainsi que le code nécessaire pour envoyer et recevoir des message de chat.

Comme pour l’article précédent je ne vais mettre dans cet article que le code concernant le module de chat, le reste est disponible sur le code dans github.

Interface graphique (GUI) ?

Une interface graphique (GUI), correspond aux éléments graphiques utilisés dans un jeux pour afficher des informations au joueur, par exemple : le score, les points de vies, le menu, les messages de chat…

La question qui se pose alors, est comment gérer une GUI dans un jeux HTML5 (phaser) ? pour cette question il y a deux réponses possibles :

1 – Utiliser Javascript + Canvas

Ceci va impliquer de créer un objet Bitmapdata pour dessiner dessus notre interface graphique, cette solution est selon moi compliquée à mettre en place, difficilement customisable, et peux engendrer des problèmes de performances (une partie du CPU est utilisée pour exécuter le Javascript de notre jeux sera occupée à gérer la GUI, et ça c’est pas bien)

2 – Utiliser de l’HTML + CSS

Un navigateur web utilise par défaut le language CSS pour gérer le style d’un site web, cette solution est plus facile à mettre en place, et n’a pas un aussi grand impact sur les performances que de dessiner directement sur un objet Canvas.

Pour ce tutorial on va opter pour la deuxième solution, on va utiliser HTML et CSS pour mettre en place notre GUI, ça va nous permettre de nous concentrer sur le code de notre jeux sans passer trop de temps sur la GUI.

Demo

Voici une demo du résultat final, vous pouvez chatter avec les autres lecteurs directement sur cette page, si il n’y a personne ouvrez cette page sur un autre navigateur et testez par vous même le système de chat.

Le code source sur GitHub

Ajout d’un écran d’authentification

Afin d’identifier les joueurs dans le module de chat, il nous faudra ajouter un écran d’authentification, pour cette démo on va juste ajouter un écran contenant un champ pour remplir son nom d’utilisateur.

L’écran d’authentification sera ajouté entre l’écran de chargement et l’écran du jeux, le cheminement des écrans ressemblera donc à ça :

Boot (Chargement des resources) –> Login (Authentification du joueur) –> Play (Le jeux)

GUI de l’écran d’authentification

L’ecran d’authentification sera constitué d’un champ texte et un libelle, donc la première chose à faire sera de trouver la structure HTML qu’il faudra utiliser, pour garder les choses simples on va utiliser cette structure HTML :

<div class="gui-panel gui-panel-medium game-login-panel">
   <form>
      <div class="game-input-block">
         <div class="game-gui-label">Enter a nickname</div>
         <input type="text" class="game-gui-input">
      </div>
      <button class="game-login-button">GO !!</button>
   </form>
</div>

  

On a choisi la structure de notre écran, il faudra maintenant créer cette structure avec Javascript.

Pour cette démo on va créer cette structure directement avec l’API DOM de Javascript sans passer par une librairie spécialisée, mais sachez qu’il est conseillé d’utiliser une librairie comme HandleBars pour gérer la structure de vos gui si vous avez beaucoup d’HTML à gérer dans votre code, ça vous aidera à garder la partie HTML bien séparée du code de votre jeux.

» Afficher le fichier : client/states/login.js

'use strict';

var ChatManager = require('client/utils/ChatManager');
var DomHelper = require('client/utils/DomHelper');

var nickNameInput;
var domToRemove = [];

function Login(){}


Login.prototype = {

    create: function(){
        this.game.stage.backgroundColor = 0x66990D;

        DomHelper.init(this.game);
        domToRemove = [];
        this.showLoginPanel();
    },
    showLoginPanel: function(){
        var me = this;
        var panel = DomHelper.mediumPanel(180, 120, 'game-login-panel');
        var form = DomHelper.form(saveName);
        var blockInput = DomHelper.inputBlock();

        nickNameInput = DomHelper.inputWithLabel(blockInput, 'Enter a nickname', 200, 200);

        var saveButton = DomHelper.createButton('GO !!', 'game-login-button');

        form.appendChild(blockInput);
        form.appendChild(saveButton);
        panel.appendChild(form);

        domToRemove.push(panel); // removing the panel will remove all its childs

        function saveName(){
            me.game.mainPlayerName = ChatManager.setMainPlayerName(nickNameInput.value);
            if(me.game.mainPlayerName){
                me.cleanDom();
                me.game.state.start('play');
             }
             nickNameInput.value = '';
        }
    },

    cleanDom: function(){
        for(var i = 0, max = domToRemove.length; i < max; i++){
            domToRemove[i].remove();
        }
    }
};

module.exports = Login;

» Cacher

Afin de faciliter la gestion des éléments HTML, on va créer un Objet pour nous aider à créer des éléments qu’on va appeler : DomHelper

» Afficher le fichier : client/utils/DomHelper.js

'use strict';

var containerElement, verticalOffset = 0, horizontalOffset = 0;

function getY(y){
    return y - verticalOffset;
}

function getX(x){
    return x - horizontalOffset;
}


module.exports = {
    init: function(game){
        containerElement = document.getElementById(game.parent);
        verticalOffset = game.height;
    },

    mediumPanel: function (x, y, cssClass){
        if(!cssClass){
            cssClass = '';
        }
        var panel = document.createElement('div');
        panel.className = 'gui-panel gui-panel-medium ' + cssClass;
        panel.style.left = getX(x) + 'px';
        panel.style.top = getY(y) + 'px';

        containerElement.appendChild(panel);

        return panel;
    },

    form: function(onSaveCallback){
        var form = document.createElement('form');
        form.onsubmit= function(){
            onSaveCallback();

            return false;
        };

        return form;
    },

    inputBlock: function(){
        var blockInput = document.createElement('div');
        blockInput.className='game-input-block';
        return blockInput;
    },

    inputWithLabel: function(parent, label, x, y){


        var nameLabel = document.createElement('div');
        nameLabel.className='game-gui-label';
        nameLabel.innerText = label;


        var nameInput = document.createElement('input');
        nameInput.type = 'text';
        nameInput.className = 'game-gui-input';

        parent.appendChild(nameLabel);
        parent.appendChild(nameInput);

        return nameInput;
    },

    createButton: function(label, cssClass){
        var button = document.createElement('button');
        button.className = cssClass;

        button.innerText = label;
        return button;
    }

};

» Cacher

Ce code va afficher un écran d’authentification au joueur, après avoir cliqué sur le bouton le nom du joueur sera enregistré dans l’objet ChatManager qu’on verra un peu plus tard, après ça on va passer à l’écran du jeu.

Remarquez la présence de la fonction cleanDom, on appelle cette fonction pour supprimer les éléments qu’on a créé avant de passer à l’écran suivant, si on ne fait pas ça l’HTML qu’on a utilisé durant l’écran d’authentification restera affiché sur l’écran du jeu.

Ajouter du CSS avec LESS

On a créé la structure HTML, maintenant on va appliquer du css dessus pour que ça ressemble au résultat voulu.

Pour ce tutorial on va utiliser LESS pour écrire notre css, pour ceux qui ne connaissent pas encore, LESS est une extension de CSS, ça ajoute des fonctionnalités très utiles comme les variables et les fonctions, et puisque on utilise brunch comme outil de build, on aura quasiment rien à faire pour l’installer, d’ailleurs, il est installé depuis le début du tutorial, ouvrez le fichier package.json, et vous verrez cette ligne :

...
"devDependencies": {
...
    "less-brunch": "^1.8.1"
  },
  ...

On constate donc que LESS est correctement installé dans notre environnement de développement.

Le code LESS pour l’écran d’authentification sera mis dans le fichier _loginScreen.less et aura le contenu suivant :

» Afficher le fichier : client/styles/_loginScreen.less

.gui-panel{
  position: relative;
  padding: 10px;
  background: #FFF;
}

.gui-panel-medium {

  user-select: none;

  width: 400px;
  height: 160px;

  margin-bottom: 20px;
  background-color: #fff;
  border: 6px solid #43606a;
  border-radius: 4px;
}

.game-input-block {

  font-family: @game-font;

  .game-gui-label {
    text-align: center;
    font-size: 20pt;
  }

  .game-gui-input {
    width: 99%;
    height: 40px;
    font-size: 20pt;
    margin-top: 5px;
    text-align: center;
  }

}

.game-login-button{

  margin-top: 20px;
  width: 100%;
  height: 40px;

  font-family: @game-font;
  font-size:14pt;
}

» Cacher

On a terminé la partie authentification, la prochaine étape est de créer le module de chat et l’intégrer dans l’écran du jeux.

L’objet ChatManager

Afin de centraliser la gestion de notre module de chat, on va créer un objet ChatManager qui va effectuer les taches suivantes :

  • Créer la gui de la chat-box
  • Envoyer les messages écrits par le joueur au serveur
  • Afficher les messages envoyés par les autres joueurs

Création de la gui du module de chat

On va commencer par la partie graphique de notre module de chat, avant de commencer le code, on va d’abort réfléchir à la structure de notre module de chat, ça devrait donner quelquechose qui ressemble à ça :

chatbox prototype

Chatbox prototype

l’étape suivante est de mettre en place une structure HTML qui va gérer ça

<div id="game-chat-box">
     <div class="game-chat-messages">
        Messages goes here
     </div>
     <form>
        <input type="text" class="game-chat-input">
     </form>
 </div>

ChatManager : Création de la structure html

On va commencer par voir la fonction qui se charge de construire le code HTML de la structure qu’on a définit :

» Afficher le fichier : client/utils/ChatManager.js

...

function initGuiElements(containerId){
    var container = document.getElementById(containerId);

    var chatBox = document.createElement('div');
    chatBox.id = 'game-chat-box';

    messagesBox = document.createElement('div');
    messagesBox.className = 'game-chat-messages';

    var chatForm = document.createElement('form');

    chatForm.onsubmit= onSendMessage;

    chatInput = document.createElement('input');
    chatInput.type = 'text';
    chatInput.className = 'game-chat-input';

    chatForm.appendChild(chatInput);

    chatBox.appendChild(messagesBox);
    chatBox.appendChild(chatForm);

    container.appendChild(chatBox);
}

...

» Cacher

Comme pour l’écran d’authentification, on va créer la structure HTML avec du Javascript, ici on va centraliser la création de la structure dans la fonction initGuiElements.

Après avoir créé la structure, on va Utiliser LESS pour gérer le css du module de chat :

» Afficher le fichier : client/styles/_chatbox.less

#game-chat-box{

  // variables to make the chat box configuration a little bit less confusing
  @chatboxWidth: 400px;
  @messagesBoxHeight: 120px;
  @inputHeight: 20px;
  @bottomSpacing: 142px;


  width: @chatboxWidth;
  float: right;

  position:relative;
  bottom: @bottomSpacing;

  .game-chat-messages{
    width: 390px;
    height: @messagesBoxHeight;

    overflow-y: scroll;
    background: #000;
    background: rgba(0,0,0, .5);
    color: white;
    font-size: 9pt;
    font-family: @game-font;

    padding-left: 5px;
    padding-right: 5px;

    user-select: none;

  }

  .game-chat-input{
    width: 100%;
    border: 0;
    height: @inputHeight;
    font-size:9pt;
  }

  .game-message-author{
    color: #E9D246;
  }

  .game-message {
    font-size: 8pt;
    font-weight: bold;
  }
  .game-message-type-info {
    .message-author{
      font-style: italic;
      color: #dbe7ff;
    }
  }

  .game-message-type-error {
    color: #ff5b54;

    .message-author{
      font-style: italic;
      color: #dbe7ff;
    }
  }
}

» Cacher

Envoyer des messages text au serveur

A ce stade on arrive à afficher une boite de chat, on va donc lui donner vie en ajoutant la possibilité d’envoyer un message au serveur, commençons par faire en sorte d’envoyer un message dès que le joueur tape la touche [Entrée].

...
function initGuiElements(containerId){
  ...

  chatForm.onsubmit= onSendMessage;

  ...
}

...

L’envoi de message doit être déclenché par l’appui sur la touche entrée, pour faire ça on va utiliser l’attribut onSubmit de la balise form, en assignant la fonction onSendMessage à l’attribut onsubmit, elle sera déclenchée dès que le joueur appuie sur entrée.

» Afficher le fichier : client/utils/ChatManager.js

...

function onSendMessage(){
    var textMessage = escapeHtml(chatInput.value);

    NetworkManager.sendChatMessage(textMessage);

    appendMessage(mainPlayerName, textMessage);

    chatInput.value = '';

    return false;
}
...

function escapeHtml(unsafe) {
    return unsafe
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;");
}

» Cacher

Dans la fonction onSendMessage on va lire le texte entré par le joueur, et l’envoyer au serveur à travers NetworkManager en utilisant la fonction NetworkManager.sendChatMessage.

Après l’envoi du message au serveur, on va afficher le message que le joueur viens d’envoyer dans la chatBox avec la fonction appendMessage, notez bien qu’on passe le nom du joueur et le texte à cette fonction, le nom du joueur est ce lui qu’on a renseigné dans l’écran d’authentification.

Voyons maintenant comment envoyer le message du joueur au serveur :

» Afficher le fichier : client/utils/NetworkManager.js


...

var networkManager = {

...
sendChatMessage: function(textMessage){
        serverSocket.emit('CLIENT_CHAT_MESSAGE', {
            uid: mainPlayer.uid,
            nickname: mainPlayer.nickname,
            text: textMessage
        });

    }

...
};

» Cacher

On va envoyer au serveur l’information en utilisant un evennement socket qu’on va nommer CLIENT_CHAT_MESSAGE, comme vous pouvez le voir, on va envoyer le message et l’identifiant unique du joueur pour identifier la source du message, pour avoir plus de détails sur les intéractions client serveur du jeux, vous pouvez revenir à la partie 2 du tuto.

Petite note à propos de la sécurité

Il y a un vieil adage dans le domaine du génie logiciel qui dit : »Never trust user input », qui se traduit en français comme : « Ne faites jamais confiance en ce qu’un utilisateur vous envoi ».

ce qui veux dire dans notre cas qu’il faudra prendre le temps de bien vérifier les données qu’un utilisateur voudra envoyer dans le chat.

Oui mais pourquoi c’est si important ?

Imaginez qu’un des des joueurs se mette à envoyer dans le chat du code Javascript au lieu de texte en clair, il aura à ce moment le contrôle sur les pages web de tout les autres joueurs, et ça c’est ce qu’on appelle une grosse faille de sécurité XSS.

Voici un lien utile qui éxplique XSS ( cross site scripting)

Afin de garder le code bien sécurisé, pensez a vérifier les messages avant de les envoyer au serveur, et de refaire une deuxième vérification du coté serveur avant de les renvoyer au autres joueurs.

Pour le moment on va implémenter une sécurité basique du coté client avec la fonction escapeHtml, qui va enlever les characères spéciaux du text envoyé par le joueur.

Serveur : renvoi des messages au autres joueurs

On a vu comment envoyer un message au serveur, maintenant il va faloir renvoyer ce message au autres joueurs :

» Afficher le fichier : server/game/gameServer.js


... 

var MESSAGE_MAX_LENGTH = 100;

...

function onClientConnected(client){
    ...
    client.on('CLIENT_CHAT_MESSAGE', onChatMessage);

}

...

function onChatMessage(chatMessageInfo){
        chatMessageInfo.nickname = filterInput(chatMessageInfo.nickname, NICKNAME_MAX_LENGTH);
        chatMessageInfo.text = filterInput(chatMessageInfo.text, MESSAGE_MAX_LENGTH);

        client.broadcast.emit('SERVER_PLAYER_CHAT_MESSAGE', chatMessageInfo);
    }
...


» Cacher

Ici on va faire comme dans la partie 2, on configure la fonction onChatMessage pour être exécutée quand le serveur reçoit du client l’événement CLIENT_CHAT_MESSAGE.

Vous remarquerez qu’avant de renvoyer le message aux autres joueurs on va filtrer une deuxième fois les caractères spéciaux, on en profite pour limiter la taille des messages à 100 caractères, je me suis arrêté là mais vous pouvez ajouter si vous voulez d’autres filtres comme :

  • Detecteur de gros mots : parce que dire des gros mots c’est pas gentils 🙂
  • Anti spam
  • Enregistrer un historique des messages

Recevoir les messages des autres joueurs

Le serveur renvoi bien les messages textes aux autres joueurs, il ne reste plus qu’a les afficher sur la chatBox.

On va donc configurer le client pour écouter les messages textes provenant du serveur comme suit :

» Afficher le fichier : client/utils/NetworkManager.js

...

var networkManager = {

...

configureIncomingTraffic: function(){

  ...

  serverSocket.on('SERVER_PLAYER_CHAT_MESSAGE', onReceiveChatMessage);

  ...

}

setOnReceiveChatMessage: function(callback){
        onReceiveChatMessageCallback = callback;
    }
}


function onReceiveChatMessage(messageInfo){
    onReceiveChatMessageCallback(messageInfo);
}

» Cacher

Finalement on initialise le module de chat dans le fichier client/states/play.js :

» Afficher le fichier : client/states/play.js

...

Play.prototype = {
...
  create: function(){
    ...
    this.initChatModule();

    this.connectToServer();
  }
  ...

  initChatModule: function(){
        ChatManager.init(this.game.parent);

        NetworkManager.setOnReceiveChatMessage(function(messageInfo){
            ChatManager.appendMessage(messageInfo.nickname, messageInfo.text);
        });
    }
}

» Cacher

On va initialiser le module de chat avec la fonction initChatModule , cette fonction va faire les choses suivantes :

  • ChatManager.init : Initialiser le module de chat, on va passer l’id de la balise qui contient notre jeux.
  • NetworkManager.setOnReceiveChatMessage : Configurer la fonction à exécuter quand le client reçoit un message de chat depuis le serveur.

Conclusion

On a vu dans cet article comment créer la gui de votre jeux avec HTML et CSS, je ne suis pas rentré dans les détails, mais ça fera surement l’objet d’un article dans le future.

Après la gui on a vu comment échanger les messages texte entre les joueurs à travers le serveur, et parlé de l’importance à vérifier la validité du message à envoyer au niveau du serveur pour protéger vos joueurs des failles de sécurité ( XSS par exemple)

Je ne sais pas si j’ai assez détaillé cet article, donc n’hésitez pas à regarder le code sur github et de l’exécuter sur votre ordinateur pour bien comprendre le fonctionnement du module de chat, biensur n’hésitez pas à me poser des questions dans la zone de commentaires.

Dans le prochain article on va voir comment gérer l’interaction des joueurs avec des objets dans la map, n’hésitez pas à vous inscrire à la newsletter pour être prévenu dès que l’article sera prêt.

Partie 4 : Interagir avec des objets dans la map

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *