Fermer

décembre 5, 2019

Comment créer un jeu de réalité virtuelle multijoueur en temps réel (2e partie)


À propos de l'auteur

Doctorat en intelligence artificielle chez UC Berkeley, axé sur les petits réseaux de neurones en perception, pour véhicules autonomes. Grand amateur de cheesecake, corgis, et…
Plus d'informations sur
Alvin
Blême

Dans ce didacticiel, vous allez décrire les mécanismes de jeu d'un jeu de réalité virtuelle, étroitement associé aux éléments multijoueurs en temps réel du jeu.

Dans cette série de didacticiels, nous allons jeu de réalité virtuelle multijoueur basé sur le Web, dans lequel les joueurs devront collaborer pour résoudre un puzzle. Dans la première partie de cette série, nous avons conçu les orbes présentés dans le jeu. Dans cette partie de la série, nous ajouterons la mécanique du jeu et la configuration des protocoles de communication entre paires de joueurs.

La description du jeu ci-dessous est extraite de la première partie de la série: Chaque paire de joueurs reçoit un anneau d'orbes. L’objectif est d’allumer toutes les orbes, où une orbe est allumée si elle est élevée et brillante. Un orbe est désactivé s'il est plus bas et plus sombre. Cependant, certaines sphères «dominantes» affectent leurs voisins: si elles changent d'état, ses voisins changent également d'état. Le joueur 2 peut contrôler les globes pairs, et le joueur 1, les globes impairs. Cela oblige les deux joueurs à collaborer pour résoudre le puzzle.

Les 8 étapes de ce didacticiel sont regroupées en 3 sections:

  1. Remplissage de l'interface utilisateur (étapes 1 et 2)
  2. Ajout du mécanisme de jeu (étapes 3 à 5)
  3. Communication de configuration (étapes 6 à 8)

Cette partie s'achèvera par une démonstration en ligne entièrement opérationnelle à la disposition de tous. Vous utiliserez A-Frame VR et plusieurs extensions A.

Vous pouvez trouver le code source fini ici .

 Le jeu multijoueur terminé, synchronisé sur plusieurs clients
Le jeu multijoueur terminé, synchronisé sur plusieurs clients. ( Grand aperçu )

1. Ajouter des indicateurs visuels

Pour commencer, nous allons ajouter des indicateurs visuels de l’ID d’un orbe. Insérer un nouvel élément a-text VR en tant que premier enfant de # container-orb0 en L36.


    
     ...
    
        ...
    

Les «dépendances» d’un orbe sont les orbes qu’il va basculer lorsqu’il est basculé: par exemple, disons que orbe 1 a pour dépendances orbes 2 et 3. Cela signifie que si orbe 1 est activé, les orbes 2 et 3 le seront également. Nous ajouterons des indicateurs visuels des dépendances, comme suit, directement après .animation-position .



            

Vérifiez que votre code correspond à notre code source de l’étape 1 . Votre orbe devrait maintenant correspondre à ce qui suit:

 Orbe avec indicateurs visuels pour l'identifiant de l'orbe et ID des orbes qu'il déclenchera
Orbe avec indicateurs visuels pour l'identifiant de l'orbe et pour les orbes qu'il déclenchera ( Grand aperçu )

Ceci conclut les indicateurs visuels supplémentaires dont nous aurons besoin. Ensuite, nous allons ajouter dynamiquement des orbes à la scène de réalité virtuelle, en utilisant ce modèle d’orbe.

2. Ajouter dynamiquement des orbes

Dans cette étape, nous allons ajouter des orbes en fonction d'une spécification JSON-esque d'un niveau. Cela nous permet de spécifier facilement et de générer de nouveaux niveaux. Nous utiliserons l'orbe de la dernière étape de la partie 1 comme modèle.

Pour commencer, importez jQuery, car cela facilitera les modifications du DOM, et donc la scène VR. Directement après l'importation de l'image A, ajoutez ce qui suit à L8:

 

Spécifiez un niveau à l'aide d'un tableau. Le tableau contiendra des littéraux d’objet qui codent les «dépendances» de chaque orbe. Dans la balise ajoutez la configuration de niveau suivante:

  

Pour l'instant, chaque orbe ne peut avoir qu'une seule dépendance à sa «droite» et une à sa «gauche». Immédiatement après avoir déclaré les orbes ci-dessus, ajoutez un gestionnaire qui s'exécutera au chargement de la page. Ce gestionnaire dupliquera le modèle orb et (2) supprimera le modèle orb, en utilisant la configuration de niveau fournie:

 $ (document) .ready (function () {

  fonction populateTemplate (orb, template, i, total) {}
  
  fonction remove (sélecteur) {}

  for (var i = 0; i 

Ensuite, renseignez la fonction remove qui supprime simplement un élément de la scène VR, avec un sélecteur. Heureusement, A-Frame observe les modifications apportées au DOM, et Ainsi, le retrait de l'élément du DOM suffit à le supprimer de la scène VR. Remplissez la fonction remove comme suit.

fonction remove (sélecteur) {
    var el = document.querySelector (sélecteur);
    el.parentNode.removeChild (el);
  }

Renseignez la fonction clickOrb qui déclenche simplement l'action de clic sur un orbe.

 fonction clickOrb (i) {
  document.querySelector ("# conteneur-orbe" + i) .click ();
}

Ensuite, commencez à écrire la fonction populateTemplate . Dans cette fonction, commencez par obtenir le .container . Ce conteneur pour l'orbe contient en plus les indicateurs visuels que nous avons ajoutés à l'étape précédente. De plus, nous devrons modifier le comportement de l’Orb onclick en fonction de ses dépendances. Si une dépendance à gauche existe, modifiez à la fois l'indicateur visuel et le comportement onclick pour refléter ce comportement; il en va de même pour une dépendance de droite:

 function populateTemplate (orb, template, i, total) {
    var conteneur = template.find ('. conteneur');
    var onclick = 'document.querySelector ("# lumière-orbe' + i + '") .emit ("commutateur");';

    si (orb.left || orb.right) {
      si (orb.left) {
        onclick + = 'clickOrb (' + orb.left + ');';
        container.find ('. dep-left'). attr ('value', orb.left);
      }
      si (orb.right) {
        onclick + = 'clickOrb (' + orb.right + ');';
        container.find ('. dep-right'). attr ('valeur', orb.right);
      }
    } autre {
      container.find ('. dep-left'). remove ();
      container.find ('. dep-right'). remove ();
    }
}

Toujours dans la fonction populateTemplate définissez correctement l’ID orbe dans tous les éléments de l’orb et de son conteneur.

 conteneur.find ('. orb-id'). attr ('valeur', i);
    container.attr ('id', 'container-orb' + i);
    template.find ('. orb'). attr ('id', 'orb' + i);
    template.find ('. light-orb'). attr ('id', 'light-orb' + i);
    template.find ('. clickable'). attr ('data-id', i);

Toujours dans la fonction populateTemplate définissez le comportement onclick définissez la graine aléatoire de sorte que chaque orbe soit visuellement différente, puis définissez la position de rotation de l'orbe en fonction de son ID.

 container.attr ('onclick', onclick);
    container.find ('lp-sphere'). attr ('seed', i);
    template.attr ('rotation', '0' + (360 / total * i) + '0');

À la fin de la fonction, renvoyez le modèle avec toutes les configurations ci-dessus.

 retourner le modèle;

Dans le gestionnaire de chargement de documents et après avoir retiré le modèle avec remove ('# template') activez les orbes configurées pour être activées initialement.

 $ (document) .ready (1965). une fonction() {
  ...
  setTimeout (function () {
    for (var i = 0; i 

Ceci met fin aux modifications de Javascript. Nous modifierons ensuite les paramètres par défaut du modèle pour les remplacer par un orbe 'désactivé'. Modifiez la position et l'échelle pour # container-orb0 à ce qui suit:

 position = "8 0.5 0" scale = "0.5 0.5 0.5"

Modifiez ensuite l'intensité de # light-orb0 en 0.

 intensité = "0"

Vérifiez que votre code source correspond à notre code source de l'étape 2. .

Votre scène de réalité virtuelle devrait désormais comporter 5 orbes, remplies de manière dynamique. L'un des orbes devrait en outre comporter des indicateurs visuels des dépendances, comme ci-dessous:

 Tous les orbes sont remplis de manière dynamique, à l'aide du modèle orb
Tous les orbes sont remplis de manière dynamique, à l'aide du modèle orb ( Grand aperçu . )

Ceci conclut la première section sur l’ajout dynamique d’orbes. Dans la section suivante, nous allons passer trois étapes à ajouter des mécanismes de jeu. Spécifiquement, le joueur ne pourra basculer que des orbes spécifiques en fonction de son ID.

3. Ajouter un état de terminal

À cette étape, nous allons ajouter un état de terminal. Si tous les orbes sont activés avec succès, le joueur voit une page “victoire”. Pour ce faire, vous devrez suivre l'état de tous les orbes. Chaque fois qu'un orbe est activé ou désactivé, nous devons mettre à jour notre état interne. Dites qu'une fonction d'assistance toggleOrb met à jour l'état pour nous. Appelez la fonction toggleOrb chaque fois qu'un orbe change d'état: (1) ajoutez un écouteur de clic au gestionnaire de charge et (2) ajoutez un toggleOrb (i); à l'invocation à clickOrb . Enfin, (3) définissez un toggleOrb vide.

 $ (document) .ready (function () {
  ...
  $ ('. orb'). sur ('cliquez', fonction () {
    var id = $ (this) .attr ('data-id')
    toggleOrb (id);
  });
});

fonction toggleOrb (i) {}

fonction clickOrb (i) {
  ...
  toggleOrb (i);
}

Par souci de simplicité, nous utiliserons notre configuration de niveau pour indiquer l'état du jeu. Utilisez toggleOrb pour basculer l'état sur pour le ième orbe. toggleOrb peut en outre déclencher un état terminal si tous les orbes sont activés.

 function toggleOrb (i) {
  orbs [i] .on =! orbs [i] .on;
  if (orbs.every (orb => orb.on)) console.log ('Victory!');
}

Vérifiez à nouveau que votre code correspond à notre code source pour l'étape 3. .

Ceci met fin au mode «solo» du jeu. À ce stade, vous avez un jeu de réalité virtuelle entièrement fonctionnel. Cependant, vous devez maintenant écrire le composant multijoueur et encourager la collaboration via les mécanismes de jeu.

4. Créer un objet de joueur

Dans cette étape, nous allons créer une abstraction pour un joueur avec un ID de joueur. Cet identifiant de joueur sera attribué ultérieurement par le serveur.

Pour l'instant, il s'agira simplement d'une variable globale. Juste après avoir défini les orbes définissez un ID de joueur:

 var orbs = ...

var current_player_id = 1;

Vérifiez à nouveau que votre code correspond à notre code source pour l'étape 4 . À l'étape suivante, cet identifiant de joueur sera ensuite utilisé pour déterminer les orbes que le joueur peut contrôler.

5. Basculement conditionnel des orbes

Dans cette étape, nous allons modifier le comportement du basculement des orbes. Plus précisément, le joueur 1 peut contrôler les globes impairs et le joueur 2, les orbes pairs. Tout d’abord, implémentez cette logique aux deux endroits où les orbes changent d’état:

 $ ('. Orb'). On ('click', function () {
        var id = ...
        if (! allowedToToggle (id)) renvoie false;
        ...
    }
...

fonction clickOrb (i) {
    if (! allowedToToggle (id)) return;
    ...
}

Deuxièmement, définissez la fonction allowedToToggle juste après clickOrb . Si le joueur actuel est le joueur 1, les identifiants avec numéros impairs renverront une valeur véridique et le joueur 1 sera ainsi autorisé à contrôler les orbes portant des numéros impairs. L'inverse est vrai pour le joueur 2. Tous les autres joueurs ne sont pas autorisés à contrôler les orbes.

 function allowedToToggle (id) {
  if (current_player_id == 1) {
    id de retour% 2;
  } else if (current_player_id == 2) {
    return! (id% 2);
  }
  retourne faux;
}

Vérifiez à nouveau que votre code correspond à notre code source pour l'étape 5 . Par défaut, le joueur est le joueur 1. Cela signifie qu'en tant que joueur 1, vous ne pouvez contrôler que des orbes impairs dans l'aperçu. Ceci conclut la section sur les mécanismes du jeu.

Dans la section suivante, nous faciliterons la communication entre les deux joueurs via un serveur.

6. Configurer le serveur avec WebSocket

Au cours de cette étape, vous allez configurer un serveur simple pour (1) suivre les identifiants des joueurs et (2) les messages de relais. Ces messages incluront l'état du jeu, afin que les joueurs puissent être certains que chacun voit ce que voit l'autre.

Nous nous référerons à votre précédent index.html en tant que code source côté client. Dans cette étape, nous ferons référence au code en tant que code source côté serveur. Accédez à glitch.com, cliquez sur "nouveau projet" en haut à droite, puis dans le menu déroulant, cliquez sur "hello-express".

Dans le panneau de gauche, sélectionnez "package.json" et ajoutez socket-io à dépendances . Votre dictionnaire dépendances devrait maintenant correspondre à ce qui suit:

 "dépendances": {
    "express": "^ 4.16.4",
    "socketio": "^ 1.0.0"
  },

Dans le panneau de gauche, sélectionnez «index.js» et remplacez le contenu de ce fichier par le connecteur minimal suivant: Hello World:

 const express = require ("express");
const app = express ();

var http = require ('http'). Server (app);
var io = require ('socket.io') (http);

/ **
 * Exécuter l'application sur le port 3000
 * /

var port = process.env.PORT || 3000;

http.listen (port, fonction () {
  console.log ('écoute sur *:', port);
});

Ce qui précède configure socket.io sur le port 3000 pour une application express de base. Ensuite, définissez deux variables globales, une pour gérer la liste des joueurs actifs et une autre pour conserver le plus petit ID de joueur non attribué.

 / **
 * Maintenir les identifiants des joueurs
 * /

var playerIds = [];
var smallestPlayerId = 1;

Ensuite, définissez la fonction getPlayerId qui génère un nouvel ID de joueur et marque le nouvel ID de joueur comme «pris» en l'ajoutant au tableau playerIds . En particulier, la fonction marque simplement smallestPlayerId puis met à jour smallestPlayerId en recherchant le prochain plus petit entier non pris.

 function getPlayerId () {
  var playerId = smallestPlayerId;
  playerIds.push (playerId);

  tandis que (playerIds.includes (smallestPlayerId)) {
    smallestPlayerId ++;
  }
  return playerId;
}

Définissez la fonction removePlayer qui met à jour smallestPlayerId en conséquence et libère le playerId fourni afin qu'un autre joueur puisse utiliser cette fonction ID.

 removePlayer ( playerId) {
  if (playerId 

Enfin, définissez une paire de gestionnaires d'événements de socket qui enregistrent de nouveaux joueurs et annulent l'enregistrement de joueurs déconnectés, en utilisant la paire de méthodes ci-dessus.

 / **
 * Gérer les interactions de socket
 * /

io.on ('connexion', fonction (socket) {
  socket.on ('newPlayer', function () {
    socket.playerId = getPlayerId ();
    console.log ("nouveau joueur:", socket.playerId);
    socket.emit ('playerId', socket.playerId);
  });

  socket.on ('disconnect', function () {
    if (socket.playerId === undefined) retourne;
    console.log ("lecteur déconnecté:", socket.playerId);
    removePlayer (socket.playerId);
  });
});

Vérifiez à nouveau que votre code correspond à notre code source pour l'étape 6 . Ceci conclut l’inscription et la désinscription de base du joueur. Chaque client peut désormais utiliser l'ID de joueur généré par le serveur.

À l'étape suivante, nous allons modifier le client pour qu'il reçoive et utilise l'ID de joueur émis par le serveur.

7. Apply Player Player

Au cours de ces deux prochaines étapes, nous terminerons une version rudimentaire de l’expérience multijoueur. Pour commencer, intégrez l’attribution d’ID de joueur côté client. En particulier, chaque client demandera au serveur un identifiant de joueur. Revenez au côté client index.html nous travaillions dans les étapes 4 et antérieures.

Import socket.io dans la tête en L7:

   

Après le gestionnaire de chargement de document, instanciez le socket et émettez un événement newPlayer . En réponse, le serveur générera un nouvel ID de lecteur à l'aide de l'événement playerId . Ci-dessous, utilisez l'URL de l'aperçu de votre projet Glitch au lieu de . Lightful.glitch.me . Vous pouvez utiliser l'URL de démonstration ci-dessous, mais les modifications de code que vous apportez ne seront bien sûr pas répercutées.

 $ (document) .ready (function () {
    ...
});

socket = io ("https://lightful.glitch.me");
  socket.emit ('newPlayer');
  socket.on ('playerId', function (player_id) {
    current_player_id = player_id;
    console.log ("* Vous êtes maintenant joueur", current_player_id);
  });

Vérifiez que votre code correspond à notre code source pour l'étape 7 . Maintenant, vous pouvez charger votre jeu sur deux navigateurs ou onglets différents pour jouer sur les deux faces d’une partie multijoueur. Le joueur 1 sera capable de contrôler les orbes impairs et le joueur 2 de contrôler les orbes pairs.

Cependant, notez que l'activation d'orbes pour le joueur 1 n'affectera pas l'état de l'orbe pour le joueur 2. Ensuite, nous devons synchroniser les états de jeu.

8. Synchroniser l'état du jeu

Dans cette étape, nous allons synchroniser les états du jeu afin que les joueurs 1 et 2 voient les mêmes états orbe. Si orbe 1 est activé pour le joueur 1, il devrait l'être également pour le joueur 2. Du côté client, nous annoncerons et écouterons les basculements orb. Pour annoncer, nous allons simplement passer l'ID de l'orbe qui est basculé.

Avant les deux invocations de toggleOrb ajoutez l'élément socket.emit .

 $ (document ) .ready (function () {
    ...
    $ ('. orb'). sur ('cliquez', fonction () {
        ...
        socket.emit ('toggleOrb', id);
        toggleOrb (id);
    });
});
...
fonction clickOrb (i) {
    ...
    socket.emit ('toggleOrb', i);
    toggleOrb (i);
}

Ensuite, écoutez les basculements d'orbe et basculez l'orbe correspondant. Directement sous l'écouteur d'événement de socket playerId ajoutez un autre écouteur pour l'événement toggleOrb .

 socket.on ('toggleOrb', fonction (i) {
  document.querySelector ("# conteneur-orbe" + i) .click ();
  toggleOrb (i);
});

Ceci conclut les modifications apportées au code côté client. Vérifiez à nouveau que votre code correspond à notre code source à l'étape 8. .

Le serveur doit maintenant recevoir et diffuser l'ID orbe basculé. Dans le serveur index.js ajoutez le programme d'écoute suivant. Cet auditeur doit être placé directement sous l'écouteur disconnect .

 socket.on ('toggleOrb', fonction (i) {
    socket.broadcast.emit ('toggleOrb', i);
  });

Vérifiez à nouveau que votre code correspond à notre code source pour l'étape 8 . Maintenant, le joueur 1 chargé dans une fenêtre et le joueur 2 chargé dans une seconde fenêtre verront le même état de jeu. Avec cela, vous avez terminé un jeu de réalité virtuelle multijoueur. De plus, les deux joueurs doivent collaborer pour atteindre l'objectif. Le produit final correspondra à ce qui suit:

 Le jeu multijoueur terminé, synchronisé sur plusieurs clients
Le jeu multijoueur terminé, synchronisé sur plusieurs clients. ( Grand aperçu )

Conclusion

Ceci conclut notre tutoriel sur la création d'un jeu de réalité virtuelle multijoueur. Au cours de ce processus, vous avez abordé de nombreux sujets, dont la modélisation 3D en A-Frame VR et les expériences multijoueurs en temps réel avec WebSockets.

En vous basant sur les concepts que nous avons abordés, comment assurer une expérience plus fluide pour les deux joueurs? Cela peut inclure la vérification de la synchronisation de l’état du jeu et l’alerte de l’utilisateur, dans le cas contraire. Vous pouvez également créer de simples indicateurs visuels sur l'état du terminal et sur l'état de la connexion du lecteur.

Compte tenu du cadre que nous avons établi et des concepts que nous avons introduits, vous disposez désormais des outils nécessaires pour répondre à ces questions et en construire beaucoup plus. [19659005] Vous pouvez trouver le code source fini ici .

 Editorial éclatant (dm, il)




Source link