Kaip sukurti žaidimą su „Three.js“

Turėdamas nuostabų 3D atvaizdavimo galią, „WebGL“ verta apsvarstyti galimybę sukurti kitą žaidimą. Šiame straipsnyje nagrinėjama, ko reikia norint sukurti paprastą žaidimą naudojant nemokamus ir atvirojo šaltinio įrankius, skirtus „WebGL“. Tai anaiptol nėra visiškai žvilgsnis į „WebGL“ žaidimų kūrimą, tačiau tai turėtų paskatinti susimąstyti, kaip tai padaryti savarankiškai.

Apžiūrėsime vieno lygio automobilių lenktynių žaidimą. Taisyklės yra paprastos: kuo greičiau pasieki finišą ir nenutrenk. Pažvelkime, kaip tai daroma.





„WebGL“ lenktynių žaidimas: paprastas, bet sukeliantis priklausomybę. Išbandyk!

Plėtros sistemos pasirinkimas

Visi, kurie ketina kurti 3D programą su „WebGL“, žino, kad ji gali gana greitai komplikuotis. „WebGL“ yra galinga API, suteikianti „JavaScript“ kūrėjams tiesioginę prieigą prie grafikos procesoriaus (GPU). Tačiau ši galia kainuoja žemo lygio programavimo modeliu. Užuot piešę formos primityvus (pvz., Stačiakampius ir apskritimus) su susijusiais vaizdiniais atributais, pvz., Spalvomis ir gradientais, jūs manipuliuojate 3D viršūnių duomenų segmentais, nustatote pateikimo būsenas ir rašote mažus šešėlių kodo fragmentus į C tipo programavimo kalbą, vadinamą „OpenGL ES“. Šešėlinė kalba (GLSL ES). Vargu ar tai yra praktiško žiniatinklio kūrimo receptas - todėl dauguma programuotojų savo gyvenimui palengvinti naudoja vieną ar kelias sistemas, įrankių rinkinius ir bibliotekas.

Nors ten yra kelios atviro kodo „WebGL“ bibliotekos, kol kas populiariausios Trys.js . „Three.js“ 3D grafiką vaizduoja intuityviai, ją lengva naudoti, jos našumas yra geras ir ji yra gerai prižiūrima. Tikriausiai daugelis žinomesnių „WebGL“ demonstracinių versijų, kurias matėte, buvo sukurtos naudojant „Three.js“. Tai puiki vieta pradėti kurti paprastus žaidimus.



„Three.js“ turi atlikti užduotį: ištikimai nupiešti 3D objektus su dideliu našumu. Tai gerai atlieka tą darbą ir nenuklysta toli už savo misijos ribų. Daugumai programų viršuje turite pridėti kodo sluoksnius, kad galėtumėte prijungti „Three.js“ prie savo tinklalapio drobės elemento, pridėti DOM įvykių tvarkytuvų, išsiųsti įvykius į savo objektus ir pan. Jei pažvelgsite į daugybę pavyzdžių, pateikiamų su „Three.js“ šaltiniu, pamatysite, kad jie beveik visi dubliuoja tas pačias maždaug šimtą kodo eilučių, kad atliktų tuos dalykus ... ir ne itin objektyviai mada. Jei esate panašus į mane, pirmas dalykas, kurį atliksite prieš rašydami savo žaidimą, bus susidėti visas šias medžiagas į daugkartinio naudojimo klases. Aš tai padariau savo bibliotekoje „Sim.js“ - paprastoje „WebGL“ kūrimo simuliacijos sistemoje.

kada gimsta 5

„Three.js“ yra populiari „WebGL“ biblioteka su daugybe pavyzdžių

Be viso kodo, kuris sujungia „Three.js“ su tinklalapiu, pagrindinis „Sim.js“ uždavinys yra paleisti ciklą. Vykdymo ciklas yra nuolat vadinamas kiekvieną kartą, kai žaidimas yra pasirengęs dar kartą pateikti puslapio elementus. Vykdymo ciklo metu jūsų žaidimas atnaujins visus savo objektus ir perteiks sceną, kad atspindėtų šiuos pokyčius, kurie, pavyzdžiui, skatina animacijas ir reaguoja į vartotojo sąveiką.



Sim.App.prototype.run = function() { this.update(); this.renderer.render( this.scene, this.camera ); var that = this; requestAnimationFrame(function() { that.run(); }); }

Vykdymo ciklo raktas yra palyginti nauja naršyklės funkcija, vadinama requestAnimationFrame (). Naudodamasis šia funkcija, jūsų kodas užregistruoja atgalinį skambutį, kurį reikia iškviesti kiekvieną kartą, kai naršyklė yra pasirengusi piešti puslapį. Jei įmanoma, jūsų programos turėtų naudoti „requestAnimationFrame“ () vietoj tradiciškesnio „setTimeout“ (). Jis buvo sukurtas atsižvelgiant į atvaizdavimą, nes naršyklė žino, kad visi atgaliniai skambučiai, užregistruoti naudojant šią funkciją, yra skirti piešti ir ji gali pakuoti visus skambučius kartu su kitų puslapio vaizdinių elementų atnaujinimais.

„Sim.js“ vykdymo ciklas iškviečia programos atnaujinimo () metodą, kurį kūrėjas nepaiso, kad įgyvendintų žaidimo specifiką. Atlikę atnaujinimą, mes pateikiame sceną. Mūsų atvaizdavimo objektas yra Three.js klasė, vadinama THREE.WebGLRenderer. Perduodame jo pateikimo metodą - sceną, vaizduojančią trimačius objektus, kuriuos norime nupiešti, ir kamerą, apibrėžiančią tašką, iš kurio yra nupiešta scena. Po to, kai atliksime, mes užregistruosime savo paleidimo metodą, kad būtume vėl iškviesti kitą kartą requestAnimationFrame () yra paruoštas piešti.

Įdiegę šį pagrindinį kadravimą, turime savo žaidimo sukūrimo karkasą ir įvykio pompa, kuri jį atgaivins. Kažką nupiešime.

Piešimo grafika

„Three.js“ suteikia mums aukšto lygio 3D grafikos konstrukcijas, įskaitant galimybę pateikti primityvias figūras su šešėliais ir apšvietimo atributais, sutvarkyti savo sceną į objektų hierarchiją ir nurodyti kameros požiūrį scenai perteikti. Mūsų paprastame žaidime naudojami visi šie grafiniai elementai.

Menas 3D scenoje: paprastos žemės, dangaus ir apsauginių turėklų tekstūros žemėlapiai; sudėtingi 3D modeliai automobiliams ir kelio ženklai

Menas 3D scenoje: paprastos žemės, dangaus ir apsauginių turėklų tekstūros žemėlapiai; sudėtingi 3D modeliai automobiliams ir kelio ženklai

Išankstinio žaidimo kūrimo metu pastatiau meno krypties studiją. Šio tyrimo idėja yra užtikrinti, kad vaizdiniai elementai atitiktų nuoseklų meno krypties stilių. Žemei, dangui ir apsauginiams turėklams naudojau bitmap vaizdus, ​​kurie sukurti naudojant paprastas plokštumines formas.

Čia yra kodo fragmentas, naudojamas kuriant žaidimo aplinką, įdiegtas „JavaScript“ klasėje, vadinamoje „Aplinka“. Metodas „createGround ()“ sukuria objektą „Three.js“, vaizduojantį smėlio dykumos grindis ir įtraukiantį jį į 3D sceną:

Environment.prototype.createGround = function() { var texture = null; // Sand texture if (this.textureGround) { texture = THREE.ImageUtils.loadTexture('../images/Sand_002.jpg'); texture.wrapS = texture.wrapT = THREE.RepeatWrapping; texture.repeat.set(10, 10); } else { texture = null; } var ground = new THREE.Mesh( new THREE.PlaneGeometry( Environment.GROUND_WIDTH, Environment.GROUND_LENGTH ), new THREE.MeshBasicMaterial( { color: this.textureGround ? 0xffffff : 0xaaaaaa, ambient: 0x333333, map:texture } ) ); ground.rotation.x = -Math.PI/2; ground.position.y = -.02 + Environment.GROUND_Y; this.app.scene.add( ground ); this.ground = ground; }

Prieš kurdami gruntą, mes sukuriame tekstūrą (dar vadinamą tekstūros žemėlapiu), kuri bus pritaikyta jai. Tekstūros yra bitų žemėlapiai, naudojami 3D objekto paviršiui atspalvinti. Mes tai darome paskambinę THREE.ImageUtils.loadTexture (), perduodami jam URL į galiojantį HTML vaizdo failą (pvz., PNG arba JPEG formatu). Mes taip pat nustatėme keletą parametrų, kad „Three.js“ pasakytų, kaip „apvynioti“ tekstūrą aplink objektą. Mes norime, kad vaizdas kartotųsi kelis kartus visame geometrijos paviršiuje.

Tada mes sukuriame geometriją. „Three.js“ turi primityvų formos tipą, vadinamą „THREE.PlaneGeometry“, kuris vaizduoja 2D stačiakampį 3D erdvėje. Kad geometrija būtų pateikta, ji turi turėti medžiagą, kuri yra objektas, apibrėžiantis paviršiaus savybes, tokias kaip spalva ir minėtas tekstūros žemėlapis. Šiuo atveju medžiaga yra „THREE.MeshBasicMaterial“ tipo, kuri suteikia paviršiaus savybes be jokio apšvietimo, naudojant tik pateiktas spalvas ir tekstūras. Mes perduodame anksčiau sukurtą tekstūrą kaip medžiagos žemėlapio parametrą. Kad geometrija būtų įdėta į sceną, turime ją kartu su medžiaga įdėti į objektą „Three.js“, vadinamą tinklu, kuris yra „THREE.Mesh“ tipas.

Prieš įtraukdami savo naujai sukurtą tinklelį į sceną, mes padarysime dar vieną dalyką. Pagal numatytuosius nustatymus „THREE.PlaneGeometry“ brėžiama nukreipta iš ekrano žiūrovo link, ty xy plokštumoje ties z = 0. Turime pasukti tai nuo žiūrovo 90 laipsnių kampu, nustatydami jo sukimosi.x savybę į neigiamą 90 laipsnių. Three.js (ir WebGL) laipsniais nurodomi radianai. Mes taip pat norime numušti žemę šiek tiek žemiau asfaltuoto kelio, todėl mes kompensuojame poziciją.y tik šiek tiek neigiama linkme.

Žemės tekstūros žemėlapis. Tekstūros yra taškai, kurie yra taškai, naudojami 3D objekto paviršiui atspalvinti

Žemės tekstūros žemėlapis. Tekstūros yra taškai, kurie yra taškai, naudojami 3D objekto paviršiui atspalvinti

Galiausiai mes esame pasirengę pridėti tinklelį kaip savo aukščiausio lygio scenos vaiką, paskambinę programos scenos objekto metodu add () (this.app.scene.add ()). Panašiu būdu žaidimas sukuria asfaltuotą kelią, atitvarus, dangaus foną ir finišo linijos ženklą naudodamas paprastas plokštumas ir pagrindines medžiagas su faktūromis.

Modelių importavimas iš 3D paketų

Dabar mūsų lenktynių žaidimas būtų gana nuobodus, jei grafiką sudarytų tik iš tekstūros atvaizduoti 2D lėktuvai. Norint suteikti tikroviškumo, mums reikia gražiai atrodančių automobilių ir dekoratyvinių elementų, tokių kaip kelio ženklai.

Nesu profesionalus 3D modeliuotojas, todėl prisijungiau prie interneto, norėdamas sužinoti, ar galėčiau rasti tinkamų ir prieinamų modelių, kuriuos galėčiau naudoti. Aš esu TurboSquid , paslauga, leidžianti įkelti ir atsisiųsti 3D modelius, sukurtus įvairiuose profesionaliuose paketuose. Kai kurie „TurboSquid“ modeliai yra nemokami, kiti - palyginti nebrangūs. Man pavyko rasti gerą „pagrindinio veikėjo“ automobilio modelį „Nissan GTR“. Taip pat radau porą kitų automobilių tipų ir tikrai puikų maršruto ženklo, skirto istoriniam Kalifornijos 66-ajam keliui, modelį.

Šiam projektui naudojame modelius, saugomus populiariame „Wavefront“ OBJ formate (.obj failo plėtinys). „Three.js“ yra su „Python“ parašytu komandinės eilutės įrankiu OBJ failams konvertuoti. Kiekvieną failą iš OBJ konvertuojau į Three.js formatą naudodamas komandų eilutę, kaip nurodyta toliau:

python /utils/exporters/convert_obj_three.py -i . obj -o .js

Kai modeliai bus konvertuoti, galėsime juos įkelti į žaidimą naudodami „THREE.JSONLoader“:

var that = this; var loader = new THREE.JSONLoader(); loader.load( url, function( data ) { that.handleLoaded(data) } );

„THREE.JSONLoader“ iškvies mūsų atgalinio skambinimo funkciją „handLoaded ()“, kai JSON failas bus atsisiųstas ir 3D duomenys bus išanalizuoti. „handLoaded“) prideda naujai įkeltą 3D modelį (grąžintą duomenyse) į sceną.

Modelio peržiūros priemonė naudojama modeliams išbandyti prieš įkeliant į žaidimą

Modelio peržiūros priemonė naudojama modeliams išbandyti prieš įkeliant į žaidimą

Animuoti sceną

Kad mūsų žaidimas turėtų gyvybę, turime mokėti animuoti scenos objektus. Automobilis turėtų atšokti nuo atitvaros, jei ji per arti, ir sudužti, jei atsitrenktų į kitą automobilį. Taip pat norime animuoti keletą aplinkos elementų: lėtai judantį dangų fone ir greitai judantį kelią, kad sukurtume greičio iliuziją.

Pažvelkime, kaip atgaivinti automobilio avariją. Mes naudosime pagrindinį animacijos stilių, vadinamą klavišų rėmelių animacija arba klavišų rėminimu. Raktų rėminimas naudoja klavišų masyvą - animacijos laiko reikšmes tarp nulio (animacijos pradžia) ir vieną (animacijos pabaiga) - ir vertes, skirtas animacijai, tokias kaip objekto padėtis ir pasukimas. Rėminant raktus, reikšmės pateikiamos tik pagrindiniuose taškuose, o ne kiekviename animacijos kadre. Tarpinės vertės apskaičiuojamos arba interpoliuojamos kaip linijinė bet kurio rakto ir paskesnio rakto delta funkcija.

spartusis klavišas kopijuoti sluoksnį Photoshop

„Three.js“ turi pagrindines rėmelių animacijos klases, įtrauktas į biblioteką, bet man pasirodė, kad jas šiek tiek sunku naudoti paprastoms animacijos užduotims atlikti. Aš parašiau savo mažą raktų rėmelio įrankį.

Čia pateikiamas animacinis kodas iš žaidimo automobilio klasės.

Car.prototype.createCrashAnimation = function() { this.crashAnimator = new Sim.KeyFrameAnimator; this.crashAnimator.init({ interps: [ { keys:Car.crashPositionKeys, values:Car.crashPositionValues, target:this.mesh.position }, { keys:Car.crashRotationKeys, values:Car.crashRotationValues, target:this.mesh.rotation } ], loop: false, duration:Car.crash_animation_time }); this.addChild(this.crashAnimator); this.crashAnimator.subscribe('complete', this, this.onCrashAnimation Complete); } Car.prototype.animateCrash = function(on) { if (on) { this.crashAnimator.start(); } else { this.crashAnimator.stop(); } } Car.crashPositionKeys = [0, .25, .75, 1]; Car.crashPositionValues = [ { x : -1, y: 0, z : 0}, { x: 0, y: 1, z: -1}, { x: 1, y: 0, z: -5}, { x : -1, y: 0, z : -2} ]; Car.crashRotationKeys = [0, .25, .5, .75, 1]; Car.crashRotationValues = [ { z: 0, y: 0 }, { z: Math.PI, y: 0}, { z: Math.PI * 2, y: 0}, { z: Math.PI * 2, y: Math.PI}, { z: Math.PI * 2, y: Math.PI * 2}, ]; Car.crash_animation_time = 2000;

Pirmiausia „createCrashAnimation“ () nustato pagrindinį rėmą naudodamas pagalbininkų klasę „Sim“. „KeyFrameAnimator“ inicijuojant automobilio padėties ir sukimosi interpoliacijos raktus ir reikšmes („crashPositionKeys“, „crashPositionValues“, „crashRotationKeys“ ir „crashRotationValues“ ypatybės). Kiekvienas pateikimo ciklas, Sim. „KeyFrameAnimator“ atnaujina tikslinio objekto padėtį ir sukimosi vertes, todėl automobilis sukasi ir juda per kosmosą (bendras efektas yra tas, kad automobilis blaškosi erdvėje). Animacija vyksta per dvi sekundes, kaip nurodyta trukmės parametre naudojamoje ypatybėje crash_animation_time. Animacija suveikia, kai žaidimo variklis nustato automobilio žaidėjo ir ne žaidėjo automobilio susidūrimą, iškviesdamas Car.animateCrash ().

Animacinis griūvančios automobilio avarijos atvejis naudojant pagrindinio rėmo animaciją

Animacinis griūvančios automobilio avarijos atvejis naudojant pagrindinio rėmo animaciją

Judančiam dangui ir keliui animuoti naudojame kitą techniką. Galų gale animacija tiesiog reiškia objekto vertybių keitimą laikui bėgant. Rakto rėminimas yra vienas iš būdų tai padaryti. Kitas būdas yra tiesiog atnaujinti nuosavybės vertes kiekvieną atnaujinimo ciklą. Kiekviename „Sim.js“ objekte gali būti atnaujinimo () metodas, kuris kiekvieną kartą iškviečiamas per programos vykdymo ciklą. Grįždami į Aplinkos klasę matome, kad jos atnaujinimo () metodas naudojamas judančiam dangui ir keliui animuoti:

Environment.prototype.update = function() { if (this.textureSky) { this.sky.material.map.offset.x += 0.00005; } if (this.app.running) { var now = Date.now(); var deltat = now - this.curTime; this.curTime = now; dist = -deltat / 1000 * this.app.player.speed; this.road.material.map.offset.y += (dist * Environment.ANIMATE_ ROAD_FACTOR); } Sim.Object.prototype.update.call(this); }

Apgaulė čia yra nuolat atnaujinti tekstūros žemėlapio poslinkio ypatybę. „Three.js“ naudoja poslinkį tekstūros žemėlapiui uždėti ant objekto paviršiaus. Nulinio poslinkio reikšmė x ir y juda tekstūra, todėl šios ypatybės animacija laikui bėgant ją „slenka“ atitinkamai kairėn / dešinėn ir aukštyn / žemyn.

eidamas į meno mokyklą po koledžo

Elgesio ir sąveikos pridėjimas

Dabar, kai turime pilną sceną, tinkamos išvaizdos automobilį ir keletą animacinių dekoracijų, kad būtų įdomu, atėjo laikas parašyti žaidimo variklį ir valdiklius. Variklis yra gana paprastas: jis išbando susidūrimus ir galines sąlygas. Pirma, susidūrimo bandymas:

RacingGame.prototype.testCollision = function() { var playerpos = this.player.object3D.position; if (playerpos.x > (Environment.ROAD_WIDTH / 2 - (Car.CAR_ WIDTH/2))) { this.player.bounce(); this.player.object3D.position.x -= 1; } if (playerpos.x < -(Environment.ROAD_WIDTH / 2 - (Car.CAR_WIDTH/2))) { this.player.bounce(); this.player.object3D.position.x += 1; } var i, len = this.cars.length; for (i = 0; i < len; i++) { var carpos = this.cars[i].object3D.position; var dist = playerpos.distanceTo(carpos); if (dist < RacingGame.COLLIDE_RADIUS) { this.crash(this.cars[i]); break; } } }

Greičiui variklis naudoja paprastą 2D skaičiavimą, kad išbandytų susidūrimą su viena iš apsauginių turėklų. Patikriname automobilio x ašies (horizontalią) padėtį nuo kelio kraštų. Labiau susiduriama su susidūrimu su kitu automobiliu, todėl turime žinoti, ar žaidėjo automobilis yra tam tikrame atstume. Objektas „Three.js Vector3“ pateikia „distanceTo“ () metodą, kuris jį apskaičiuoja mums.

Automobilis kaip

Automobilis kaip „žaidėjo“ personažas. Klaviatūros klavišai vairuoja automobilį ir fotoaparatas automatiškai seka paskui

Aptikus susidūrimą, mes paleidžiame atitinkamą atsakymą: atšokimą nuo apsauginių turėklų arba žaidimo užbaigimą avarija. Dabar mes išbandome galutines sąlygas. Arba jau sutrenkėme žaidėjo automobilį su kitu automobiliu, arba patekome į finišo tiesiąją ir laimėjome. Žr. Žemiau pateiktus avarijos () ir „finishGame“ () metodus:

RacingGame.prototype.crash = function(car) { this.player.crash(); car.crash(); this.running = false; this.state = RacingGame.STATE_CRASHED; this.showResults(); } RacingGame.prototype.finishGame = function() { this.running = false; this.player.stop(); var i, len = this.cars.length; for (i = 0; i < len; i++) { this.cars[i].stop(); } this.state = RacingGame.STATE_COMPLETE; this.showResults(); }

Fotoaparato atnaujinimas

Kameras, kurį trumpai palietėme anksčiau, bet dar išsamiai nepateikėme, yra kamera. „Three.js“ pateikia fotoaparato objektus, apibrėžiančius 3D scenos taškus. Šiame žaidime naudojame trečiojo asmens požiūrį, tai yra, kamera visada žvelgia per mūsų „personažo“, kuris šiuo atveju yra automobilis, „petį“. Kai automobilis juda atsakydamas į klaviatūrą, turime išlaikyti kameros vaizdą, palyginti su ja. Mūsų žaidėjo objekto „updateCamera“) metodas tuo rūpinasi:

Player.prototype.updateCamera = function() { var camerapos = new THREE.Vector3(Player.CAMERA_OFFSET_X, Player.CAMERA_OFFSET_Y, Player.CAMERA_OFFSET_Z); camerapos.addSelf(this.object3D.position); this.camera.position.copy(camerapos); this.camera.lookAt(this.object3D.position);

Kaip ir grafinis objektas scenoje, „Three.js“ kamera turi padėties ir pasukimo savybes, kuriomis galima manipuliuoti. Programoje „updateCamera“ () sukuriame naują objektą „THREE.Vector3“, kuris inicijuojamas su poslinkio verte. Tada mes pridedame šią vertę prie žaidėjo automobilio padėties ir nukopijuojame sumą į pačios fotoaparato pozicijos ypatybę, perkeldami fotoaparatą į tą naują padėtį. Bet mes nebaigėme. Norime įsitikinti, kad kamera atrodo tinkamoje vietoje. Mes vadiname specialų fotoaparato objekto metodą „lookAt“ (), kuris nukreipia kamerą į objekto padėtį. Todėl kamera visada yra fiksuoto atstumo ir orientacijos, palyginti su žaidėjo automobiliu.

Žaidimo 2D vartotojo sąsajos elementai yra rezultatų perdanga ir „heads-up“ ekranas

Tik pradžia

Kurdami šį paprastą žaidimą, mes subraižėme žaidimų kūrimo paviršių tik naudodami „WebGL“. Vis dėlto aptarėme keletą esminių temų: bėgimo ciklas; piešti grafiką su „Three.js“; modelių importavimas iš 3D paketų; scenos animacija paprastais pagrindiniais rėmeliais ir procedūrinės tekstūros atnaujinimu; elgesio ir sąveikos, įskaitant susidūrimus ir fotoaparato judėjimą, kūrimas. Šios temos atspindi labai didelio ledkalnio viršūnę. Aš primygtinai raginu jus ištirti, kas slypi po juo. Laimingo kodavimo!

Žodžiai : Tonis Paryžius

Šis straipsnis iš pradžių pasirodė tinklinis žurnalas 241 leidimas.

Patiko tai? Perskaitykite tai!

Turite klausimą? Klauskite komentaruose!