Cara Membuat Game Endless Runner Dalam Virtual Reality (Bagian 2)
Diterbitkan: 2022-03-10Di Bagian 1 seri ini, kita telah melihat bagaimana model realitas virtual dengan efek pencahayaan dan animasi dapat dibuat. Di bagian ini, kami akan menerapkan logika inti game dan memanfaatkan manipulasi lingkungan A-Frame yang lebih canggih untuk membangun bagian "permainan" dari aplikasi ini. Pada akhirnya, Anda akan memiliki game realitas virtual yang berfungsi dengan tantangan nyata.
Tutorial ini melibatkan sejumlah langkah, termasuk (namun tidak terbatas pada) deteksi tabrakan dan lebih banyak konsep A-Frame seperti mixin.
- Demo produk akhir
Prasyarat
Sama seperti di tutorial sebelumnya, Anda memerlukan yang berikut ini:
- Akses internet (khusus untuk glitch.com);
- Sebuah proyek Glitch selesai dari bagian 1. (Anda dapat melanjutkan dari produk jadi dengan menavigasi ke https://glitch.com/edit/#!/ergo-1 dan mengklik "Remix untuk mengedit";
- Headset realitas virtual (opsional, disarankan). (Saya menggunakan Google Cardboard, yang ditawarkan seharga $15 per buah.)
Langkah 1: Merancang Hambatan
Pada langkah ini, Anda mendesain pohon yang akan kita gunakan sebagai penghalang. Kemudian, Anda akan menambahkan animasi sederhana yang menggerakkan pohon ke arah pemain, seperti berikut:
Pohon-pohon ini akan berfungsi sebagai templat untuk rintangan yang Anda hasilkan selama permainan. Untuk bagian terakhir dari langkah ini, kami kemudian akan menghapus "pohon template" ini.
Untuk memulai, tambahkan sejumlah mixin A-Frame yang berbeda . Mixin adalah kumpulan properti komponen yang umum digunakan. Dalam kasus kami, semua pohon kami akan memiliki warna, tinggi, lebar, kedalaman yang sama, dll. Dengan kata lain, semua pohon Anda akan terlihat sama dan oleh karena itu akan menggunakan beberapa mixin bersama.
Catatan : Dalam tutorial kami, satu-satunya aset Anda adalah mixin. Kunjungi halaman Mixin A-Frame untuk mempelajari lebih lanjut.
Di editor Anda, navigasikan ke index.html . Tepat setelah langit Anda dan sebelum lampu Anda, tambahkan entitas A-Frame baru untuk menampung aset Anda:
<a-sky...></a-sky> <!-- Mixins --> <a-assets> </a-assets> <!-- Lights --> ...
Di entitas a-assets
baru Anda, mulailah dengan menambahkan mixin untuk dedaunan Anda. Mixin ini mendefinisikan properti umum untuk dedaunan pohon templat. Singkatnya, ini adalah piramida putih berbayang datar, untuk efek poli rendah.
<a-assets> <a-mixin geometry=" primitive: cone; segments-height: 1; segments-radial:4; radius-bottom:0.3;" material="color:white;flat-shading: true;"></a-mixin> </a-assets>
Tepat di bawah mixin dedaunan Anda, tambahkan mixin untuk batangnya. Batang ini akan menjadi prisma persegi panjang putih kecil.
<a-assets> ... <a-mixin geometry=" primitive: box; height:0.5; width:0.1; depth:0.1;" material="color:white;"></a-mixin> </a-assets>
Selanjutnya, tambahkan objek pohon template yang akan menggunakan mixin ini. Masih di index.html , gulir ke bawah ke bagian platform. Tepat sebelum bagian pemain, tambahkan bagian pohon baru, dengan tiga entitas pohon kosong:
<a-entity ...> <!-- Trees --> <a-entity></a-entity> <a-entity></a-entity> <a-entity></a-entity> <!-- Player --> ...
Selanjutnya, reposisi, rescale, dan tambahkan bayangan ke entitas pohon.
<!-- Trees --> <a-entity shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity> <a-entity shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity> <a-entity shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity>
Sekarang, isi entitas pohon dengan batang dan dedaunan, menggunakan mixin yang telah kita definisikan sebelumnya.
<!-- Trees --> <a-entity ...> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> </a-entity> <a-entity ...> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> </a-entity> <a-entity ...> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> </a-entity>
Arahkan ke pratinjau Anda, dan Anda sekarang akan melihat pohon template berikut.
Sekarang, animasikan pohon dari lokasi yang jauh di platform ke arah pengguna. Seperti sebelumnya, gunakan tag a-animation
:
<!-- Trees --> <a-entity ...> ... <a-animation attribute="position" ease="linear" from="0 0.6 -7" to="0 0.6 1.5" dur="5000"></a-animation> </a-entity> <a-entity ...> ... <a-animation attribute="position" ease="linear" from="-0.5 0.55 -7" to="-0.5 0.55 1.5" dur="5000"></a-animation> </a-entity> <a-entity ...> ... <a-animation attribute="position" ease="linear" from="0.5 0.55 -7" to="0.5 0.55 1.5" dur="5000"></a-animation> </a-entity>
Pastikan kode Anda cocok dengan yang berikut ini.
<a-entity...> <!-- Trees --> <a-entity shadow scale="0.3 0.3 0.3" position="0 0.6 0"> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> <a-animation attribute="position" ease="linear" from="0 0.6 -7" to="0 0.6 1.5" dur="5000"></a-animation> </a-entity> <a-entity shadow scale="0.3 0.3 0.3" position="-0.5 0.55 0"> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> <a-animation attribute="position" ease="linear" from="-0.5 0.55 -7" to="-0.5 0.55 1.5" dur="5000"></a-animation> </a-entity> <a-entity shadow scale="0.3 0.3 0.3" position="0.5 0.55 0"> <a-entity mixin="foliage"></a-entity> <a-entity mixin="trunk" position="0 -0.5 0"></a-entity> <a-animation attribute="position" ease="linear" from="0.5 0.55 -7" to="0.5 0.55 1.5" dur="5000"></a-animation> </a-entity> <!-- Player --> ...
Arahkan ke pratinjau Anda, dan sekarang Anda akan melihat pepohonan bergerak ke arah Anda.
Navigasi kembali ke editor Anda. Kali ini, pilih assets/ergo.js . Di bagian permainan, atur pohon setelah jendela dimuat.
/******** * GAME * ********/ ... window.onload = function() { setupTrees(); }
Di bawah kontrol tetapi sebelum bagian Game, tambahkan bagian TREES
baru. Di bagian ini, tentukan fungsi setupTrees
baru.
/************ * CONTROLS * ************/ ... /********* * TREES * *********/ function setupTrees() { } /******** * GAME * ********/ ...
Dalam fungsi setupTrees
baru, dapatkan referensi ke objek DOM pohon template, dan buat referensi tersedia secara global.
/********* * TREES * *********/ var templateTreeLeft; var templateTreeCenter; var templateTreeRight; function setupTrees() { templateTreeLeft = document.getElementById('template-tree-left'); templateTreeCenter = document.getElementById('template-tree-center'); templateTreeRight = document.getElementById('template-tree-right'); }
Selanjutnya, tentukan utilitas removeTree
baru. Dengan utilitas ini, Anda kemudian dapat menghapus pohon template dari tempat kejadian. Di bawah fungsi setupTrees
, tentukan utilitas baru Anda.
function setupTrees() { ... } function removeTree(tree) { tree.parentNode.removeChild(tree); }
Kembali ke setupTrees
, gunakan utilitas baru untuk menghapus pohon template.
function setupTrees() { ... removeTree(templateTreeLeft); removeTree(templateTreeRight); removeTree(templateTreeCenter); }
Pastikan bahwa bagian pohon dan permainan Anda cocok dengan yang berikut:
/********* * TREES * *********/ var templateTreeLeft; var templateTreeCenter; var templateTreeRight; function setupTrees() { templateTreeLeft = document.getElementById('template-tree-left'); templateTreeCenter = document.getElementById('template-tree-center'); templateTreeRight = document.getElementById('template-tree-right'); removeTree(templateTreeLeft); removeTree(templateTreeRight); removeTree(templateTreeCenter); } function removeTree(tree) { tree.parentNode.removeChild(tree); } /******** * GAME * ********/ setupControls(); // TODO: AFRAME.registerComponent has to occur before window.onload? window.onload = function() { setupTrees(); }
Buka kembali pratinjau Anda, dan pohon Anda sekarang seharusnya tidak ada. Pratinjau harus cocok dengan permainan kita di awal tutorial ini.
Ini menyimpulkan desain pohon template.
Pada langkah ini, kita membahas dan menggunakan mixin A-Frame, yang memungkinkan kita untuk menyederhanakan kode dengan mendefinisikan properti umum. Selanjutnya, kami memanfaatkan integrasi A-Frame dengan DOM untuk menghapus objek dari adegan VR A-Frame.
Pada langkah selanjutnya, kita akan menelurkan banyak rintangan dan merancang algoritme sederhana untuk mendistribusikan pohon di antara jalur yang berbeda.
Langkah 2: Pemijahan Hambatan
Dalam permainan endless runner, tujuan kami adalah menghindari rintangan yang terbang ke arah kami. Dalam implementasi game ini, kami menggunakan tiga jalur seperti yang paling umum.
Tidak seperti kebanyakan game endless runner, game ini hanya akan mendukung gerakan kiri dan kanan . Ini memberlakukan batasan pada algoritme kami untuk memunculkan rintangan: kami tidak dapat memiliki tiga rintangan di ketiga jalur, pada saat yang sama, terbang ke arah kami. Jika itu terjadi, pemain tidak akan memiliki peluang untuk bertahan hidup. Akibatnya, algoritma pemijahan kami perlu mengakomodasi kendala ini.
Pada langkah ini, semua pengeditan kode kita akan dilakukan di assets/ergo.js . File HTML akan tetap sama. Navigasikan ke bagian TREES
dari assets/ergo.js .
Untuk memulai, kami akan menambahkan utilitas untuk menelurkan pohon. Setiap pohon akan membutuhkan ID unik, yang secara naif akan kita definisikan sebagai jumlah pohon yang ada saat pohon tersebut dimunculkan. Mulailah dengan melacak jumlah pohon dalam variabel global.
/********* * TREES * *********/ ... var numberOfTrees = 0; function setupTrees() { ...
Selanjutnya, kita akan menginisialisasi referensi ke elemen DOM wadah pohon, yang akan ditambahkan oleh fungsi spawn kita. Masih di bagian TREES
, tambahkan variabel global lalu buat referensi.
... var treeContainer; var numberOfTrees ... function setupTrees() { ... templateTreeRight = ... treeContainer = document.getElementById('tree-container'); removeTree(...); ... }
Menggunakan jumlah pohon dan wadah pohon, tulis fungsi baru yang memunculkan pohon.
function removeTree(tree) { ... } function addTree(el) { numberOfTrees += 1; el.id = 'tree-' + numberOfTrees; treeContainer.appendChild(el); } ...
Untuk kemudahan penggunaan nanti, Anda akan membuat fungsi kedua yang menambahkan pohon yang benar ke jalur yang benar. Untuk memulai, tentukan larik templates
baru di bagian TREES
.
var templates; var treeContainer; ... function setupTrees() { ... templates = [templateTreeLeft, templateTreeCenter, templateTreeRight]; removeTree(...); ... }
Dengan menggunakan larik templat ini, tambahkan utilitas yang memunculkan pohon di jalur tertentu, diberi ID yang mewakili kiri, tengah, atau kanan.
function function addTree(el) { ... } function addTreeTo(position_index) { var template = templates[position_index]; addTree(template.cloneNode(true)); }
Navigasikan ke pratinjau Anda, dan buka konsol pengembang Anda. Di konsol pengembang Anda, aktifkan fungsi addTreeTo
global.
> addTreeTo(0); # spawns tree in left lane
Sekarang, Anda akan menulis algoritme yang memunculkan pohon secara acak:
- Pilih jalur secara acak (yang belum dipilih, untuk langkah waktu ini);
- Memunculkan pohon dengan kemungkinan tertentu;
- Jika jumlah maksimum pohon telah muncul untuk langkah waktu ini, berhenti. Jika tidak, ulangi langkah 1.
Untuk menerapkan algoritme ini, kami akan mengacak daftar templat dan memprosesnya satu per satu. Mulailah dengan mendefinisikan fungsi baru, addTreesRandomly
yang menerima sejumlah argumen kata kunci yang berbeda.
function addTreeTo(position_index) { ... } /** * Add any number of trees across different lanes, randomly. **/ function addTreesRandomly( { probTreeLeft = 0.5, probTreeCenter = 0.5, probTreeRight = 0.5, maxNumberTrees = 2 } = {}) { }
Di fungsi addTreesRandomly
baru Anda, tentukan daftar pohon template, dan acak daftarnya.
function addTreesRandomly( ... ) { var trees = [ {probability: probTreeLeft, position_index: 0}, {probability: probTreeCenter, position_index: 1}, {probability: probTreeRight, position_index: 2}, ] shuffle(trees); }
Gulir ke bawah ke bagian bawah file, dan buat bagian utilitas baru, bersama dengan utilitas shuffle
baru. Utilitas ini akan mengacak array di tempatnya.
/******** * GAME * ********/ ... /************* * UTILITIES * *************/ /** * Shuffles array in place. * @param {Array} a items An array containing the items. */ function shuffle(a) { var j, x, i; for (i = a.length - 1; i > 0; i--) { j = Math.floor(Math.random() * (i + 1)); x = a[i]; a[i] = a[j]; a[j] = x; } return a; }
Arahkan kembali ke fungsi addTreesRandomly
di bagian Pohon Anda. Tambahkan variabel baru numberOfTreesAdded
dan ulangi daftar pohon yang ditentukan di atas.
function addTreesRandomly( ... ) { ... var numberOfTreesAdded = 0; trees.forEach(function (tree) { }); }
Dalam iterasi di atas pohon, telurkan pohon hanya dengan beberapa kemungkinan dan hanya jika jumlah pohon yang ditambahkan tidak melebihi 2
. Perbarui for loop sebagai berikut.
function addTreesRandomly( ... ) { ... trees.forEach(function (tree) { if (Math.random() < tree.probability && numberOfTreesAdded < maxNumberTrees) { addTreeTo(tree.position_index); numberOfTreesAdded += 1; } }); }
Untuk mengakhiri fungsinya, kembalikan jumlah pohon yang ditambahkan.
function addTreesRandomly( ... ) { ... return numberOfTreesAdded; }
Periksa kembali apakah fungsi addTreesRandomly
Anda cocok dengan yang berikut ini.
/** * Add any number of trees across different lanes, randomly. **/ function addTreesRandomly( { probTreeLeft = 0.5, probTreeCenter = 0.5, probTreeRight = 0.5, maxNumberTrees = 2 } = {}) { var trees = [ {probability: probTreeLeft, position_index: 0}, {probability: probTreeCenter, position_index: 1}, {probability: probTreeRight, position_index: 2}, ] shuffle(trees); var numberOfTreesAdded = 0; trees.forEach(function (tree) { if (Math.random() < tree.probability && numberOfTreesAdded < maxNumberTrees) { addTreeTo(tree.position_index); numberOfTreesAdded += 1; } }); return numberOfTreesAdded; }
Terakhir, untuk menelurkan pohon secara otomatis, siapkan pengatur waktu yang akan memicu pemijahan pohon secara berkala. Tentukan timer secara global, dan tambahkan fungsi teardown baru untuk timer ini.
/********* * TREES * *********/ ... var treeTimer; function setupTrees() { ... } function teardownTrees() { clearInterval(treeTimer); }
Selanjutnya, tentukan fungsi baru yang menginisialisasi timer dan menyimpan timer dalam variabel global yang telah ditentukan sebelumnya. Timer di bawah ini dijalankan setiap setengah detik.
function addTreesRandomlyLoop({intervalLength = 500} = {}) { treeTimer = setInterval(addTreesRandomly, intervalLength); }
Terakhir, mulai penghitung waktu setelah jendela dimuat, dari bagian Game.
/******** * GAME * ********/ ... window.onload = function() { ... addTreesRandomlyLoop(); }
Arahkan ke pratinjau Anda, dan Anda akan melihat pohon bertelur secara acak. Perhatikan bahwa tidak pernah ada tiga pohon sekaligus.
Ini menyimpulkan langkah hambatan. Kami telah berhasil mengambil sejumlah pohon templat dan menghasilkan jumlah rintangan yang tak terbatas dari templat. Algoritme pemijahan kami juga menghormati batasan alami dalam game untuk membuatnya dapat dimainkan.
Pada langkah selanjutnya, mari tambahkan pengujian tabrakan.
Langkah 3: Pengujian Tabrakan
Di bagian ini, kami akan menerapkan uji tabrakan antara rintangan dan pemain. Tes tabrakan ini lebih sederhana daripada tes tabrakan di kebanyakan game lain; namun, pemain hanya bergerak sepanjang sumbu x, jadi setiap kali pohon memotong sumbu x, periksa apakah jalur pohon sama dengan jalur pemain. Kami akan menerapkan pemeriksaan sederhana ini untuk game ini.
Arahkan ke index.html , turun ke bagian TREES
. Di sini, kami akan menambahkan informasi jalur ke masing-masing pohon. Untuk setiap pohon, tambahkan data-tree-position-index=
, sebagai berikut. Selain itu tambahkan class="tree"
, sehingga kita dapat dengan mudah memilih semua pohon di baris berikutnya:
<a-entity data-tree-position-index="1" class="tree" ...> </a-entity> <a-entity data-tree-position-index="0" class="tree" ...> </a-entity> <a-entity data-tree-position-index="2" class="tree" ...> </a-entity>
Navigasikan ke assets/ergo.js dan aktifkan fungsi setupCollisions
baru di bagian GAME
. Selain itu, tentukan variabel global isGameRunning
baru yang menunjukkan apakah game yang ada sudah berjalan atau belum.
/******** * GAME * ********/ var isGameRunning = false; setupControls(); setupCollision(); window.onload = function() { ...
Tentukan bagian COLLISIONS
baru tepat setelah bagian TREES
tetapi sebelum bagian Game. Di bagian ini, tentukan fungsi setupCollisions.
/********* * TREES * *********/ ... /************** * COLLISIONS * **************/ const POSITION_Z_OUT_OF_SIGHT = 1; const POSITION_Z_LINE_START = 0.6; const POSITION_Z_LINE_END = 0.7; function setupCollision() { } /******** * GAME * ********/
Seperti sebelumnya, kita akan mendaftarkan komponen AFRAME dan menggunakan pendengar acara tick
untuk menjalankan kode di setiap langkah waktu. Dalam hal ini, kami akan mendaftarkan komponen dengan player
dan menjalankan pemeriksaan terhadap semua pohon di pendengar itu:
function setupCollisions() { AFRAME.registerComponent('player', { tick: function() { document.querySelectorAll('.tree').forEach(function(tree) { } } } }
Dalam perulangan for
, mulailah dengan mendapatkan informasi yang relevan dari pohon:
document.querySelectorAll('.tree').forEach(function(tree) { position = tree.getAttribute('position'); tree_position_index = tree.getAttribute('data-tree-position-index'); tree_id = tree.getAttribute('id'); }
Selanjutnya, masih dalam for
loop, hapus pohon jika tidak terlihat, tepat setelah mengekstrak properti pohon:
document.querySelectorAll('.tree').forEach(function(tree) { ... if (position.z > POSITION_Z_OUT_OF_SIGHT) { removeTree(tree); } }
Selanjutnya, jika tidak ada game yang berjalan, jangan periksa apakah ada tabrakan.
document.querySelectorAll('.tree').forEach(function(tree) { if (!isGameRunning) return; }
Terakhir (masih dalam for
loop), periksa apakah pohon berbagi posisi yang sama pada waktu yang sama dengan pemain. Jika demikian, panggil fungsi gameOver
yang belum ditentukan:
document.querySelectorAll('.tree').forEach(function(tree) { ... if (POSITION_Z_LINE_START < position.z && position.z < POSITION_Z_LINE_END && tree_position_index == player_position_index) { gameOver(); } }
Periksa apakah fungsi setupCollisions
Anda cocok dengan yang berikut ini:
function setupCollisions() { AFRAME.registerComponent('player', { tick: function() { document.querySelectorAll('.tree').forEach(function(tree) { position = tree.getAttribute('position'); tree_position_index = tree.getAttribute('data-tree-position-index'); tree_id = tree.getAttribute('id'); if (position.z > POSITION_Z_OUT_OF_SIGHT) { removeTree(tree); } if (!isGameRunning) return; if (POSITION_Z_LINE_START < position.z && position.z < POSITION_Z_LINE_END && tree_position_index == player_position_index) { gameOver(); } }) } }) }
Ini menyimpulkan pengaturan tabrakan. Sekarang, kita akan menambahkan beberapa basa-basi untuk mengabstraksi urutan startGame
dan gameOver
. Arahkan ke bagian GAME
. Perbarui blok window.onload
agar sesuai dengan yang berikut, menggantikan addTreesRandomlyLoop
dengan fungsi startGame
yang belum ditentukan.
window.onload = function() { setupTrees(); startGame(); }
Di bawah pemanggilan fungsi pengaturan, buat fungsi startGame
baru. Fungsi ini akan menginisialisasi variabel isGameRunning
yang sesuai, dan mencegah panggilan yang berlebihan.
window.onload = function() { ... } function startGame() { if (isGameRunning) return; isGameRunning = true; addTreesRandomlyLoop(); }
Terakhir, tentukan gameOver
, yang akan mengingatkan "Game Over!" pesan untuk saat ini.
function startGame() { ... } function gameOver() { isGameRunning = false; alert('Game Over!'); teardownTrees(); }
Ini menyimpulkan bagian pengujian tabrakan dari game endless runner.
Pada langkah ini, kami kembali menggunakan komponen A-Frame dan sejumlah utilitas lain yang kami tambahkan sebelumnya. Kami juga mengatur ulang dan mengabstraksi fungsi game dengan benar; kami selanjutnya akan menambah fungsi game ini untuk mencapai pengalaman game yang lebih lengkap.
Kesimpulan
Di bagian 1, kami menambahkan kontrol ramah headset-VR: Lihat ke kiri untuk bergerak ke kiri, dan kanan untuk bergerak ke kanan. Di bagian kedua dari seri ini, saya telah menunjukkan kepada Anda betapa mudahnya membangun game realitas virtual dasar yang berfungsi. Kami menambahkan logika permainan, sehingga pelari tak berujung sesuai dengan harapan Anda: berlari selamanya dan memiliki serangkaian rintangan berbahaya yang tak ada habisnya terbang ke arah pemain. Sejauh ini, Anda telah membuat game yang berfungsi dengan dukungan tanpa keyboard untuk headset realitas virtual.
Berikut adalah sumber daya tambahan untuk kontrol dan headset VR yang berbeda:
- Bingkai-A untuk Headset VR
Survei browser dan headset yang didukung A-Frame VR. - A-Frame untuk Pengontrol VR
Bagaimana A-Frame tidak mendukung pengontrol, pengontrol 3DoF, dan pengontrol 6DoF, selain alternatif lain untuk interaksi.
Di bagian selanjutnya, kami akan menambahkan beberapa sentuhan akhir dan menyinkronkan status game , yang membuat kami selangkah lebih dekat ke game multipemain.