Opprette et enkelt 3D-fysikkspill ved hjelp av Three.js og Physijs

Hva du skal skape

I denne opplæringen lærer du hvordan du bruker Physi.js til å legge til spillfysikk til en 3D-scene opprettet ved hjelp av Three.js. Vi lager et enkelt spill der vi kjører en vogn rundt å samle gjenstander, ved hjelp av grunnleggende fysikkformer og fysikkbegrensninger.

Denne opplæringen vil bygge på toppen av konseptene som er delt i min forrige Three.js opplæring. Jeg vil be deg om å lese den hvis du er ny på Three.js og til sin 3D-sceneoppretting. 

På grunn av en teknisk begrensning i hosting webarbeiderbaserte løsninger på JSFiddle, kan vi ikke legge inn det interaktive spillet i denne opplæringssiden. Vennligst bruk den angitte kildekoden for å sjekke ut arbeidseksemplet på eventuelle skybaserte IDE-er eller ved selvstendig hosting.

1. 3D Fysikk på nettet

Det finnes flere rammer og motorer som for øyeblikket er tilgjengelige, som kan brukes til å lage 3D-innhold på nettet med fysikk. Noen av de som er verdt å nevne er Turbulenz, BabylonJS, PlayCanvas og den åpenbare Unity WebGL-bygningen. Men når det gjelder popularitet og brukervennlighet, liker de fleste å bruke Three.js for sine eksperimenter. Som Three.js er en gjengivelsesmotor og ikke har integrert fysikk, må vi utforske ytterligere rammer for å legge til fysikk evnen.

En veldig populær JavaScript fysikk løsning er Ammo.js, som er en direkte port av Bullet fysikk. Selv om det er mulig å bruke Ammo.js, er det ikke veldig nybegynnervennlig og har mye boilerplate-kode for hvert aspekt. Også, da det ikke er manuelt skrevet, men portet ved hjelp av Emscripten, er koden ikke lett å forstå. 

En alternativ løsning er å bruke Cannon.js eller Physijs. Det interessante med Physijs er at fokuset alltid er på å gjøre ting enklere, noe som gjør det til et ideelt valg for nybegynnere. Den er bygget basert på Ammo.js og har til og med en Cannon.js filial. Denne opplæringen bruker Physijs og Three.js til å bygge en fungerende spill prototype med fysikk evner. 

Et annet alternativ, selv om det ikke var forenklet, ville være å bruke Whitestorm-rammen, som er et komponentbasert rammeverk basert på Three.js og Physijs. 

2. Sette opp Physijs

Vi må ha ammo.js, physi.js, physijs_worker.js og three.js-filer i mappestrukturen eller kodemiljøet for å bruke Physijs. Physijs bruker en webarbeider til å bruke forskjellige tråder for fysikkberegninger. Så det første trinnet i integrasjonsprosessen er å sette opp nettarbeider som nedenfor.

Physijs.scripts.worker = 'lib / physijs_worker.js'; Physijs.scripts.ammo = 'ammo.js';

På dette tidspunktet er oppsettet fullført, og vi kan begynne å bruke fysikkrammen. Physijs har sørget for at den fulgte kodingsstilen til Three.js, og de fleste konseptene er enkle erstatninger av det tilsvarende Three.js-konseptet.

Grunnleggende trinn

I stedet for THREE.Scene, vi må bruke Physijs.Scene.

Det finnes flere masker tilgjengelig i Physijs som må brukes i stedet for THREE.Mesh. De tilgjengelige alternativene er PlaneMesh, BoxMesh, SphereMesh, CylinderMesh, ConeMesh, CapsuleMesh, ConvexMesh, ConcaveMesh, og HeighfieldMesh.

Vi må ringe scene.simulate metode for å gjøre fysikkberegningene enten i gjengi metode eller innenfor hyppige intervaller. La meg minne deg om at fysikkberegningene skjer i en annen tråd og vil ikke synkroniseres eller så fort som scenen gjengir loop. 

Selv det neste anropet til scene.simulate kan skje mens de tidligere beregningene fortsatt kjører. For å gjøre det riktig i samsvar med fysikkberegningene, kunne vi bruke Physijs-scenen Oppdater begivenhet.

scene.addEventListener ('update', funksjon () // din kode. fysikk beregninger har gjort oppdatering);

For å registrere en kollisjon på et Physijs mesh objekt kalt vilkårlig som cube, vi kan lytte til kollisjon begivenhet.

cube.addEventListener ('kollisjon', funksjon (objCollidedWith, linearVelOfCollision, angularVelOfCollision) );

Innenfor fremgangsmåten ovenfor, dette vil referere til cube, samtidig som objCollidedWith er objektet cube har kollidert med.

3. Eksempel Game Prototype

For denne opplæringen vil vi skape et veldig enkelt fysikkbasert spill hvor vi skal bruke fysikkbegrensninger for å skape et kjøretøy. Spilleren kan bruke piltastene til å kjøre bilen og samle en hoppende ball som tilfeldigvis vises i spillområdet. 

Interessant, Physijs har allerede en spesiell kjøretøy funksjon som kan brukes direkte til å skape biler, men vi vil ikke bruke den.

Spillverdenen

Vår spillverden er en stor bakke med vegger på sine fire sider som nedenfor.

Vi bruker Physijs.BoxMesh for bakken og de fire veggene som vist i koden nedenfor.

ground_material = Physijs.createMaterial (nytt THREE.MeshStandardMaterial (color: 0x00ff00), friksjon, .9 // lav restitusjon); // Ground ground = new Physijs.BoxMesh (new THREE.BoxGeometry (150, 1, 150), ground_material, 0 // masse); ground.receiveShadow = true; scene.add (bakken);

Legg merke til bruken av Physijs.createMaterial å skape de nødvendige fysikkmaterialene ved å sende en friksjonsverdi og en restitutionsverdi. Friksjonsverdien bestemmer grepet på bakken, og restitusjonsverdien bestemmer bouncinessen. En viktig ting å merke seg er at når vi gir en masseverdi på 0, Vi lager et stasjonært nettobjekt.

Kjøretøyet

Vi skal lage et spesielt kjøretøy som har to sammenhengende deler. Frontdelen, som har tre hjul, fungerer som motor, og den bakre delen, som har to hjul, vil fungere som en vogn. Vogndelen er koblet til motordelen ved hjelp av en hengselforbindelse implementert ved hjelp av en Physijs.HingeContraint

Hjulene bruker Physijs.DOFConstraint, som er en grad av frihet, som er begrenset til å være festet til kjøretøyets kropp, mens du beholder evnen til å rotere uavhengig. Jeg vil invitere deg til å lese den offisielle dokumentasjonen om de ulike begrensningene som er tilgjengelige i Physijs.

Motorkroppen og vognkroppen er enkle BoxMesh gjenstander som bakke vist ovenfor, men med en bestemt masseverdi. De er koblet til hverandre ved hjelp av en hengselskjøt, som vist i følgende kode. Et hengselforbindelse begrenser bevegelsen til det tilkoblede objektet som en vanlig dør.

car.carriage_constraint = ny Physijs.HingeConstraint (car.carriage, // Første objekt å være begrenset car.body, // begrenset til denne nye THREE.Vector3 (6, 0, 0), // på dette punktet nye THREE.Vector3 (0, 1, 0) // langs denne aksen); scene.addConstraint (car.carriage_constraint); car.carriage_constraint.setLimits (-Math.PI / 3, // minimum bevegelsesvinkel, i radianer Math.PI / 3, // maksimal bevegelsesvinkel, i radianer 0, // anvendt som en faktor for begrensningsfeil 0 / / kontroller sprette ved grense (0.0 == ingen sprette));

Den andre delen av koden gjelder grenser for hengslets rotasjon, som i dette tilfellet er mellom -Math.PI / 3 og Math.PI / 3

Hjulene bruker en frihetsbegrensning som kan brukes til å sette grenser på både lineær bevegelse og vinkelbevegelse i alle tre aksene. En metode addWheel er laget for å legge til hjul, som tar i flere parametere. 

Parametrene er hjulets posisjon, hjulets vekt, om hjulet er stort eller lite, og hjulreferanseobjektet. Metoden returnerer en nyopprettet DOFConstraint, som brukes til kjøring av kjøretøyet.

funksjon addWheel (hjul, pos, isBig, vekt) var geometri = wheel_geometry; hvis (isBig) geometri = big_wheel_geometry;  hjul = nye fysikalier. CylinderMesh (geometri, wheel_material, vekt); wheel.name = "kurv"; wheel.rotation.x = Math.PI / 2; wheel.position.set (pos.x, pos.y, pos.z); wheel.castShadow = true; scene.add (hjul); wheel.setDamping (0, demping); var wheelConstraint = ny Physijs.DOFConstraint (hjul, bil, kropp, pos); hvis (isBig) wheelConstraint = new Physijs.DOFConstraint (hjul, car.carriage, pos);  scene.addConstraint (wheelConstraint); wheelConstraint.setAngularLowerLimit (x: 0, y: 0, z: 0); wheelConstraint.setAngularUpperLimit (x: 0, y: 0, z: 0); returhjulConstraint; 

De store hjulene må festes til vognen, og de små hjulene er festet til motoren. Hjulene er gitt en dempingsverdi slik at deres vinkelhastighet blir redusert når ingen ekstern kraft blir påført. Dette sørger for at kjøretøyet senkes når vi slipper ut gasspedalen. 

Vi vil utforske kjørelogikken i et senere avsnitt. Hvert hjulmaske sammen med bilmaskene er tildelt navnet på kurven for identifikasjon under kollisjonen tilbake. Hvert hjulbegrensning har forskjellige vinkelgrenser, som settes uavhengig av hverandre når de er opprettet. For eksempel er her koden for motorkrossens fremre hjul, car.wheel_fm, og den tilsvarende begrensningen, car.wheel_fm_constraint.

car.wheel_fm_constraint = addWheel (car.wheel_fm, nytt 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);

Ballen

Ballen er a Physijs.SphereMesh objekt med en lavere masse verdi av 20. Vi bruker releaseBall Metode for å plassere ballen tilfeldig i vårt spillområde når det blir samlet. 

funksjon addBall () var ball_material = Physijs.createMaterial (nytt THREE.MeshStandardMaterial (color: 0x0000ff, shading: THREE.FlatShading), friksjon, .9 // god restitusjon); var ball_geometry = ny THREESphereGeometry (2,16,16); ball = ny Physijs.SphereMesh (ball_geometry, ball_material, 20); ball.castShadow = true; releaseBall (); scene.add (ball); ball.setDamping (0,0.9); ball.addEventListener ("kollisjon", onCollision);  funksjon releaseBall () var range = 10 + Math.random () * 30; ball.position.y = 16; ball.position.x = ((2 * Math.floor (Math.random () * 2)) - 1) * rekkevidde; ball.position.z = ((2 * Math.floor (Math.random () * 2)) - 1) * rekkevidde; ball .__ dirtyPosition = true; // force ny posisjon // Du må også avbryte objektets hastighet ball.setLinearVelocity (nytt THREE.Vector3 (0, 0, 0)); ball.setAngularVelocity (nytt THREE.Vector3 (0, 0, 0)); 

En ting verdt å merke seg er det faktum at vi må overstyre posisjonsverdiene som er satt av fysikk simuleringen for å plassere vår ball. For dette bruker vi __dirtyPosition flagg, som sørger for at den nye posisjonen brukes til videre fysikk simulering.

Ballen blir samlet når den kolliderer med hvilken som helst del av kjøretøyet som skjer i onCollision lytter metode.

funksjon onCollision (other_object, linear_velocity, angular_velocity) if (other_object.name === "cart") score ++; releaseBall (); scoreText.innerHTML = score.toString (); 

Kjører kjøretøyet

Vi legger til hendelselyttere for onkeydown og onkeyup hendelser i dokumentet, der vi bestemmer nøkkelkode å angi verdiene for de tilsvarende hjulbegrensningene. Teorien er at motorens eneste forhjul styrer vendingen av kjøretøyet, og de to hjulene på baksiden av motoren styr akselerasjon og retardasjon. Hjulene på vognen spiller ingen rolle i kjøringen.

document.onkeydown = handleKeyDown; document.onkeyup = handleKeyUp; funksjonshåndtakKeyDown (keyEvent) switch (keyEvent.keyCode) tilfelle 37: // Venstre car.wheel_fm_constraint.configureAngularMotor (1, -Math.PI / 3, Math.PI / 3, 1, 200); car.wheel_fm_constraint.enableAngularMotor (1); gå i stykker; tilfelle 39: // Høyre bil.wheel_fm_constraint.configureAngularMotor (1, -Math.PI / 3, Math.PI / 3, -1, 200); car.wheel_fm_constraint.enableAngularMotor (1); gå i stykker; tilfelle 38: // Opp bilen.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); gå i stykker; tilfelle 40: // Nedbil.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); gå i stykker;  funksjon handleKeyUp (keyEvent) switch (keyEvent.keyCode) tilfelle 37: // Venstre car.wheel_fm_constraint.disableAngularMotor (1); gå i stykker; sak 39: // Høyre bil.wheel_fm_constraint.disableAngularMotor (1); gå i stykker; tilfelle 38: // opp bil.wheel_bl_constraint.disableAngularMotor (2); car.wheel_br_constraint.disableAngularMotor (2); gå i stykker; tilfelle 40: // Ned bil.wheel_bl_constraint.disableAngularMotor (2); car.wheel_br_constraint.disableAngularMotor (2); gå i stykker; 

De DOFConstraint bruker enableAngularMotor metode for å påføre vinkelhastighet på hjulet som dreier hjulet basert på aksens verdi som er gitt som parameter. I utgangspunktet dreier vi bare hjulene, og bevegelsen av kjøretøyet skjer på grunn av friksjonsresponsen til bakken, som i den virkelige verden.

Vi kan ikke legge inn arbeidsspillet på denne siden som nevnt i starten av opplæringen. Vennligst gå gjennom hele kilden for å forstå hvordan alt er koblet sammen.

Konklusjon

Dette er en veldig enkel introduksjon til implementering av fysikk i 3D-verdenen din. Physys dokumentasjon mangler stort, men det finnes mange eksempler som er verdt å se på. Physijs er et veldig nybegynner-vennlig rammeverk, som Three.js, når du vurderer kompleksiteten som er til stede under hetten. 

JavaScript er tydelig populært for spillutvikling og webutvikling. Det er ikke uten sine lærekurver, og det er nok av rammer og biblioteker for å holde deg opptatt også. Hvis du leter etter flere ressurser for å studere eller bruke i arbeidet ditt, sjekk ut hva vi har tilgjengelig på Envato-markedet.

Håper denne veiledningen hjelper deg med å komme i gang med å utforske den interessante verdenen av 3D-webspillfysikk.