Création d'un jeu simple 3D Endless Runner à l'aide de Three.js

Ce que vous allez créer

La plate-forme Web a connu une croissance extraordinaire ces derniers temps avec l'aide de HTML5, WebGL et la puissance accrue de la génération actuelle d'appareils. Désormais, les appareils mobiles et les navigateurs sont capables de fournir un contenu hautement performant en 2D et en 3D. La familiarité de JavaScript (JS) en tant que langage de script a également été un facteur déterminant après la disparition de la plate-forme Web Flash.. 

La plupart des développeurs Web sont bien conscients de la complexité de l'écosystème JS avec les divers cadres et normes disponibles, ce qui peut parfois être pénible pour un nouveau développeur. Mais en matière de 3D, les choix sont simples, grâce à M. Doob. Three.js est actuellement la meilleure option pour créer du contenu WebGL 3D hautement performant. Babylon.js est une autre alternative puissante qui pourrait également être utilisée pour créer des jeux en 3D..

Dans ce didacticiel, vous apprendrez à créer un jeu 3D Web natif de style coureur sans fin et simple à l’aide du puissant framework Three.js. Vous utiliserez les touches fléchées pour contrôler une boule de neige sur une montagne afin d'esquiver les arbres sur votre chemin. Il n'y a pas d'art impliqué, et tous les visuels sont créés dans le code.

1. Scène 3D de base

Envato Tuts + a déjà quelques tutoriels qui pourraient vous aider à démarrer avec Three.js. En voici quelques-uns pour vous aider à démarrer.

  • Le guide de Noob en Three.js
  • WebGL avec Three.js: bases
  • Three.js pour le développement de jeux

Commençons par créer une scène 3D de base, comme illustré ici, où il y a un cube en rotation. Vous pouvez utiliser le glisser de la souris pour faire le tour du cube.

Tout graphique affiché sur un écran bidimensionnel est pratiquement de nature 2D, avec quelques éléments importants qui fournissent l’illusion 3D: l’éclairage, les ombres, les ombres et la magie de la projection 3D à 2D qui se produit via la caméra. Dans la scène ci-dessus, nous permettons un éclairage efficace en utilisant ces lignes de code.

camera = new THREE.PerspectiveCamera (60, sceneWidth / sceneHeight, 0.1, 1000); // rendu de caméra en perspective = new THREE.WebGLRenderer (alpha: true); // rendu avec un fond transparent renderer.shadowMap.enabled = true; // activer shadow renderer.shadowMap.type = THREE.PCFSoftShadowMap; //… hero = new THREE.Mesh (heroGeometry, heroMaterial); hero.castShadow = true; hero.receiveShadow = false; //… ground.receiveShadow = true; ground.castShadow = false; //… sun = new THREE.DirectionalLight (0xffffff, 0.8); sun.position.set (0,4,1); sun.castShadow = true; scene.add (soleil); // Définition des propriétés d'ombre pour la lumière du soleil sun.shadow.mapSize.width = 256; sun.shadow.mapSize.height = 256; sun.shadow.camera.near = 0,5; sun.shadow.camera.far = 50;

le moteur de rendu a besoin d'avoir shadowMap activé, la scène doit avoir une lumière avec ombre activé, et tous les objets 3D ont besoin de la ombre et recevoir ombre propriétés définies correctement. Pour que l’ombrage soit correct, nous devons également utiliser le MeshStandardMaterial ou un matériau plus riche en fonctionnalités pour nos objets 3D. La caméra est contrôlée à l'aide du script astucieux OrbitControls. Je vous conseillerais de jouer avec la scène 3D de base en ajoutant des formes plus primitives ou en jouant avec l'éclairage, etc., avant de poursuivre le didacticiel..

2. Le concept du coureur sans fin

Il existe de nombreux types de jeux de coureurs sans fin, et le nôtre est un «rouleau sans fin». Nous allons créer un jeu où une boule de neige dévalera une montagne sans fin et nous utiliserons les touches fléchées pour esquiver les arbres entrants. Une chose intéressante est que ce jeu simple ne comportera aucun actif artistique, car tous les composants seraient créés par le code. Voici le jeu complet à jouer.

3. Composants du jeu

Les composants ou éléments principaux du jeu sont: 

  • la boule de neige qui roule
  • les arbres aléatoires
  • le terrain de défilement
  • le brouillard de distance
  • l'effet de collision

Nous allons explorer chacune d’elles une à une dans la section suivante.

Le brouillard

le brouillard est une propriété de la scène 3D Trois. C'est toujours un truc pratique à utiliser pour simuler la profondeur ou afficher un horizon. La couleur du brouillard est importante pour que l’illusion fonctionne correctement et dépend de la couleur de la scène et de la lumière. Comme vous pouvez le voir dans le code ci-dessous, nous définissons également la moteur de rendude clearColor valeur d'être proche de la couleur de la brouillard.

scène = nouveau THREE.Scene (); scene.fog = new THREE.FogExp2 (0xf0fff0, 0,14); camera = new THREE.PerspectiveCamera (60, sceneWidth / sceneHeight, 0.1, 1000); // rendu de caméra en perspective = new THREE.WebGLRenderer (alpha: true); // rendu avec un fond transparent render.setClearColor (0xfffafa, 1) ; 

Afin de correspondre à l'ambiance, nous utilisons également des valeurs de couleur similaires aux lumières utilisées dans la scène. Chaque couleur ambiante est une nuance de blanc différente qui se gélifie pour créer l'effet nécessaire.

var hemisphereLight = new THREE.HemisphereLight (0xfffafa, 0x000000, .9) scene.add (hemisphereLight); sun = new THREE.DirectionalLight (0xcdc1c5, 0.9); sun.position.set (12,6, -7); sun.castShadow = true; scene.add (soleil);

La boule de neige

Notre boule de neige est un Géométrie Dodécaèdre trois formes primitives créées comme indiqué ci-dessous.

var sphereGeometry = new THREE.DodecahedronGeometry (heroRadius, 1); var sphereMaterial = new THREE.MeshStandardMaterial (color: 0xe5f2f2, ombrage: THREE.FlatShading) heroSphere = new THREE.Mesh (sphereGeometry, sphereMaterial);

Pour tous les éléments 3D de ce jeu, nous utilisons TROIS.FlatShading pour obtenir le look low poly désiré.

La montagne qui défile

Le terrain de défilement nommé rollingGroundSphere est un grand SphèreGéométrie primitif, et nous le faisons tourner sur le X axe pour créer l'illusion de terrain en mouvement. La boule de neige ne tourne vraiment pas sur rien; nous créons simplement l'illusion en maintenant la sphère terrestre tout en maintenant la boule de neige immobile. 

Une sphère normale primitive sera très lisse et ne fournira donc pas la robustesse nécessaire à la pente de la montagne. Nous faisons donc quelques manipulations de vertex pour changer la surface de la sphère lisse en un terrain accidenté. Voici le code correspondant suivi d'une explication.

var côtés = 40; var tiers = 40; var sphereGeometry = new THREE.SphereGeometry (worldRadius, sides, tiers); var sphereMaterial = new THREE.MeshStandardMaterial (color: 0xfffafa, shading: THREE.FlatShading) var vertexIndex; var vertexVector = new THREE.Vector3 (); var nextVertexVector = new THREE.Vector3 (); var firstVertexVector = new THREE.Vector3 (); var offset = new THREE.Vector3 (); var currentTier = 1; var lerpValue = 0,5; var heightValue; var maxHeight = 0,07; pour (var j = 1; j

Nous créons une primitive de sphère avec 40 segments horizontaux (côtés) et 40 segments verticaux (les gradins). Chaque sommet d’une géométrie à trois est accessible via le bouton sommets propriété de tableau. Nous parcourons tous les niveaux entre les sommets extrême haut et extrême bas pour effectuer nos manipulations de sommet. Chaque niveau de la géométrie de la sphère contient exactement côtés nombre de sommets formant un anneau fermé autour de la sphère. 

La première étape consiste à faire pivoter chaque anneau de sommets impair afin de casser l'uniformité des contours de surface. Nous déplaçons chaque sommet de l'anneau d'une fraction aléatoire comprise entre 0,25 et 0,75 de la distance jusqu'au sommet suivant. En conséquence, les sommets verticaux de la sphère ne sont plus alignés en ligne droite et nous obtenons un joli contour en zigzag.. 

Dans un deuxième temps, nous fournissons à chaque sommet un réglage de hauteur aléatoire aligné sur la normale au sommet, quel que soit le niveau auquel il appartient. Il en résulte une surface inégale et accidentée. J'espère que les mathématiques vectorielles utilisées ici sont simples quand on considère que le centre de la sphère est considéré comme l'origine (0,0).

Les arbres

Les arbres apparaissent en dehors de notre piste de roulement pour ajouter de la profondeur au monde, et à l'intérieur comme des obstacles. La création de l'arbre est un peu plus compliquée que le terrain accidenté mais suit la même logique. Nous utilisons un CôneGéométrie primitif pour créer la partie verte supérieure de l’arbre et un Géométrie Cylindre pour créer la partie inférieure du coffre. 

Pour la partie supérieure, nous parcourons chaque niveau de sommets et développons l'anneau de sommets, puis réduisons l'anneau suivant. Le code suivant montre le blowUpTree méthode utilisée pour élargir l’anneau alternatif de sommets vers l’extérieur et la serrerTree méthode utilisée pour réduire l'anneau suivant de sommets.

function createTree () var sides = 8; var tiers = 6; var scalarMultiplier = (Math.random () * (0.25-0.1)) + 0,05; var midPointVector = new THREE.Vector3 (); var vertexVector = new THREE.Vector3 (); var treeGeometry = new THREE.ConeGeometry (0.5, 1, côtés, niveaux); var treeMaterial = new THREE.MeshStandardMaterial (color: 0x33ff33, shading: THREE.FlatShading); var offset; midPointVector = treeGeometry.vertices [0] .clone (); var currentTier = 0; var vertexIndex; blowUpTree (treeGeometry.vertices, sides, 0, scalarMultiplier); tightenTree (treeGeometry.vertices, sides, 1); blowUpTree (treeGeometry.vertices, sides, 2, scalarMultiplier * 1.1, true); tightenTree (treeGeometry.vertices, sides, 3); blowUpTree (treeGeometry.vertices, sides, 4, scalarMultiplier * 1.2); tightenTree (treeGeometry.vertices, sides, 5); var treeTop = new THREE.Mesh (treeGeometry, treeMaterial); treeTop.castShadow = true; treeTop.receiveShadow = false; treeTop.position.y = 0,9; treeTop.rotation.y = (Math.random () * (Math.PI)); var treeTrunkGeometry = nouvelle THREE.CylinderGeometry (0.1, 0.1,0.5); var trunkMaterial = new THREE.MeshStandardMaterial (color: 0x886633, shading: THREE.FlatShading); var treeTrunk = new THREE.Mesh (treeTrunkGeometry, trunkMaterial); treeTrunk.position.y = 0,25; var tree = new THREE.Object3D (); tree.add (treeTrunk); tree.add (treeTop); arbre de retour;  function blowUpTree (sommets, côtés, currentTier, scalarMultiplier, impair) var vertexIndex; var vertexVector = new THREE.Vector3 (); var midPointVector = sommets [0] .clone (); var offset; pour (var i = 0; i

le blowUpTree méthode supprime chaque sommet alternatif dans un anneau de sommets tout en maintenant les autres sommets de l’anneau à une hauteur moindre. Cela crée les branches pointues sur l'arbre. Si nous utilisons les sommets impairs dans un niveau, nous utilisons les sommets pairs dans le niveau suivant afin que l'uniformité soit rompue. Une fois l’arbre complet formé, nous lui donnons une rotation aléatoire sur l’axe des y pour le rendre légèrement différent.

L'effet d'explosion

L’effet d’explosion de pixels en bloc n’est pas le plus élégant que nous puissions utiliser, mais il fonctionne certainement bien. Cet effet de particule particulier est en fait une géométrie 3D qui est manipulée pour ressembler à un effet à l'aide de la Trois points classe. 

fonction addExplosion () particleGeometry = new THREE.Geometry (); pour (var i = 0; i < particleCount; i ++ )  var vertex = new THREE.Vector3(); particleGeometry.vertices.push( vertex );  var pMaterial = new THREE.ParticleBasicMaterial( color: 0xfffafa, size: 0.2 ); particles = new THREE.Points( particleGeometry, pMaterial ); scene.add( particles ); particles.visible=false;  function explode() particles.position.y=2; particles.position.z=4.8; particles.position.x=heroSphere.position.x; for (var i = 0; i < particleCount; i ++ )  var vertex = new THREE.Vector3(); vertex.x = -0.2+Math.random() * 0.4; vertex.y = -0.2+Math.random() * 0.4 ; vertex.z = -0.2+Math.random() * 0.4; particleGeometry.vertices[i]=vertex;  explosionPower=1.07; particles.visible=true;  function doExplosionLogic()//called in update if(!particles.visible)return; for (var i = 0; i < particleCount; i ++ )  particleGeometry.vertices[i].multiplyScalar(explosionPower);  if(explosionPower>1,005) explosionPower- = 0,001;  else particules.visible = false;  particleGeometry.verticesNeedUpdate = true; 

le addExplosion méthode ajoute 20 sommets à la sommets tableau des particuleGéométrie. le exploser La méthode est appelée lorsque nous avons besoin de l'effet, qui positionne de manière aléatoire chaque sommet de la géométrie. le doExplosionLogic se fait appeler dans le mettre à jour méthode si l'objet particule est visible, où on déplace chaque sommet vers l'extérieur. Chaque sommet dans un points l'objet est rendu sous forme de bloc carré.

4. Le gameplay

Maintenant que nous savons comment créer chacun des éléments nécessaires au jeu, passons au gameplay. Les principaux éléments de jeu sont:

  • la boucle du jeu
  • le placement des arbres
  • l'interaction de l'utilisateur
  • la détection de collision

Analysons ceux en détail.

La boucle du jeu

Tout le mécanisme de base du jeu se passe dans la boucle de jeu, qui dans notre cas est la mettre à jour méthode. Nous l'appelons pour la première fois depuis le init méthode, qui est appelée sur le chargement de la fenêtre. Après cela, il s’accroche à la boucle de rendu du document à l’aide du bouton requestAnimationFrame méthode pour qu'il soit appelé à plusieurs reprises. 

function update () rollingGroundSphere.rotation.x + = rollingSpeed; heroSphere.rotation.x - = heroRollingSpeed; si (heroSphere.position.y<=heroBaseY) jumping=false; bounceValue=(Math.random()*0.04)+0.005;  heroSphere.position.y+=bounceValue; heroSphere.position.x=THREE.Math.lerp(heroSphere.position.x,currentLane, 2*clock.getDelta());//clock.getElapsedTime()); bounceValue-=gravity; if(clock.getElapsedTime()>treeReleaseInterval) clock.start (); addPathTree (); if (! hasCollided) score + = 2 * treeReleaseInterval; scoreText.innerHTML = score.toString ();  doTreeLogic (); doExplosionLogic (); rendre(); requestAnimationFrame (update); // demande la prochaine mise à jour function render () renderer.render (scène, caméra); // draw

Dans mettre à jour, nous appelons le rendre méthode, qui utilise le moteur de rendu dessiner la scène. Nous appelons le doTreeLogic méthode, qui vérifie la collision et supprime également les arbres une fois qu'ils sont hors de vue. 

La boule de neige et les sphères terrestres sont en rotation tandis que nous ajoutons également une logique de rebondissement aléatoire à la boule de neige. Les nouveaux arbres sont placés dans le chemin en appelant addPathTree après un temps prédéfini. Le temps est suivi en utilisant un TROIS heures objet. Nous mettons également à jour le But sauf si une collision s'est produite.

Placement des arbres

Un ensemble d’arbres est placé en dehors de la piste de roulement pour créer le monde à l’aide des addWorldTrees méthode. Tous les arbres sont ajoutés en tant qu'enfant du rollingGroundSphere afin qu'ils bougent aussi quand on tourne la sphère. 

fonction addWorldTrees () var numTrees = 36; écart var = 6,28 / 36; pour (var i = 0; i

Pour planter des arbres du monde, nous appelons le addTree méthode en passant des valeurs autour de la circonférence de notre sphère terrestre. le sphériqueHelper utilité nous aide à trouver la position sur la surface d'une sphère.

Pour planter des arbres sur le chemin, nous allons utiliser un pool d’arbres créés au démarrage en utilisant createTreesPool méthode. Nous avons également des valeurs d’angle prédéfinies pour chaque trajet de la sphère stockées dans pathAngleValues tableau.

pathAngleValues ​​= [1.52,1.57,1.62]; //… function createTreesPool () var maxTreesInPool = 10; var newTree; pour (var i = 0; i0,5) voie = Math.floor (Math.random () * 2); addTree (true, options [lane]); 

le addPathTree La méthode est appelée depuis la mise à jour quand il s'est écoulé suffisamment de temps après la plantation du dernier arbre. Il appelle à son tour le addTree méthode montrée précédemment avec un ensemble de paramètres différent où l’arbre est placé dans le chemin sélectionné. le doTreeLogic la méthode retournera l’arbre à la piscine une fois qu’elle disparaîtra.

Interaction de l'utilisateur

Nous ajoutons un auditeur au document pour rechercher des événements de clavier pertinents. le handleKeyDown méthode définit le currentLane valeur si les touches fléchées droite ou gauche sont enfoncées ou définit le bounceValue valeur si la flèche vers le haut est enfoncée.

document.onkeydown = handleKeyDown; //… function handleKeyDown (keyEvent) if (sautant) return; var validMove = true; if (keyEvent.keyCode === 37) // left si (currentLane == middleLane) currentLane = leftLane;  else if (currentLane == rightLane) currentLane = middleLane;  else validMove = false;  else if (keyEvent.keyCode === 39) // right if (currentLane == middleLane) currentLane = rightLane;  else if (currentLane == leftLane) currentLane = middleLane;  else validMove = false;  else if (keyEvent.keyCode === 38) // up, jump bounceValue = 0.1; sauter = vrai;  validMove = false;  if (validMove) jumping = true; valeur de rebond = 0,06; 

Dans mettre à jour, la X la position de notre boule de neige s’incrémente lentement pour atteindre la currentLane positionner en changeant de voie. 

Détection de collision

Il n'y a pas de physique réelle impliquée dans ce jeu particulier, bien que nous puissions utiliser divers cadres de physique pour notre objectif de détection de collision. Mais comme vous le savez bien, un moteur physique ajoute beaucoup de performance à notre jeu et nous devrions toujours essayer de voir si nous pouvons l'éviter.. 

Dans notre cas, nous calculons simplement la distance entre notre boule de neige et chaque arbre pour déclencher une collision s’ils sont très proches. Cela se passe dans le doTreeLogic méthode, appelée par mettre à jour.

function doTreeLogic () var oneTree; var treePos = new THREE.Vector3 (); treesInPath.forEach (fonction (élément, index) oneTree = treesInPath [index]; treePos.setFromMatrixPosition (oneTree.matrixWorld); if (treePos.distanceTo (heroSphere.position)<=0.6) console.log("hit"); hasCollided=true; explode();  ); //… 

Comme vous avez pu le constater, tous les arbres actuellement présents sur notre chemin sont stockés dans le répertoire. treesInPath tableau. le doTreeLogic La méthode supprime également les arbres de l'affichage et dans la piscine une fois qu'ils sont hors de notre vue en utilisant le code ci-dessous.

var treesToRemove = []; treesInPath.forEach (fonction (élément, index) oneTree = treesInPath [index]; treePos.setFromMatrixPosition (oneTree.matrixWorld); if (treePos.z> 6 && oneTree.visible) // est sorti de notre zone de vue treesToRemove.push (un arbre);  ); var fromWhere; treesToRemove.forEach (fonction (élément, index) oneTree = treesToRemove [index]; fromWhere = treesInPath.indexOf (oneTree); treesInPath.splice (fromWhere, 1); treesPool.push (oneTree); onePree.Office = false; console; .log ("supprimer l'arbre"););

Conclusion

La création d'un jeu en 3D est un processus compliqué si vous n'utilisez pas un outil visuel comme Unity. Cela peut sembler intimidant ou accablant, mais laissez-moi vous assurer qu'une fois que vous aurez compris, vous vous sentirez beaucoup plus puissant et créatif. J'aimerais que vous exploriez davantage en utilisant les divers cadres de physique ou systèmes de particules ou les exemples officiels.