Creazione di un semplice gioco Endless Runner 3D usando Three.js

Cosa starai creando

La piattaforma web ha avuto un'enorme crescita negli ultimi tempi con l'aiuto di HTML5, WebGL e la maggiore potenza dell'attuale generazione di dispositivi. Ora i dispositivi mobili e i browser sono in grado di offrire contenuti ad alte prestazioni sia in 2D che in 3D. La familiarità di JavaScript (JS) come linguaggio di scripting è stata anche un fattore trainante, dopo la scomparsa della piattaforma web Flash. 

La maggior parte degli sviluppatori web è ben consapevole di quanto sia complicato l'ecosistema JS con tutti i vari framework e standard disponibili, che a volte potrebbero essere schiaccianti per un nuovo sviluppatore. Ma quando si parla di 3D, le scelte sono semplici, grazie a Mr.Doob. Il suo Three.js è attualmente l'opzione migliore là fuori per creare contenuti WebGL 3D ad alte prestazioni. Un'altra potente alternativa è Babylon.js, che potrebbe essere utilizzata anche per creare giochi 3D.

In questo tutorial, imparerai a creare un semplice gioco web 3D nativo per corridori senza fine utilizzando la potente struttura Three.js. Utilizzerai i tasti freccia per controllare una palla di neve che rotola giù da una montagna per evitare gli alberi sul tuo percorso. Non c'è arte coinvolta e tutte le immagini sono create in codice.

1. Scena 3D di base

Envato Tuts + ha già alcune esercitazioni che potrebbero aiutarti a iniziare con Three.js. Ecco alcuni di questi per iniziare.

  • A Noob's Guide to Three.js
  • WebGL con Three.js: Nozioni di base
  • Three.js per lo sviluppo del gioco

Creiamo prima una scena 3D di base, come mostrato qui dove c'è un cubo rotante. Puoi utilizzare il trascinamento del mouse in orbita attorno al cubo.

Qualsiasi immagine visualizzata su uno schermo bidimensionale è praticamente di natura 2D, con alcuni elementi importanti che forniscono l'illusione 3D: l'illuminazione, l'ombreggiatura, le ombre e la magia di proiezione 3D in 2D che avviene tramite la fotocamera. Nella scena sopra, abilitiamo l'illuminazione efficace usando queste linee di codice.

camera = new THREE.PerspectiveCamera (60, sceneWidth / sceneHeight, 0.1, 1000); // prospettiva camera renderer = new THREE.WebGLRenderer (alpha: true); // renderer con sfondo trasparente renderer.shadowMap.enabled = true; // enable shadow renderer.shadowMap.type = THREE.PCFSoftShadowMap; // ... hero = new THREE.Mesh (heroGeometry, heroMaterial); hero.castShadow = true; hero.receiveShadow = falso; // ... ground.receiveShadow = true; ground.castShadow = falso; // ... sun = new THREE.DirectionalLight (0xffffff, 0.8); sun.position.set (0,4,1); sun.castShadow = true; scene.add (sole); // Imposta le proprietà delle ombre per la luce del sole sun.shadow.mapSize.width = 256; sun.shadow.mapSize.height = 256; sun.shadow.camera.near = 0.5; sun.shadow.camera.far = 50;

Il renderer deve avere shadowmap abilitato, la scena deve avere una luce con castShadow abilitato, e tutti gli oggetti 3D hanno bisogno del castShadowreceiveShadow proprietà impostate in modo appropriato. Perché l'ombreggiatura corretta avvenga, dovremmo usare anche il MeshStandardMaterial o un materiale più ricco di funzionalità per i nostri oggetti 3D. La telecamera è controllata usando lo script nifty OrbitControls. Consiglierei di giocare con la scena 3D di base aggiungendo forme più primitive o giocando con l'illuminazione, ecc., Prima di procedere con il tutorial.

2. Il concetto di corridore senza fine

Esistono molti tipi di giochi per corridori senza fine e il nostro è un "rullo infinito". Creeremo un gioco in cui una palla di neve sta rotolando giù per una montagna senza fine dove usiamo i tasti freccia per schivare gli alberi in arrivo. Una cosa interessante è che questo semplice gioco non coinvolgerà alcun patrimonio artistico, in quanto tutti i componenti sarebbero creati dal codice. Ecco il gioco completo per giocare.

3. Componenti del gioco

I componenti o gli elementi principali del gioco sono: 

  • la palla di neve rotolante
  • gli alberi casuali
  • il terreno di scorrimento
  • la nebbia a distanza
  • l'effetto collisione

Esploreremo ognuno di questi uno per uno nella sezione seguente.

La nebbia

Il nebbia è una proprietà della scena 3D in Tre. È sempre un trucco utile da usare per simulare la profondità o mostrare un orizzonte. Il colore della nebbia è importante perché l'illusione funzioni correttamente e dipende dal colore della scena e dall'illuminazione. Come puoi vedere nel codice qui sotto, impostiamo anche il renderer'S clearColor valore per essere vicino al colore del nebbia.

scene = new THREE.Scene (); scene.fog = new THREE.FogExp2 (0xf0fff0, 0.14); camera = new THREE.PerspectiveCamera (60, sceneWidth / sceneHeight, 0.1, 1000); // prospettiva camera renderer = new THREE.WebGLRenderer (alpha: true); // renderer con sfondo trasparente renderer.setClearColor (0xfffafa, 1) ; 

Per abbinare l'atmosfera, stiamo anche utilizzando valori di colore simili alle luci utilizzate nella scena. Ogni colore ambientale è una diversa tonalità di bianco che si unisce per creare l'effetto necessario.

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 (sole);

La palla di neve

La nostra palla di neve è un DodecahedronGeometry tre forme primitive create come mostrato di seguito.

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

Per tutti gli elementi 3D in questo gioco, stiamo usando THREE.FlatShading per ottenere l'aspetto desiderato a basso numero di poligoni.

The Scrolling Mountain

Il terreno di scorrimento chiamato rollingGroundSphere è un grande SphereGeometry primitivo e lo ruotiamo sul X asse per creare l'illusione del terreno in movimento. La palla di neve non scorre davvero su nulla; stiamo solo creando l'illusione mantenendo la sfera terrestre roteando mantenendo la palla di neve ferma. 

Una primitiva di sfera normale apparirà molto liscia e quindi non fornirà la necessaria robustezza necessaria per il pendio della montagna. Quindi facciamo alcune manipolazioni dei vertici per cambiare la superficie della sfera liscia in un terreno accidentato. Ecco il codice corrispondente seguito da una spiegazione.

lati var = 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; per (var j = 1; j

Stiamo creando una sfera primitiva con 40 segmenti orizzontali (lati) e 40 segmenti verticali (livelli). È possibile accedere a ciascun vertice di una tre geometria tramite vertici proprietà dell'array. Effettuiamo un ciclo attraverso tutti i livelli tra i vertici estremi in alto e in basso estremo per eseguire le nostre manipolazioni dei vertici. Ogni livello della geometria della sfera contiene esattamente lati numero di vertici, che forma un anello chiuso attorno alla sfera. 

Il primo passo consiste nel ruotare ogni anello dispari di vertici per rompere l'uniformità dei contorni della superficie. Spostiamo ogni vertice nell'anello di una frazione casuale tra 0,25 e 0,75 della distanza dal vertice successivo. Come risultato di ciò, i vertici verticali della sfera non sono più allineati in linea retta, e otteniamo un bel contorno a zigzag. 

Come secondo passo, forniamo a ciascun vertice una regolazione dell'altezza casuale allineata con la normale al vertice, indipendentemente dal livello a cui appartiene. Ciò si traduce in una superficie irregolare e robusta. Spero che la matematica vettoriale utilizzata qui sia semplice una volta che si considera che il centro della sfera è considerato l'origine (0,0).

Gli alberi

Gli alberi appaiono fuori dalla nostra pista di rotolamento per aggiungere profondità al mondo e dentro come ostacoli. Creare l'albero è un po 'più complicato di un terreno accidentato, ma segue la stessa logica. Noi usiamo a ConeGeometry primitivo per creare la parte superiore verde dell'albero e a CylinderGeometry per creare la parte inferiore del tronco. 

Per la parte superiore, passiamo attraverso ogni livello di vertici ed espandiamo l'anello dei vertici seguito dal restringimento lungo l'anello successivo. Il seguente codice mostra il blowUpTree metodo utilizzato per espandere l'anello alternativo dei vertici verso l'esterno e il tightenTree metodo utilizzato per ridurre il prossimo anello di vertici.

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, lati, livelli); 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, fianchi, 0, scalarMultiplier); tightenTree (treeGeometry.vertices, lati, 1); blowUpTree (treeGeometry.vertices, lati, 2, scalarMultiplier * 1.1, true); tightenTree (treeGeometry.vertices, fianchi, 3); blowUpTree (treeGeometry.vertices, lati, 4, scalarMultiplier * 1.2); tightenTree (treeGeometry.vertices, fianchi, 5); var treeTop = new THREE.Mesh (treeGeometry, treeMaterial); treeTop.castShadow = true; treeTop.receiveShadow = falso; treeTop.position.y = 0,9; treeTop.rotation.y = (Math.random () * (Math.PI)); var treeTrunkGeometry = new 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); albero di ritorno;  function blowUpTree (vertici, lati, currentTier, scalarMultiplier, dispari) var vertexIndex; var vertexVector = new THREE.Vector3 (); var midPointVector = vertici [0] .clone (); var offset; per (var i = 0; i

Il blowUpTree il metodo spinge ogni vertice alternativo in un anello di vertici mantenendo gli altri vertici nell'anello ad un'altezza minore. Questo crea i rami appuntiti sull'albero. Se usiamo i vertici dispari in un livello, usiamo i vertici pari nel livello successivo in modo che l'uniformità sia interrotta. Una volta formato l'albero completo, diamo una rotazione casuale sull'asse y per renderlo leggermente diverso.

L'effetto di esplosione

L'effetto esplosione del pixel blocco non è il più elegante che potremmo usare, ma sicuramente funziona bene. Questo particolare effetto particellare è in realtà una geometria 3D che viene manipolata per apparire come un effetto usando il THREE.Points classe. 

function addExplosion () particleGeometry = new THREE.Geometry (); per (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 particles.visibile = falso;  particleGeometry.verticesNeedUpdate = true; 

Il addExplosion il metodo aggiunge 20 vertici al vertici matrice del particleGeometry. Il esplodere il metodo viene chiamato quando è necessario eseguire l'effetto, che posiziona casualmente ciascun vertice della geometria. Il doExplosionLogic viene chiamato nel aggiornare metodo se l'oggetto particella è visibile, dove spostiamo ciascun vertice all'esterno. Ogni vertice in a punti l'oggetto viene reso come un blocco quadrato.

4. Il gameplay

Ora che sappiamo come creare ciascuno degli elementi necessari per il gioco, entriamo nel gameplay. Gli elementi principali del gameplay sono:

  • il ciclo di gioco
  • il posizionamento degli alberi
  • l'interazione dell'utente
  • il rilevamento delle collisioni

Analizziamo quelli in dettaglio.

Il ciclo di gioco

Tutta la meccanica di base del gioco avviene nel ciclo di gioco, che nel nostro caso è il aggiornare metodo. Lo chiamiamo per la prima volta dal dentro metodo, che viene richiamato sul carico della finestra. Dopo questo, si aggancia al ciclo di rendering del documento usando il requestAnimationFrame metodo in modo che venga chiamato ripetutamente. 

function update () rollingGroundSphere.rotation.x + = rollingSpeed; heroSphere.rotation.x - = heroRollingSpeed; if (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 (); render (); requestAnimationFrame (update); // request next update function render () renderer.render (scene, camera); // disegna

Nel aggiornare, noi chiamiamo il rendere metodo, che utilizza il renderer per disegnare la scena. Chiamiamo il doTreeLogic metodo, che verifica la collisione e rimuove anche gli alberi una volta scomparsi. 

La palla di neve e le sfere terrestri vengono ruotate mentre aggiungiamo una logica di rimbalzo casuale alla palla di neve. I nuovi alberi vengono posizionati nel percorso chiamando addPathTree dopo che è trascorso un tempo predefinito. Il tempo è tracciato usando a THREE.Clock oggetto. Aggiorniamo anche il Punto a meno che non si sia verificata una collisione.

Posizionamento degli alberi

Un gruppo di alberi è posizionato all'esterno della pista di rotolamento per creare il mondo usando il addWorldTrees metodo. Tutti gli alberi sono aggiunti come figlio di rollingGroundSphere così che si muovano anche quando ruotiamo la sfera. 

function addWorldTrees () var numTrees = 36; var gap = 6,28 / 36; per (var i = 0; i

Per piantare alberi del mondo, chiamiamo il addtree metodo passando valori attorno alla circonferenza della nostra sfera terrestre. Il sphericalHelper l'utilità ci aiuta a trovare la posizione sulla superficie di una sfera.

Per piantare alberi sul sentiero, faremo uso di un pool di alberi che vengono creati all'avvio usando il createTreesPool metodo. Abbiamo anche valori di angolo predefiniti per ogni percorso sulla sfera memorizzato nel pathAngleValues schieramento.

pathAngleValues ​​= [1.52,1.57,1.62]; // ... function createTreesPool () var maxTreesInPool = 10; var newTree; per (var i = 0; i0.5) lane = Math.floor (Math.random () * 2); addtree (vero, le opzioni [corsia]); 

Il addPathTree il metodo viene chiamato dall'aggiornamento quando è trascorso un tempo sufficiente dopo aver piantato l'ultimo albero. A sua volta chiama il addtree metodo mostrato in precedenza con un diverso set di parametri in cui l'albero viene posizionato nel percorso selezionato. Il doTreeLogic il metodo restituirà l'albero alla piscina quando non sarà visibile.

Interazione dell'utente

Stiamo aggiungendo un listener al documento per cercare eventi di tastiera rilevanti. Il handleKeyDown il metodo imposta il currentLane valore se i tasti freccia destra o sinistra sono premuti o imposta il bounceValue valore se viene premuta la freccia su.

document.onkeydown = handleKeyDown; // ... function handleKeyDown (keyEvent) if (jumping) return; var validMove = true; if (keyEvent.keyCode === 37) // left if (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; salto = true;  validMove = false;  if (validMove) jumping = true; bounceValue = 0.06; 

Nel aggiornare, il X la posizione della nostra palla di neve viene lentamente incrementata per raggiungere la currentLane posizionarsi lì cambiando corsia. 

Rilevamento collisione

Non c'è una vera fisica coinvolta in questo particolare gioco, sebbene potremmo usare vari framework fisici per il nostro scopo di rilevamento delle collisioni. Ma come ben sai, un motore fisico aggiunge un sacco di prestazioni generali al nostro gioco, e dovremmo sempre cercare di vedere se possiamo evitarlo. 

Nel nostro caso, calcoliamo semplicemente la distanza tra la nostra palla di neve e ogni albero per innescare una collisione se sono molto vicini. Questo succede nel doTreeLogic metodo, da cui viene chiamato aggiornare.

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

Come avrai notato, tutti gli alberi attualmente presenti nel nostro percorso sono memorizzati nel treesInPath array. Il doTreeLogic il metodo rimuove anche gli alberi dal display e nella piscina quando escono dalla nostra vista usando il codice mostrato sotto.

var treesToRemove = []; treesInPath.forEach (function (element, index) oneTree = treesInPath [index]; treePos.setFromMatrixPosition (oneTree.matrixWorld); if (treePos.z> 6 && oneTree.visible) // è uscito dagli alberi della view zoneToRemove.push (Un albero);  ); var fromhere; treesToRemove.forEach (function (element, index) oneTree = treesToRemove [index]; fromWhere = treesInPath.indexOf (oneTree); treesInPath.splice (fromWhere, 1); treesPool.push (oneTree); oneTree.visible = false; console .log ("remove tree"););

Conclusione

Creare un gioco 3D è un processo complicato se non si utilizza uno strumento visivo come Unity. Potrebbe sembrare intimidatorio o travolgente, ma ti assicuro che una volta capito, ti sentirai molto più potente e creativo. Vorrei che tu approfondissi ulteriormente utilizzando i vari quadri di fisica o sistemi di particelle o gli esempi ufficiali.