Crearea unui simplu joc 3D fără sfârșit a alergătorului Utilizând Three.js

Ce veți crea

Platforma web a înregistrat o creștere extraordinară în ultima vreme cu ajutorul HTML5, WebGL și a creșterii puterii generației actuale de dispozitive. Acum, dispozitivele mobile și browserele sunt capabile să furnizeze conținut de înaltă performanță atât în ​​2D, cât și în 3D. Familiaritatea JavaScript (JS) ca limbaj de scripting a fost, de asemenea, un factor de conducere, după decesul platformei web Flash. 

Majoritatea dezvoltatorilor de web-uri sunt bine conștienți de cât de complicat este ecosistemul JS cu toate cadrele și standardele disponibile, care ar putea uneori să fie copleșitoare pentru un nou dezvoltator. Dar când vine vorba de 3D, alegerile sunt simple, datorită domnului Doo. Setul său Three.js este în prezent cea mai bună opțiune acolo pentru a crea conținut 3D WebGL performant. O altă alternativă puternică este Babylon.js, care ar putea fi de asemenea folosită pentru a face jocuri 3D.

În acest tutorial, veți învăța să creați un simplu joc fără precedent în stilul de joc nativ 3D folosind cadrul puternic Three.js. Utilizați tastele săgeată pentru a controla o bulgăre de zăpadă care se rostogolește pe o parte a muntelui, pentru a evita arborii în calea ta. Nu există o artă implicată, iar toate imaginile sunt create în cod.

1. Scenă 3D de bază

Envato Tuts + are deja cateva tutoriale care te-ar putea incepe cu Three.js. Iată câteva dintre ele pentru a începe.

  • Un ghid al lui Noob la trei.js
  • WebGL cu Three.js: Noțiuni de bază
  • Three.js pentru dezvoltarea jocurilor

Să facem mai întâi o scenă 3D de bază, așa cum se arată aici, unde există un cub rotativ. Puteți utiliza trageți mouse-ul pentru a orbita în jurul cubului.

Orice grafic afișat pe un ecran bidimensional are practic 2D în natură, cu câteva elemente importante care oferă iluzia 3D: iluminarea, umbrirea, umbrele și magia de proiectare 3D până la 2D care se întâmplă prin intermediul camerei. În scenia de mai sus, permitem iluminarea eficientă utilizând aceste linii de cod.

camera = noua cameră THREE.PerspectiveCamera (60, sceneWidth / sceneHeight, 0.1, 1000); // perspectivă renderer cameră = nouă THREE.WebGLRenderer (alpha: true); // renderer cu backdrop transparent renderer.shadowMap.enabled = true; // enable shadow renderer.shadowMap.type = THREE.PCFSoftShadowMap; // ... erou = nou TREI.Mesh (eroGeometrie, heroMaterial); hero.castShadow = true; hero.receiveShadow = false; // ... ground.receiveShadow = adevărat; ground.castShadow = false; // ... sun = nouă lumină tridimensională (0xffffff, 0,8); poziția sun.position (0,4,1); sun.castShadow = adevărat; scene.add (soare); // Configurați proprietățile umbrelor pentru lumina soarelui sun.shadow.mapSize.width = 256; sun.shadow.mapSize.height = 256; sun.shadow.camera.near = 0.5; sun.shadow.camera.far = 50;

renderer trebuie să aibă shadowMap activat, scena trebuie să aibă o lumină cu castShadow activat și toate obiectele 3D au nevoie castShadow și receiveShadow proprietățile stabilite corespunzător. Pentru ca umbrele să se producă, ar trebui să folosim și MeshStandardMaterial sau un material mai bogat în caracteristici pentru obiectele noastre 3D. Camera este controlată cu ajutorul scriptului OrbitControls. Aș recomanda să jucați cu scena 3D de bază prin adăugarea de forme mai primitive sau prin joc cu iluminare etc. înainte de a continua cu tutorialul.

2. Conceptul Endless Runner

Există multe tipuri de jocuri fără sfârșit, iar a noastră este o "rolă nesfârșită". Vom crea un joc în care un bulgăre de zăpadă se rostogolește pe o suprafață montană nesfârșită, unde folosim săgețile pentru a evita arborii care intră. Un lucru interesant este că acest joc simplu nu va implica niciun fel de elemente de artă, deoarece toate componentele ar fi create prin cod. Aici este jocul complet de jucat.

3. Componente ale jocului

Principalele componente sau elemente ale jocului sunt: 

  • bulgăre de zăpadă
  • copacii aleatorii
  • terenul de defilare
  • distanța ceață
  • efectul de coliziune

Vom explora fiecare dintre acestea unul câte unul în următoarea secțiune.

Ceata

ceaţă este o proprietate a scenei 3D din Trei. Este întotdeauna un truc ușor de folosit pentru a simula adâncimea sau pentru a arăta un orizont. Culoarea ceață este importantă pentru ca iluzia să funcționeze corect și depinde de culoarea scenei și de iluminare. După cum puteți vedea în codul de mai jos, setăm de asemenea renderer„s clearColor valoare pentru a fi aproape de culoarea ceaţă.

scenă = nouă TRIȘIUNI (); scene.fog = nou THREE.FogExp2 (0xf0fff0, 0.14); camera = nouă cameră THREE.PerspectiveCamera (60, sceneWidth / sceneHeight, 0.1, 1000); // vizualizator de cameră cu perspectivă = new THREE.WebGLRenderer (alpha: true; // // renderer cu backdrop transparent renderer.setClearColor (0xfffafa, ; 

Pentru a potrivi ambianța, folosim, de asemenea, valori similare de culoare pentru luminile utilizate în scenă. Fiecare culoare înconjurătoare este o nuanță de culoare albă, care coagulează împreună pentru a crea efectul necesar.

var hemisphereLight = noua lumină THREE.HemisphereLight (0xfffafa, 0x000000, .9) scene.add (hemisphereLight); soare = nouă lumină tridimensională (0xcdc1c5, 0.9); poziția sun.position (12,6, -7); sun.castShadow = adevărat; scene.add (soare);

The Snowball

Bătața noastră de zăpadă este a DodecahedronGeometry trei forme primitive create după cum se arată mai jos.

sferă varăGeometrie = nouă TREI.DecodectronăGeometrie (heroRadius, 1); var sphereMaterial = nou TRI.MeshStandardMaterial (culoare: 0xe5f2f2, umbrire: THREE.FlatShading) heroSphere = nou TRE.Mesh (sferaGeometrie, sferaMaterial);

Pentru toate elementele 3D din acest joc pe care le folosim THREE.FlatShading pentru a obține aspectul dorit de poli-scăzut.

Muntele Scrolling

Țara de defilare numită rollingGroundSphere este mare SphereGeometry primitiv, și îl rotim pe X axa pentru a crea iluzia terenului în mișcare. Blocul de zăpadă nu se rostogolește cu nimic; noi creăm doar iluzia prin menținerea sferei de la sol în timp ce păstrăm mingia de zăpadă staționară. 

O primitivă sferică normală va arăta foarte netedă și, prin urmare, nu va asigura robustețea necesară pentru panta montană. Așa că facem unele manipulări de vârfuri pentru a schimba suprafața sferei netede într-un teren accidentat. Aici este codul corespunzător urmat de o explicație.

var laturi = 40; var tiers = 40; sferă varăGeometrie = nouă treime geometrie (lumeRadius, laturi, niveluri); var sphereMaterial = nou TRI.MeshStandardMaterial (culoare: 0xfffafa, umbrire: THREE.FlatShading) var vertexIndex; var vertexVector = nou THREE.Vector3 (); var următorulVertexVector = nou THREE.Vector3 (); var firstVertexVector = nou THREE.Vector3 (); var offset = nou THREE.Vector3 (); var currentTier = 1; var lerpValue = 0,5; var heightValue; var maxHeight = 0,07; pentru (var j = 1;

Creați o primă sferică cu 40 de segmente orizontale (fete) și 40 de segmente verticale (niveluri). Fiecare vertex al unei geometrii a trei poate fi accesat prin noduri proprietate array. Vom trece prin toate nivelurile între vârfurile extreme de sus și cele de jos extreme pentru a face manipulările noastre de vârfuri. Fiecare nivel al geometriei sferei conține exact fete numărul de vârfuri, care formează un inel închis în jurul sferei. 

Primul pas este să rotiți fiecare inel ciudat de vârfuri pentru a rupe uniformitatea conturului suprafeței. Deplasăm fiecare vârf din inel cu o fracțiune aleatorie între 0,25 și 0,75 de la distanța până la următorul vârf. Drept urmare, nodurile verticale ale sferei nu mai sunt aliniate în linie dreaptă și avem un contur zigzag frumos. 

Ca a doua etapă, oferim fiecărui vârf o ajustare aleatoare a înălțimii aliniată la normal la vârf, indiferent de nivelul de care aparține. Aceasta are ca rezultat o suprafață neuniformă și accidentată. Sper că matematica vectorială folosită aici este simplă odată ce consideri că centrul sferei este considerat originea (0,0).

Copacii

Arborii apar în afara căii noastre de rulare pentru a adăuga adâncime lumii și înăuntru ca obstacole. Crearea arborelui este un pic mai complicat decât terenul accidentat, dar urmează aceeași logică. Noi folosim a ConeGeometry primitiv pentru a crea partea verde de sus a copacului și a CylinderGeometry pentru a crea partea inferioară a trunchiului. 

Pentru partea de sus, buclele prin fiecare nivel de vârfuri și extindeți inelul de vârfuri, urmată de micșorarea următorului inel. Următorul cod arată blowUpTree metoda folosită pentru a extinde inelul alternativ de vârfuri spre exterior și tightenTree metoda folosită pentru a micșora următorul inel de vârfuri.

funcția createTree () var sides = 8; var tiers = 6; var scalarMultiplier = (Math.random () * (0,25-0,1)) + 0,05; var midPointVector = nou THREE.Vector3 (); var vertexVector = nou THREE.Vector3 (); var treeGeometrie = nouă GESometrie triunghiulară (0,5, 1, laturi, niveluri); var treeMaterial = nou TRI.MeshStandardMaterial (culoare: 0x33ff33, umbrire: THREE.FlatShading); var offset; midPointVector = treeGeometry.vertices [0] .clone (); var currentTier = 0; var vertexIndex; blowUpTree (treeGeometry.vertices, fete, 0, scalarMultiplier); tightenTree (treeGeometry.vertices, fete, 1); blowUpTree (treeGeometry.vertices, fete, 2, scalarMultiplier * 1.1, true); tightenTree (treeGeometry.vertices, fete, 3); blowUpTree (treeGeometry.vertices, fete, 4, scalarMultiplier * 1.2); tightenTree (treeGeometry.vertices, fete, 5); var treeTop = nou THREE.Mesh (treeGeometry, treeMaterial); treeTop.castShadow = true; treeTop.receiveShadow = false; treeTop.position.y = 0,9; treeTop.rotation.y = (Math.random () * (Math.PI)); var treeTrunkGeometry = noua gaometrie de trei ori (0,1, 0,1,0,5); var trunkMaterial = nou THREE.MeshStandardMaterial (culoare: 0x886633, umbrire: THREE.FlatShading); var treeTrunk = TREE.Mesh (TreeTrunkGeometrie, trunkMaterial); treeTrunk.position.y = 0,25; var tree = nou THREE.Object3D (); tree.add (treeTrunk); tree.add (treetop); retur copac;  funcția blowUpTree (vârfuri, laturi, currentTier, scalarMultiplier, ciudat) var vertexIndex; var vertexVector = nou THREE.Vector3 (); var midPointVector = noduri [0] .clone (); var offset; pentru (var i = 0; i

blowUpTree metoda împinge fiecare vertex alternativ într-un inel de vârfuri, păstrând în același timp celelalte noduri din inel la o înălțime mai mică. Acest lucru creează ramurile cu vârfuri arborice. Dacă vom folosi vârfurile ciudate într-un singur nivel, vom folosi vârfurile uniforme din următorul nivel, astfel încât uniformitatea să fie întreruptă. Odată ce se formează arborele complet, îi oferim o rotație aleatorie pe axa y pentru a face să pară puțin diferită.

Efectul de explozie

Efectul de explozie a pixelilor bloc nu este cel mai elegant pe care l-am putea folosi, dar cu siguranță se comportă bine. Acest efect particular de particule este de fapt o geometrie 3D care este manipulată pentru a arăta ca un efect care utilizează THREE.Points clasă. 

funcția addExplosion () particleGeometry = noua GESometrie (); pentru (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) exploionPower- = 0.001;  altceva particles.visible = false;  particleGeometry.verticesNeedUpdate = true; 

 addExplosion metoda adaugă 20 de vârfuri la noduri array of the particleGeometry.  exploda metoda se numește atunci când avem nevoie de efectul de a alerga, care poziționează aleator fiecare vârf al geometriei. doExplosionLogic este chemat în Actualizați dacă obiectul particulei este vizibil, unde mutăm fiecare vârf spre exterior. Fiecare vârf din a puncte obiect devine redat ca un bloc pătrat.

4. Modul de joc

Acum, că știm cum să creăm fiecare dintre elementele necesare jocului, să intrăm în joc. Principalele elemente de joc sunt:

  • jocul de buzunar
  • plasarea copacilor
  • interacțiunea utilizatorului
  • detectarea coliziunii

Să analizăm detaliile.

Jocul Loop

Toți mecanicii jocului de bază se întâmplă în bucla de joc, care în cazul nostru este Actualizați metodă. Noi o numim pentru prima dată din init metoda, care devine apelată la încărcarea ferestrelor. După aceasta, se cuplează pe bucla de randare a documentelor utilizând requestAnimationFrame astfel încât să se numească repetat. 

actualizare functie () rollingGroundSphere.rotation.x + = rollingSpeed; heroSphere.rotation.x - = heroRollingSpeed; în cazul în care (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 (); dacă ! hasCollided) scorul + = 2 * treeReleaseInterval; scoreText.innerHTML = score.toString ();  doTreeLogic (); doExplosionLogic (); face(); requestAnimationFrame (actualizare); // cere noua actualizare function render () renderer.render (scena, camera); // draw

În Actualizați, noi numim face care utilizează renderer pentru a desena scena. Noi numim doTreeLogic care verifică coliziunea și, de asemenea, scoate copacii după ce au ieșit din vedere. 

Bilele de zăpadă și sferele de pământ se rotesc în timp ce adăugăm, de asemenea, o logică aleatorie la balonul de zăpadă. Arborii noi sunt plasați în cale prin apel addPathTree după ce a trecut un timp prestabilit. Timpul este urmărit folosind a THREE.Clock obiect. De asemenea, actualizăm scor cu excepția cazului în care a survenit o coliziune.

Plasarea copacilor

Un set de copaci este plasat în afara căii ferate pentru a crea lumea folosind addWorldTrees metodă. Toți copacii sunt adăugați ca un copil al familiei rollingGroundSphere astfel încât să se miște și atunci când rotim sfera. 

funcția addWorldTrees () var numTrees = 36; var gap = 6,28 / 36; pentru (var i = 0; i

Pentru a planta copacii lumii, numim addTree prin trecerea valorilor în jurul circumferinței sferei noastre pământești. sphericalHelper utilitate ne ajută să găsim poziția pe suprafața unei sfere.

Pentru a planta copaci pe traseu, vom folosi o piscina de copaci care sunt create la inceput folosind createTreesPool metodă. De asemenea, avem valori predefinite ale unghiului pentru fiecare cale din sfera stocată în pathAngleValues mulțime.

pathAngleValues ​​= [1.52,1.57,1.62]; // ... funcția createTreesPool () var maxTreesInPool = 10; var newTree; pentru (var i = 0; i0.5) Lane = Math.floor (Math.random () * 2); addTree (true, opțiunile [banda]); 

addPathTree metoda este chemată din actualizare când a trecut suficient timp după plantarea ultimului copac. La rândul său, îl cheamă addTree metoda prezentată mai devreme cu un alt set de parametri în care arborele este plasat în calea selectată. doTreeLogic metoda va returna copacul în piscină odată ce acesta nu mai este vizibil.

Interacțiunea cu utilizatorul

Adăugăm un document de ascultător pentru a căuta evenimente de tastatură relevante. handleKeyDown metoda stabilește currentLane dacă tastele săgeată din dreapta sau din stânga sunt apăsate sau setează bounceValue dacă este apăsată săgeata în sus.

document.onkeydown = handleKeyDown; // ... handleKeyDown (keyEvent) if (jumping) retur; var validMove = true; dacă (keyEvent.keyCode === 37) // a plecat dacă (currentLane == middleLane) currentLane = leftLane;  altfel dacă (currentLane == rightLane) currentLane = middleLane;  altfel validMove = false;  altfel dacă (keyEvent.keyCode === 39) // dreapta dacă (currentLane == middleLane) currentLane = rightLane;  altfel dacă (currentLane == leftLane) currentLane = middleLane;  altfel validMove = false;  altceva if (keyEvent.keyCode === 38) // sus, salt bounceValue = 0.1; sărituri = true;  validMove = false;  dacă (validMove) jumping = true; bounceValue = 0,06; 

În Actualizați, X poziția mingii noastre de zăpadă crește încet pentru a ajunge la currentLane poziția acolo prin comutarea benzilor. 

Detectarea coliziunii

Nu există o fizică reală implicată în acest joc, deși am putea folosi diferite cadre fizice pentru scopul nostru de detectare a coliziunii. Dar, după cum știți, un motor de fizică adaugă la jocul nostru o mulțime de performanțe și ar trebui să încercăm întotdeauna să vedem dacă putem evita acest lucru. 

În cazul nostru, se calculează distanța dintre globul nostru de zăpadă și fiecare copac pentru a declanșa o coliziune dacă acestea sunt foarte apropiate. Acest lucru se întâmplă în doTreeLogic metoda, care este chemat de la Actualizați.

funcția doTreeLogic () var oneTree; var treePos = nou THREE.Vector3 (); arboreleInPath.forEach (funcție (element, index) oneTree = treesInPath [index]; treePos.setFromMatrixPosition (oneTree.matrixWorld); if (treePos.distanceTo (heroSphere.position)<=0.6) console.log("hit"); hasCollided=true; explode();  ); //… 

După cum probabil ați observat, toți copacii prezenți în prezent în calea noastră sunt stocați în treesInPath matrice. doTreeLogic De asemenea, metoda elimină arborii de pe ecran și în piscină odată ce au ieșit din viziunea noastră folosind codul de mai jos.

var copaciToRemove = []; copaciInPath.forEach (functie (element, index) oneTree = treesInPath [index]; treePos.setFromMatrixPosition (oneTree.matrixWorld); daca (treePos.z> 6 && oneTree.visible) // plecat din zona noastra de vedere tree treesToRemove.push (un copac);  ); var dincolo; treesToRemove.forEach (funcție (element, index) oneTree = treesToRemove [index]; fromWhere = treesInPath.indexOf (oneTree); treesInPath.splice (fromWhere, 1); copaciPool.push (oneTree); oneTree.visible = false; .log ("eliminați copacul"););

Concluzie

Crearea unui joc 3D este un proces complicat dacă nu folosiți un instrument vizual cum ar fi Unitatea. S-ar părea intimidantă sau copleșitoare, dar permiteți-mi să vă asigur că, odată ce veți fi atârnat de ea, veți simți mult mai puternic și mai creativ. Aș dori să explorați în continuare folosind diverse cadre fizice sau sisteme de particule sau exemple oficiale.