In questo tutorial imparerai come usare Physi.js per aggiungere fisica di gioco a una scena 3D creata utilizzando Three.js. Creeremo un semplice gioco in cui guidiamo un carrello attorno alla raccolta di oggetti, utilizzando forme fisiche di base e vincoli fisici.
Questo tutorial si baserà sui concetti condivisi nel mio precedente tutorial su Three.js. Ti chiederei di leggerlo se sei nuovo di Three.js e della sua creazione di scene 3D.
A causa di una limitazione tecnica nell'hosting di soluzioni basate su web worker su JSFiddle, non siamo in grado di incorporare il gioco interattivo in questa pagina di tutorial. Utilizzare il codice sorgente fornito per verificare l'esempio di lavoro su qualsiasi IDE basato su cloud o self-hosting.
Ci sono più framework e motori attualmente disponibili che possono essere usati per creare contenuti 3D per il web con la fisica. Alcuni di quelli degni di nota sono Turbulenz, BabylonJS, PlayCanvas e l'evidente build Unity WebGL. Ma quando si tratta di popolarità e facilità d'uso, molte persone amano usare Three.js per i loro esperimenti. Poiché Three.js è un motore di rendering e non ha una fisica integrata, abbiamo bisogno di esplorare ulteriori framework per aggiungere la capacità fisica.
Una soluzione fisica JavaScript molto popolare è Ammo.js, che è una porta diretta della fisica di Bullet. Anche se è possibile utilizzare direttamente Ammo.js, non è molto adatto ai principianti e ha un sacco di codice per ogni aspetto. Inoltre, poiché non è stato scritto manualmente ma è stato portato su Emscripten, il codice non è facile da capire.
Una soluzione alternativa è usare Cannon.js o Physijs. La cosa interessante di Physijs è che l'attenzione è sempre rivolta a semplificare le cose, il che la rende la scelta ideale per i principianti. È basato su Ammo.js e ha anche un ramo Cannon.js. Questo tutorial usa Physijs e Three.js per costruire un prototipo di gioco funzionante con funzionalità fisiche.
Un'altra opzione, sebbene semplificata, sarebbe quella di usare il framework Whitestorm, che è un framework basato su componenti basato su Three.js e Physijs.
Abbiamo bisogno di avere i file ammo.js, physi.js, physijs_worker.js e three.js all'interno della nostra struttura di cartelle o dell'ambiente di codifica per usare Physijs. Physijs utilizza un web worker per utilizzare diversi thread per i calcoli fisici. Quindi il primo passo nel processo di integrazione è impostare il web worker come sotto.
Physijs.scripts.worker = 'lib / physijs_worker.js'; Physijs.scripts.ammo = 'ammo.js';
A questo punto, l'installazione è completa e possiamo iniziare a utilizzare la struttura fisica. Physijs ha fatto in modo di seguire lo stile di codifica di Three.js e la maggior parte dei concetti sono semplici sostituzioni del corrispondente concetto Three.js.
Invece di THREE.Scene
, dobbiamo usare Physijs.Scene
.
Ci sono più mesh disponibili in Physijs che devono essere usate al posto di THREE.Mesh
. Le opzioni disponibili sono PlaneMesh
, BoxMesh
, SphereMesh
, CylinderMesh
, ConeMesh
, CapsuleMesh
, ConvexMesh
, ConcaveMesh
, e HeighfieldMesh
.
Dobbiamo chiamare il scene.simulate
metodo per fare i calcoli fisici nel rendere
metodo o entro intervalli frequenti. Permettetemi di ricordarvi che i calcoli fisici avvengono in un thread diverso e non saranno sincronizzati o veloci come il ciclo di rendering delle scene.
Anche la prossima chiamata a scene.simulate
può accadere mentre i calcoli precedenti sono ancora in esecuzione. Per farlo correttamente in sincronia con i calcoli fisici, potremmo usare la scena di Physijs aggiornare
evento.
scene.addEventListener ('update', function () // il tuo codice. i calcoli fisici hanno fatto l'aggiornamento);
Per registrare una collisione su un oggetto mesh Physijs denominato arbitrariamente come cubo
, possiamo ascoltare il collisione
evento.
cube.addEventListener ('collision', function (objCollidedWith, linearVelOfCollision, angularVelOfCollision) );
All'interno del metodo sopra, Questo
farà riferimento a cubo
, mentre objCollidedWith
è l'oggetto cubo
è entrato in collisione con.
Per questo tutorial, creeremo un semplice gioco basato sulla fisica in cui utilizzeremo i vincoli della fisica per creare un veicolo. Il giocatore può usare i tasti freccia per guidare il veicolo e raccogliere una palla che rimbalza che appare casualmente nell'area di gioco.
È interessante notare che Physijs ha già uno speciale veicolo
funzionalità che può essere utilizzata direttamente per la creazione di veicoli, ma non la useremo.
Il nostro mondo di gioco è un vasto terreno con pareti sui quattro lati come sotto.
Noi usiamo Physijs.BoxMesh
per il terreno e le quattro pareti come mostrato nel codice qui sotto.
ground_material = Physijs.createMaterial (new THREE.MeshStandardMaterial (color: 0x00ff00), attrito, .9 // bassa restituzione); // Ground ground = new Physijs.BoxMesh (new THREE.BoxGeometry (150, 1, 150), ground_material, 0 // massa); ground.receiveShadow = true; scene.add (terra);
Notare l'uso di Physijs.createMaterial
creare i materiali fisici necessari passando un valore di attrito e un valore di restituzione. Il valore di attrito determina la presa sul terreno e il valore di restituzione determina la forza di rimbalzo. Una cosa importante da notare è che quando forniamo un valore di massa di 0
, creiamo un oggetto mesh stazionario.
Creeremo un veicolo speciale con due parti collegate. La parte anteriore, che ha tre ruote, funge da motore, e la parte posteriore, con due ruote, fungerà da carrello. La parte del carrello è collegata alla parte del motore usando un giunto a cerniera implementato usando un Physijs.HingeContraint
.
Le ruote usano Physijs.DOFConstraint
, che è un grado di vincolo di libertà da attaccare al corpo del veicolo pur mantenendo la capacità di ruotare indipendentemente. Vi invito a leggere la documentazione ufficiale sui vari vincoli disponibili in Physijs.
Il corpo motore e il corpo del carrello sono semplici BoxMesh
oggetti come il terra
mostrato sopra, ma con un valore di massa definito. Sono collegati tra loro usando un giunto a cerniera, come mostrato nel seguente codice. Un giunto a cerniera limita il movimento dell'oggetto collegato come quello di una porta normale.
car.carriage_constraint = new Physijs.HingeConstraint (car.carriage, // Primo oggetto da vincolare car.body, // vincolato a questo nuovo THREE.Vector3 (6, 0, 0), // a questo punto new THREE.Vector3 (0, 1, 0) // lungo questo asse); scene.addConstraint (car.carriage_constraint); car.carriage_constraint.setLimits (-Math.PI / 3, // angolo minimo di movimento, in radianti Math.PI / 3, // angolo massimo di movimento, in radianti 0, // applicato come fattore all'errore di vincolo 0 / / controlla il rimbalzo al limite (0.0 == nessun rimbalzo));
La seconda parte del codice applica i limiti alla rotazione della cerniera, che in questo caso è compresa tra -Math.PI / 3
e Math.PI / 3
.
Le ruote utilizzano un vincolo di grado di libertà che può essere utilizzato per impostare i limiti sul movimento lineare e sul movimento angolare in tutti e tre gli assi. Un metodo addWheel
è stato creato per aggiungere ruote, che contiene più parametri.
I parametri sono la posizione della ruota, il peso della ruota, se la ruota è grande o piccola, e l'oggetto di riferimento della ruota. Il metodo restituisce un nuovo creato DOFConstraint
, che viene utilizzato per guidare il veicolo.
function addWheel (wheel, pos, isBig, weight) var geometry = wheel_geometry; if (isBig) geometry = big_wheel_geometry; wheel = new Physijs.CylinderMesh (geometry, wheel_material, weight); wheel.name = "carrello"; wheel.rotation.x = Math.PI / 2; wheel.position.set (pos.x, pos.y, pos.z); wheel.castShadow = true; scene.add (ruota); wheel.setDamping (0, smorzamento); var wheelConstraint = new Physijs.DOFConstraint (wheel, car.body, pos); if (isBig) wheelConstraint = new Physijs.DOFConstraint (wheel, car.carriage, pos); scene.addConstraint (wheelConstraint); wheelConstraint.setAngularLowerLimit (x: 0, y: 0, z: 0); wheelConstraint.setAngularUpperLimit (x: 0, y: 0, z: 0); return wheelConstraint;
Le grandi ruote devono essere fissate al carrello e le piccole ruote sono fissate al motore. Alle ruote viene assegnato un valore di smorzamento in modo che la loro velocità angolare venga ridotta quando non viene applicata alcuna forza esterna. Questo assicura che il veicolo rallenti quando rilasciamo l'acceleratore.
Esploreremo la logica di guida in una sezione successiva. A ciascuna ruota in mesh insieme alle maglie della macchina viene assegnato il nome di carrello
a scopo di identificazione durante la chiamata di collisione. Ogni vincolo di ruota ha diversi limiti angolari, che vengono impostati indipendentemente una volta creati. Ad esempio, ecco il codice per la ruota centrale anteriore del motore, car.wheel_fm
, e il vincolo corrispondente, car.wheel_fm_constraint
.
car.wheel_fm_constraint = addWheel (car.wheel_fm, new THREE.Vector3 (-7.5, 6.5, 0), false, 300); car.wheel_fm_constraint.setAngularLowerLimit (x: 0, y: -Math.PI / 8, z: 1); car.wheel_fm_constraint.setAngularUpperLimit (x: 0, y: Math.PI / 8, z: 0);
La palla è a Physijs.SphereMesh
oggetto con un valore di massa inferiore di 20
. Noi usiamo il releaseBall
metodo per posizionare la palla in modo casuale all'interno della nostra area di gioco ogni volta che viene raccolta.
function addBall () var ball_material = Physijs.createMaterial (new THREE.MeshStandardMaterial (color: 0x0000ff, shading: THREE.FlatShading), attrito, .9 // buona restituzione); var ball_geometry = new THREE.SphereGeometry (2,16,16); palla = nuovo Physijs.SphereMesh (ball_geometry, ball_material, 20); ball.castShadow = true; releaseBall (); scene.add (palla); ball.setDamping (0,0.9); ball.addEventListener ('collision', onCollision); function releaseBall () var range = 10 + Math.random () * 30; ball.position.y = 16; ball.position.x = ((2 * Math.floor (Math.random () * 2)) - 1) * range; ball.position.z = ((2 * Math.floor (Math.random () * 2)) - 1) * range; ball .__ dirtyPosition = true; // forza nuova posizione // Devi anche annullare la velocità dell'oggetto ball.setLinearVelocity (new THREE.Vector3 (0, 0, 0)); ball.setAngularVelocity (new THREE.Vector3 (0, 0, 0));
Una cosa che vale la pena di notare è il fatto che abbiamo bisogno di sovrascrivere i valori di posizione impostati dalla simulazione fisica per riposizionare la nostra palla. Per questo, usiamo il __dirtyPosition
flag, che assicura che la nuova posizione venga utilizzata per ulteriori simulazioni di fisica.
La palla viene raccolta quando entra in collisione con qualsiasi parte del veicolo che si trova nel onCollision
metodo dell'ascoltatore.
function onCollision (other_object, linear_velocity, angular_velocity) if (other_object.name === "cart") score ++; releaseBall (); scoreText.innerHTML = score.toString ();
Aggiungiamo listener di eventi per il onKeyDown
e onKeyUp
eventi del documento, dove determiniamo il chiave
per impostare i valori dei vincoli di ruota corrispondenti. La teoria è che la singola ruota anteriore del motore controlla la rotazione del nostro veicolo, e le due ruote sul retro del motore controllano l'accelerazione e la decelerazione. Le ruote sul carrello non giocano alcun ruolo nella guida.
document.onkeydown = handleKeyDown; document.onkeyup = handleKeyUp; function handleKeyDown (keyEvent) switch (keyEvent.keyCode) case 37: // Left car.wheel_fm_constraint.configureAngularMotor (1, -Math.PI / 3, Math.PI / 3, 1, 200); car.wheel_fm_constraint.enableAngularMotor (1); rompere; case 39: // right car.wheel_fm_constraint.configureAngularMotor (1, -Math.PI / 3, Math.PI / 3, -1, 200); car.wheel_fm_constraint.enableAngularMotor (1); rompere; case 38: // Risali car.wheel_bl_constraint.configureAngularMotor (2, 1, 0, 6, 2000); car.wheel_br_constraint.configureAngularMotor (2, 1, 0, 6, 2000); car.wheel_bl_constraint.enableAngularMotor (2); car.wheel_br_constraint.enableAngularMotor (2); rompere; caso 40: // Giù car.wheel_bl_constraint.configureAngularMotor (2, 1, 0, -6, 2000); car.wheel_br_constraint.configureAngularMotor (2, 1, 0, -6, 2000); car.wheel_bl_constraint.enableAngularMotor (2); car.wheel_br_constraint.enableAngularMotor (2); rompere; function handleKeyUp (keyEvent) switch (keyEvent.keyCode) case 37: // Left car.wheel_fm_constraint.disableAngularMotor (1); rompere; case 39: // right car.wheel_fm_constraint.disableAngularMotor (1); rompere; caso 38: // Risolto car.wheel_bl_constraint.disableAngularMotor (2); car.wheel_br_constraint.disableAngularMotor (2); rompere; caso 40: // Giù car.wheel_bl_constraint.disableAngularMotor (2); car.wheel_br_constraint.disableAngularMotor (2); rompere;
Il DOFConstraint
usa il enableAngularMotor
metodo per applicare la velocità angolare sulla ruota che ruota la ruota in base al valore dell'asse fornito come parametro. Fondamentalmente, giriamo solo le ruote e il movimento del veicolo avviene a causa della risposta di attrito del terreno, come nel mondo reale.
Non siamo in grado di incorporare il gioco di lavoro in questa pagina come menzionato all'inizio del tutorial. Si prega di consultare la fonte completa per capire come tutto è cablato insieme.
Questa è un'introduzione molto semplice all'implementazione della fisica nel mondo 3D di Three.js. La documentazione di Physijs è gravemente carente, ma ci sono molti esempi già disponibili che vale la pena esaminare. Physijs è un framework molto adatto ai principianti, come Three.js, quando si considera la complessità che è presente sotto il cofano.
JavaScript è chiaramente popolare per lo sviluppo di giochi e per lo sviluppo web. Non è senza le sue curve di apprendimento, e ci sono un sacco di quadri e librerie per tenerti occupato, pure. Se stai cercando ulteriori risorse da studiare o da utilizzare nel tuo lavoro, dai un'occhiata a ciò che abbiamo a disposizione sul mercato Envato.
Spero che questo tutorial ti aiuti a iniziare a esplorare il mondo interessante della fisica dei giochi Web 3D.