Serveur minecraft

Création d'un éditeur JavaScript Minecraft 3D – SitePoint – Un bon serveur Minecraft

Le 9 février 2020 - 23 minutes de lecture

Cet article a été révisé par des pairs par Paul O’Brien. Merci à tous les pairs examinateurs de SitePoint pour avoir rendu le contenu SitePoint le meilleur possible!

Écran de démarrage Minecraft

J'ai toujours voulu créer un jeu 3D. Je n'ai tout simplement jamais eu le temps et l'énergie pour apprendre les subtilités de la programmation 3D. J'ai découvert que je n'avais pas besoin de …

En bricolant un jour, j'ai pensé que je pourrais peut-être simuler un environnement 3D en utilisant des transformations CSS. Je suis tombé sur un vieil article sur la création de mondes 3D avec HTML et CSS.

Je voulais simuler un monde Minecraft (ou une infime partie de celui-ci au moins). Minecraft est un jeu de bac à sable, dans lequel vous pouvez casser et placer des blocs. Je voulais le même type de fonctionnalité, mais avec HTML, JavaScript et CSS.

Venez décrire ce que j'ai appris et comment cela peut vous aider à être plus créatif avec vos transformations CSS!

Remarque: La plupart du code de ce didacticiel se trouve sur Github. Je l'ai testé dans la dernière version de Chrome. Je ne peux pas vous promettre que cela aura exactement la même apparence dans d'autres navigateurs, mais les concepts de base sont universels.

Ce n'est que la moitié de l'aventure. Si vous souhaitez savoir comment conserver les conceptions sur un serveur réel, consultez la publication sœur, PHP Minecraft Mod. Nous y explorons des moyens d'interagir avec un serveur Minecraft, de le manipuler en temps réel et de répondre aux entrées des utilisateurs.

Les choses que nous faisons déjà

J'ai écrit ma juste part de CSS et j'en suis venu à bien le comprendre, dans le but de créer des sites Web. Mais cette compréhension repose sur l'hypothèse que je vais travailler dans un espace 2D.

Prenons un exemple:

.outils 
  position: absolue;
  gauche: 35px;
  haut: 25px;
  largeur: 200 px;
  hauteur: 400px;
  indice z: 3;


.Toile 
  position: absolue;
  gauche: 0;
  en haut: 0;
  largeur: 100%;
  hauteur: 100%;
  indice z: 2;

Ici, nous avons un élément canvas, commençant dans le coin supérieur gauche de la page et s'étendant jusqu'en bas à droite. En plus de cela, nous ajoutons un élément outils. Il commence 25px de gauche et 35px en haut de la page et les mesures 200px large par 400px haute.

Selon la commande div.tools et div.canvas sont ajoutés au balisage, il est tout à fait possible que div.canvas pourrait se chevaucher div.tools. C'est à l'exception de la z-index styles appliqués à chacun.

Vous êtes probablement habitué à considérer les éléments ainsi conçus comme des surfaces 2D susceptibles de se chevaucher. Mais ce chevauchement est essentiellement une troisième dimension. la gauche, Haut, et z-index peut aussi bien être renommé X, y, et z. Tant que nous supposons que chaque élément a une profondeur fixe de 1px, et z-index a un implicite px unité, nous pensons déjà en termes 3D.

Certains d'entre nous ont tendance à avoir du mal avec les concepts de rotation et de traduction dans cette troisième dimension…

La théorie des transformations

Les traductions CSS dupliquent cette fonctionnalité familière, dans une API qui dépasse les limites Haut, la gauche, et z-index placer sur nous. Il est possible de remplacer certains de nos styles précédents par des traductions:

.outils 
  position: absolue;
  fond: vert;
  / *
    gauche: 35px;
    haut: 25px;
  * /
  transform-origin: 0 0;
  transformer: traduire (35px, 25px);
  largeur: 200 px;
  hauteur: 400px;
  indice z: 3;

Au lieu de définir la gauche et Haut compensations (avec une origine supposée de 0px de gauche et 0px par le haut), nous pouvons déclarer une origine explicite. Nous pouvons effectuer toutes sortes de transformations sur cet élément, pour lesquelles utiliser 0 0 comme centre. traduire (35px, 25px) déplace l'élément 35px à droite et 25px vers le bas. Nous pouvons utiliser des valeurs négatives pour déplacer l'élément vers la gauche et / ou vers le haut.

Avec la possibilité de définir une origine pour nos transformations, nous pouvons également commencer à faire d'autres choses intéressantes. Par exemple, nous pouvons faire pivoter et mettre à l'échelle des éléments:

transform-origin: centre;
transformer: l'échelle (0,5) tourne (45 degrés);

Chaque élément commence par un défaut transform-origin de 50% 50% 0, mais une valeur de centre ensembles X, y, et z à l'équivalent de 50%. Nous pouvons adapter notre élément à une valeur comprise entre 0 et 1et faites-le pivoter (dans le sens horaire) de degrés ou de radians. Et nous pouvons convertir entre les deux avec:

  • 45deg = (45 * Math.PI) / 1800.79rad
  • 0.79rad = (0,79 * 180) / Math.PI45deg

Pour faire pivoter un élément dans le sens inverse des aiguilles d'une montre, il suffit d'utiliser un négatif deg ou rad valeur.

Ce qui est encore plus intéressant, à propos de ces transformations, c'est que nous pouvons en utiliser des versions 3D.

Les navigateurs Evergreen prennent assez bien en charge ces styles, bien qu'ils puissent nécessiter des préfixes de fournisseurs. CodePen a une option "autoprefix" soignée, mais vous pouvez ajouter des bibliothèques comme PostCSS à votre code local pour obtenir la même chose.

Le premier bloc

Commençons à créer notre monde 3D. Nous allons commencer par créer un espace dans lequel placer nos blocs. Créez un nouveau fichier, appelé index.html:



  
    
  
  
    

Ici, nous étirons le corps sur toute la largeur et la hauteur, réinitialisant le rembourrage 0px. Ensuite, nous créons un petit div.scene, que nous utiliserons pour contenir différents blocs. Nous utilisons 50% la gauche et Haut, ainsi qu'une gauche et un haut négatifs marge (égal à la moitié du largeur et la taille) pour le centrer horizontalement et verticalement. Ensuite, nous l'inclinons légèrement (en utilisant la rotation 3D) afin d'avoir une vue en perspective de l'emplacement des blocs.

Remarquez comment nous définissons style de transformation: préserver-3d. C'est ainsi que les éléments enfants peuvent également être manipulés dans un espace 3D.

Le résultat devrait ressembler à ceci:

Voir la scène vide du stylo par SitePoint (@SitePoint) sur CodePen.

Maintenant, commençons à ajouter une forme de bloc à la scène. Nous devons créer un nouveau fichier JavaScript, appelé block.js:

"utiliser strictement"

bloc de classe 
  constructeur (x, y, z) 
    this.x = x;
    this.y = y;
    this.z = z;

    this.build ();
  

  construire() 
    // TODO: construire le bloc
  

  createFace (type, x, y, z, rx, ry, rz) 
    // TODO: retourne une face de bloc
  

  createTexture (type) 
    // TODO: obtenez la texture
  

Chaque bloc doit avoir une forme 3D à 6 faces. Nous pouvons diviser les différentes parties de la construction en méthodes pour (1) construire le bloc entier, (2) construire chaque surface et (3) obtenir la texture de chaque surface.

Chacun de ces comportements (ou méthodes) est contenu dans une classe ES6. C’est une bonne façon de regrouper les structures de données et les méthodes qui les opèrent ensemble. Vous connaissez peut-être la forme traditionnelle:

bloc fonction (x, y, z) 
  this.x = x;
  this.y = y;
  this.z = z;

  this.build ();


var proto = Block.prototype;

proto.build = function () 
  // TODO: construire le bloc
;

proto.createFace = fonction (type, x, y, z, rx, ry, rz) 
  // TODO: retourne une face de bloc


proto.createTexture = fonction (type) 
  // TODO: obtenez la texture

Cela peut sembler un peu différent, mais c'est à peu près la même chose. En plus d'une syntaxe plus courte, les classes ES6 fournissent également des raccourcis pour étendre les prototypes et appeler les méthodes remplacées. Mais je m'égare…

Travaillons de bas en haut:

createFace (type, x, y, z, rx, ry, rz) 
  retourner $ (`
')     .css (       transformer: `         translateX ($ x px)         translateY ($ y px)         translateZ ($ z px)         rotationX ($ rx deg)         rotationY ($ ry deg)         rotationZ ($ rz deg)       ",       fond: this.createTexture (type)     ); createTexture (type)   retour `rgba (100, 100, 255, 0,2)`;

Chaque surface (ou face) est constituée d'un div tourné et translaté. Nous ne pouvons pas rendre les éléments plus épais que 1px, mais nous pouvons simuler la profondeur en couvrant tous les trous et en utilisant plusieurs éléments parallèles les uns aux autres. On peut donner au bloc l'illusion de profondeur, même s'il est creux.

À cette fin, le createFace prend un ensemble de coordonnées: X, y, et z pour la position du visage. Nous fournissons également des rotations pour chaque axe, afin que nous puissions appeler createFace avec n'importe quelle configuration et il traduira et fera pivoter le visage comme nous le voulons.

Construisons la forme de base:

construire() 
  taille const = 64;
  const x = this.x * taille;
  const y = this.y * taille;
  const z = this.z * taille;

  const block = this.block = $ (`
')     .css (       transformer: `         translateX ($ x px)         translateY ($ y px)         translateZ ($ z px)       "     );   $ (`
')     .appendTo (bloquer)     .css (       transformer: `         rotationX (90deg)         rotationY (0deg)         rotationZ (0deg)       "     );   $ (`
')     .appendTo (bloquer)     .css (       transformer: `         rotationX (0deg)         rotationY (90deg)         rotationZ (0deg)       "     );   $ (`
')     .appendTo (block);

Nous avons l'habitude de penser en termes de positions d'un pixel, mais un jeu comme Minecraft fonctionne à plus grande échelle. Chaque bloc est plus grand et le système de coordonnées traite de la position du bloc, pas des pixels individuels qui le composent. Je veux transmettre le même genre d'idée ici…

Lorsque quelqu'un crée un nouveau bloc, à 1 × 2 × 3, Je veux que cela signifie 0px × 64px × 128px. Nous multiplions donc chaque coordonnée par la taille par défaut (dans ce cas 64px, car c'est la taille des textures du pack de textures que nous utiliserons).

Ensuite, nous créons un conteneur div (que nous appelons div.block). À l'intérieur, nous plaçons encore 3 divisions. Ceux-ci nous montreront l'axe de notre bloc – ils sont comme des guides dans un programme de rendu 3D. Nous devons également ajouter de nouveaux CSS pour notre bloc:

.bloquer 
  position: absolue;
  gauche: 0;
  en haut: 0;
  largeur: 64px;
  hauteur: 64px;
  transformer-style: préserver-3d;
  origine de transformation: 50% 50% 50%;


axe .x,
axe-y,
.Axe z 
  position: absolue;
  gauche: 0;
  en haut: 0;
  largeur: 66px;
  hauteur: 66px;
  origine de transformation: 50% 50% 50%;


Axe .x 
  bordure: solide 2px rgba (255, 0, 0, 0,3);


Axe .y 
  bordure: solide 2px rgba (0, 255, 0, 0,3);


.Axe z 
  bordure: solide 2px rgba (0, 0, 255, 0,3);

Ce style est similaire à ce que nous avons vu auparavant. Nous devons nous rappeler de définir style de transformation: préserver-3d sur le .bloquer, afin que les axes soient rendus dans leur propre espace 3D. Nous leur donnons une couleur différente et les rendons légèrement plus grands que le bloc dans lequel ils sont contenus. C'est pour qu'ils soient visibles même lorsque le bloc a des côtés.

Créons un nouveau bloc et ajoutons-le au div.scene:

let first = new Block (1, 1, 1);

$ (". scene"). append (first.block);

Le résultat devrait ressembler à ceci:

Voir le Pen Basic 3D Block de SitePoint (@SitePoint) sur CodePen.

Maintenant, ajoutons ces visages:

cette
  .createFace ("top", 0, 0, size / 2, 0, 0, 0)
  .appendTo (block);

cette
  .createFace ("side-1", 0, taille / 2, 0, 270, 0, 0)
  .appendTo (block);

cette
  .createFace ("side-2", taille / 2, 0, 0, 0, 90, 0)
  .appendTo (block);

cette
  .createFace ("côté-3", 0, taille / -2, 0, -270, 0, 0)
  .appendTo (block);

cette
  .createFace ("side-4", taille / -2, 0, 0, 0, -90, 0)
  .appendTo (block);

cette
  .createFace ("bas", 0, 0, taille / -2, 0, 180, 0)
  .appendTo (block);

J'ai trouvé ce code un peu d'essai et d'erreur (en raison de mon expérience limitée avec la perspective 3D). Chaque élément commence exactement dans la même position que le div.z-axe élément. Autrement dit, dans le centre vertical de la div.block et face au sommet.

Donc, pour l'élément "top", j'ai dû le traduire "vers le haut" de la moitié de la taille du bloc, mais je n'ai pas eu à le faire pivoter de quelque façon que ce soit. Pour l'élément «bas», j'ai dû le faire pivoter de 180 degrés (le long de l'axe x ou y), et le déplacer de la moitié de la taille du bloc.

En utilisant une pensée similaire, j'ai tourné et traduit chacun des côtés restants. J'ai également dû leur ajouter du CSS correspondant:

.side 
  position: absolue;
  gauche: 0;
  en haut: 0;
  largeur: 64px;
  hauteur: 64px;
  visibilité arrière: cachée;
  contour: 1px rgba solide (0, 0, 0, 0,3);

Ajouter Visibilité arrière: caché empêche le rendu du côté «bas» des éléments. Habituellement, ils semblaient juste les mêmes (seulement en miroir), peu importe la façon dont ils étaient tournés. Avec les faces arrière cachées, seul le côté «supérieur» est rendu. Soyez prudent lors de l'activation: vos surfaces doivent être tournées dans le bon sens ou les côtés du bloc disparaîtront. C’est la raison des rotations 90/270 / -90 / -270 que j’ai données aux côtés.

Voir le Pen 3D Block Sides by SitePoint (@SitePoint) sur CodePen.

Rendons ce bloc un peu plus réaliste. Nous devons créer un nouveau fichier, appelé block.dirt.jset remplacer la createTexture méthode:

"utiliser strictement"

const DIRT_TEXTURES = 
  "Haut": [
    "textures/dirt-top-1.png",
    "textures/dirt-top-2.png",
    "textures/dirt-top-3.png"
  ],
  "côté": [
    "textures/dirt-side-1.png",
    "textures/dirt-side-2.png",
    "textures/dirt-side-3.png",
    "textures/dirt-side-4.png",
    "textures/dirt-side-5.png"
  ]
;

classe Dirt étend le bloc 
  createTexture (type)  type === "bottom") 
      const texture = DIRT_TEXTURES.top.random ();

      return `url ($ texture)`;
    

    const texture = DIRT_TEXTURES.side.random ();

    return `url ($ texture)`;
  


Block.Dirt = Dirt;

Nous allons utiliser un pack de texture populaire, appelé Sphax PureBDCraft. Il est gratuit à télécharger et à utiliser (à condition que vous n'essayiez pas de le vendre), et il existe en différentes tailles. J'utilise le x64 version.

Nous commençons par définir une table de correspondance pour les textures des côtés et du haut du bloc. Le pack de textures ne spécifie pas les textures à utiliser pour le bas, nous allons donc simplement réutiliser les textures du haut.

Si le côté ayant besoin d'une texture est «haut» ou «bas», alors nous récupérons une texture aléatoire dans la liste «haut». La méthode aléatoire n'existe pas jusqu'à ce que nous la définissions:

Array.prototype.random = function () 
  retourner ceci[Math.floor(Math.random() * this.length)];
;

De même, si nous avons besoin d'une texture pour un côté, nous en récupérons une au hasard. Ces textures sont sans couture, donc la randomisation fonctionne en notre faveur.

Le résultat devrait ressembler à ceci:

Voir les textures de bloc 3D Pen par SitePoint (@SitePoint) sur CodePen.

Faire une scène

Comment rendons-nous cela interactif? Eh bien, un bon endroit pour commencer est avec une scène. Nous avons déjà placé des blocs dans la scène, il ne nous reste plus qu'à activer le placement dynamique!

Pour commencer, nous pouvons rendre une surface plane de blocs:

const $ scene = $ (". scene");

pour (var x = 0; x <6; x ++) 
  pour (var y = 0; y <6; y ++) 
    let next = new Block.Dirt (x, y, 0);
    next.block.appendTo ($ scene);
  

Génial, cela nous donne une surface plane pour commencer à ajouter des blocs. Maintenant, mettons en surbrillance les surfaces lorsque nous les survolons avec notre curseur:

.block: hover .side 
  contour: 1px rgba solide (0, 255, 0, 0,5);

Il se passe cependant quelque chose d'étrange:

blocs avec miroitement

En effet, les surfaces se coupent de manière aléatoire. Il n'y a pas de bon moyen de résoudre ce problème, mais nous pouvons l'empêcher de se produire en redimensionnant légèrement les blocs:

const block = this.block = $ (`
')   .css (     transformer: `       translateX ($ x px)       translateY ($ y px)       translateZ ($ z px)       échelle (0.99)     "   );

blocs sans miroitement

Bien que cela améliore l'apparence, cela affectera les performances plus il y aura de blocs dans la scène. Marchez légèrement lors de la mise à l'échelle de nombreux éléments à la fois…

Étiquetons chaque surface avec le bloc et le type qui lui appartiennent:

createFace (type, x, y, z, rx, ry, rz) 
  retourner $ (`
')     .css (       transformer: `         translateX ($ x px)         translateY ($ y px)         translateZ ($ z px)         rotationX ($ rx deg)         rotationY ($ ry deg)         rotationZ ($ rz deg)       ",       fond: this.createTexture (type)     )     .data ("bloquer", ceci)     .data ("type", type);

Ensuite, en cliquant sur une surface, nous pouvons dériver un nouvel ensemble de coordonnées et créer un nouveau bloc:

function createCoordinatesFrom (côté, x, y, z) 
  if (side == "top") 
    z + = 1;
  

  si (côté == "côté-1") 
    y + = 1;
  

  si (côté == "côté-2") 
    x + = 1;
  

  si (côté == "côté-3") 
    y - = 1;
  

  si (côté == "côté-4") 
    x - = 1;
  

  si (côté == "bas") 
    z - = 1;
  

  revenir [x, y, z];


const $ body = $ ("corps");

$ body.on ("clic", ".side", fonction (e) 
  const $ this = $ (this);
  const previous = $ this.data ("block");

  const coordonnées = createCoordinatesFrom (
    $ this.data ("type"),
    previous.x,
    previous.y,
    previous.z
  );

  const suivant = nouveau Block.Dirt (... coordonnées);

  next.block.appendTo ($ scene);
);

createCoordinatesFrom a une tâche simple mais importante. Compte tenu du type de côté et des coordonnées du bloc auquel il appartient, createCoordinatesFrom devrait renvoyer un nouvel ensemble de coordonnées. C'est là que le nouveau bloc sera placé.

Ensuite, nous avons joint un écouteur d'événements. Il sera déclenché pour chaque côté div. qui obtient cliqué. Lorsque cela se produit, nous obtenons le bloc auquel appartient le côté et dérivons un nouvel ensemble de coordonnées pour le bloc suivant. Une fois que nous les avons, nous créons le bloc et l'ajoutons à la scène.

Le résultat est merveilleusement interactif:

Voir la scène pré-remplie du stylo par SitePoint (@SitePoint) sur CodePen.

Voir les fantômes

Il serait utile de voir un aperçu du bloc que nous allons placer avant de le placer. On parle parfois de «montrer un fantôme» de la chose que nous allons faire.

Le code pour l'activer est assez similaire à celui que nous avons déjà vu:

laissez ghost = null;

fonction removeGhost () 
  si (fantôme) 
    ghost.block.remove ();
    fantôme = null;
  


fonction createGhostAt (x, y, z) 
  const next = new Block.Dirt (x, y, z);

  next.block
    .addClass ("fantôme")
    .appendTo ($ scene);

  fantôme = suivant;


$ body.on ("mouseenter", ".side", function (e) 
  removeGhost ();

  const $ this = jQuery (this);
  const previous = $ this.data ("block");

  const coordonnées = createCoordinatesFrom (
    $ this.data ("type"),
    previous.x,
    previous.y,
    previous.z
  );

  createGhostAt (... coordonnées);
);

$ body.on ("mouseleave", ".side", fonction (e) 
  removeGhost ();
);

La principale différence est que nous conservons une seule instance du bloc fantôme. Au fur et à mesure que chaque nouveau est créé, l'ancien est supprimé. Cela pourrait bénéficier de quelques styles supplémentaires:

.fantôme 
  événements de pointeur: aucun;


.ghost .side 
  opacité: 0,6;
  événements de pointeur: aucun;
  -filtre Web: luminosité (1,5);

Laissés actifs, les événements de pointeur associés aux éléments du fantôme contrecarreraient la mouseenter et mouseleave événements du côté en dessous. Comme nous n'avons pas besoin d'interagir avec les éléments fantômes, nous pouvons désactiver ces événements de pointeur.

Ce résultat est plutôt soigné:

Voir le Pen 3D Block Ghosts de SitePoint (@SitePoint) sur CodePen.

Changer de perspective

Plus nous ajoutons d'interactivité, plus il est difficile de voir ce qui se passe. Cela semble être le bon moment pour faire quelque chose à ce sujet. Ce serait génial si nous pouvions zoomer et faire pivoter la fenêtre pour pouvoir voir ce qui se passe un peu mieux…

Commençons par le zoom. De nombreuses interfaces (et jeux) permettent un zoom dans la fenêtre en faisant défiler la molette de la souris. Différents navigateurs gèrent les événements de la molette de la souris de différentes manières, il est donc judicieux d'utiliser une bibliothèque d'abstraction.

Une fois installé, nous pouvons nous connecter aux événements:

laissez sceneTransformScale = 1;

$ body.on ("molette", fonction (événement) 
  if (event.originalEvent.deltaY> 0) 
    sceneTransformScale - = 0,05;
   autre 
    sceneTransformScale + = 0,05;
  

  $ scene.css (
    "transformer": `
      scaleX ($ sceneTransformScale)
      scaleY ($ sceneTransformScale)
      scaleZ ($ sceneTransformScale)
    "
  );
);

zoomer la scène avec la molette de la souris

Maintenant, nous pouvons contrôler l'échelle de la scène entière, simplement en faisant défiler la molette de la souris. Malheureusement, au moment où nous le faisons, les rotations sont annulées. Nous devons prendre en compte la rotation, car nous permettons de faire glisser la fenêtre avec la souris pour l'ajuster:

laissez sceneTransformX = 60;
laissez sceneTransformY = 0;
laissez sceneTransformZ = 60;
laissez sceneTransformScale = 1;

const changeViewport = function () 
  $ scene.css (
    "transformer": `
      rotationX ($ sceneTransformX deg)
      rotationY ($ sceneTransformY deg)
      rotationZ ($ sceneTransformZ deg)
      scaleX ($ sceneTransformScale)
      scaleY ($ sceneTransformScale)
      scaleZ ($ sceneTransformScale)
    "
  );
;

Cette fonction ne tient pas seulement compte du facteur d'échelle de la scène, mais également des facteurs de rotation x, y et z. Nous devons également modifier notre écouteur d'événement de zoom:

$ body.on ("molette", fonction (événement) 
  if (event.originalEvent.deltaY> 0) 
    sceneTransformScale - = 0,05;
   autre 
    sceneTransformScale + = 0,05;
  

  changeViewport ();
);

Maintenant, nous pouvons commencer à faire pivoter la scène. Nous avons besoin:

  1. Un écouteur d'événements pour le début de l'action de glissement
  2. Un écouteur d'événements lorsque la souris se déplace (tout en faisant glisser)
  3. Un écouteur d'événements pour quand l'action de glisser s'arrête

Quelque chose comme ça devrait faire l'affaire:

Number.prototype.toInt = String.prototype.toInt = function () 
  return parseInt (this, 10);
;

laissez lastMouseX = null;
laissez lastMouseY = null;

$ body.on ("mousedown", fonction (e) 
  lastMouseX = e.clientX / 10;
  lastMouseY = e.clientY / 10;
);

$ body.on ("mousemove", fonction (e) 
  si (! lastMouseX) 
    revenir;
  

  laissez nextMouseX = e.clientX / 10;
  laissez nextMouseY = e.clientY / 10;

  if (nextMouseX! == lastMouseX) 
    deltaX = nextMouseX.toInt () - lastMouseX.toInt ();
    degrés = sceneTransformZ - deltaX;

    si (degrés> 360) 
        degrés - = 360;
    

    si (degrés < 0) 
        degrees += 360;
    

    sceneTransformZ = degrees;
    lastMouseX = nextMouseX;

    changeViewport();
  

  if (nextMouseY !== lastMouseY) 
    deltaY = nextMouseY.toInt() - lastMouseY.toInt();
    degrees = sceneTransformX - deltaY;

    if (degrees > 360) 
        degrés - = 360;
    

    si (degrés <0) 
        degrés + = 360;
    

    sceneTransformX = degrés;
    lastMouseY = nextMouseY;

    changeViewport ();
  
);

$ body.on ("mouseup", fonction (e) 
  lastMouseX = null;
  lastMouseY = null;
);

Sur souris vers le bas nous capturons la souris initiale X et y coordonnées. Lorsque la souris se déplace (si le bouton est toujours enfoncé), nous ajustons la sceneTransformZ et sceneTransformX d'un montant réduit. Il n'y a pas de mal à laisser passer les valeurs 360 degrés ou moins 0 degrés, mais ceux-ci seraient terribles si nous voulions les afficher à l'écran.

Calcul à l'intérieur d'un déplacer la souris l'écouteur d'événements peut être coûteux en calcul en raison de la quantité de déclencheurs qui peuvent être déclenchés. Il y a potentiellement des millions de pixels sur l'écran, et cet écouteur peut être déclenché lorsque la souris se déplace vers chacun d'eux. C’est pourquoi nous quittons tôt si le bouton de la souris n’est pas maintenu enfoncé.

Lorsque le bouton de la souris est relâché, nous désactivons lastMouseX et lastMouseY, de sorte que la déplacer la souris l'auditeur arrête de calculer les choses. On pourrait juste effacer lastMouseX, mais effacer les deux me semble plus propre.

Malheureusement, l'événement mousedown peut interférer avec l'événement click sur les côtés du bloc. Nous pouvons contourner cela en empêchant le bouillonnement des événements:

$ scene.on ("mousedown", fonction (e) 
  e.stopPropagation ();
);

Essayer…

Voir le zoom et la rotation du stylet par SitePoint (@SitePoint) sur CodePen.

Suppression de blocs

Complétons l'expérience en ajoutant la possibilité de supprimer des blocs. Nous devons faire quelques choses subtiles mais importantes:

  1. Changer la couleur de la bordure de survol du vert au rouge
  2. Désactivez les fantômes bloqués

Ce sera plus facile à faire avec CSS, tant que nous avons une classe de corps pour indiquer si nous sommes en mode additionnel (normal) ou en mode soustraction:

$ body.on ("keydown", fonction (e)  e.controlKey );

$ body.on ("keyup", fonction (e) 
  $ body.removeClass ("soustraction");
);

Lorsqu'une touche de modification est enfoncée (alt, contrôle, ou commander), ce code s'assurera corps a un soustraction classe. Cela facilite le ciblage de divers éléments à l'aide de cette classe:

.subtraction .block: hover .side 
  contour: 1px rgba solide (255, 0, 0, 0,5);


.soustraction .ghost 
  affichage: aucun;

passage en mode soustraction

Nous recherchons un certain nombre de touches de modification, car différents systèmes d'exploitation interceptent différents modificateurs. Par exemple, touche Alt et metaKey travailler sur macOS, alors que clé de contrôle fonctionne sur Ubuntu.

Si nous cliquons sur un bloc, lorsque nous sommes en mode soustraction, nous devons le supprimer:

$ body.on ("clic", ".side", fonction (e) 
  const $ this = $ (this);
  const previous = $ this.data ("block");

  if ($ body.hasClass ("soustraction")) 
    previous.block.remove ();
    précédent = null;
   autre 
    const coordonnées = createCoordinatesFrom (
      $ this.data ("type"),
      previous.x,
      previous.y,
      previous.z
    );

    const suivant = nouveau Block.Dirt (... coordonnées);
    next.block.appendTo ($ scene);
  
);

C'est pareil .côté écouteur d'événements de clic que nous avions auparavant, mais au lieu d'ajouter simplement de nouveaux blocs lorsqu'un côté est cliqué, nous vérifions d'abord si nous sommes en mode de soustraction. Si c'est le cas, le bloc sur lequel nous venons de cliquer est supprimé de la scène.

La démo finale

La démo finale est merveilleuse pour jouer avec:

Voir le stylo Suppression de blocs par SitePoint (@SitePoint) sur CodePen.

Il y a un long chemin à parcourir avant de prendre en charge autant de blocs et d'interactions que Minecraft, mais c'est un bon début. De plus, nous avons réussi à atteindre cet objectif sans avoir à étudier des techniques 3D avancées. C’est une utilisation non conventionnelle (et créative) des transformations CSS!

Si vous souhaitez en faire plus avec ce code, passez à l'autre moitié de cette aventure. Vous n'avez pas besoin d'être un expert PHP pour interagir avec les serveurs Minecraft. Et imaginez les choses incroyables que vous pouvez faire avec ces connaissances…

Et n'oubliez pas: ce n'est que la moitié de l'aventure. Si vous souhaitez savoir comment conserver les conceptions sur un serveur réel, consultez la publication sœur, PHP Minecraft Mod. Nous y explorons des moyens d'interagir avec un serveur Minecraft, de le manipuler en temps réel et de répondre aux entrées des utilisateurs.

Commentaires

Laisser un commentaire

Votre commentaire sera révisé par les administrateurs si besoin.