Cara Membuat Game Endless Runner Dalam Virtual Reality (Bagian 3)
Diterbitkan: 2022-03-10Dan begitulah perjalanan kami berlanjut. Di bagian terakhir dari seri saya tentang cara membuat game VR endless runner ini, saya akan menunjukkan kepada Anda bagaimana Anda dapat menyinkronkan status game antara dua perangkat yang akan membuat Anda selangkah lebih dekat untuk membangun game multipemain. Saya akan secara khusus memperkenalkan MirrorVR yang bertanggung jawab untuk menangani server mediasi dalam komunikasi klien-ke-klien.
Catatan : Game ini dapat dimainkan dengan atau tanpa headset VR. Anda dapat melihat demo produk akhir di ergo-3.glitch.me.
Untuk memulai, Anda memerlukan yang berikut ini.
- Akses internet (khusus untuk glitch.com);
- Sebuah proyek Glitch selesai dari bagian 2 dari tutorial ini. Anda dapat memulai dari produk jadi bagian 2 dengan menavigasi ke https://glitch.com/edit/#!/ergo-2 dan mengklik “Remix untuk mengedit”;
- Headset realitas virtual (opsional, disarankan). (Saya menggunakan Google Cardboard, yang ditawarkan seharga $15 per buah.)
Langkah 1: Tampilkan Skor
Gim apa adanya berfungsi minimal, di mana pemain diberi tantangan: menghindari rintangan. Namun, di luar tabrakan objek, game tidak memberikan umpan balik kepada pemain tentang kemajuan dalam game. Untuk mengatasinya, Anda akan menerapkan tampilan skor pada langkah ini. Skor akan berupa objek teks besar yang ditempatkan di dunia realitas virtual kita, sebagai lawan dari antarmuka yang terpaku pada bidang pandang pengguna.
Dalam realitas virtual umumnya, antarmuka pengguna paling baik diintegrasikan ke dunia daripada menempel di kepala pengguna.
Mulailah dengan menambahkan objek ke index.html . Tambahkan mixin text
, yang akan digunakan kembali untuk elemen teks lainnya:
<a-assets> ... <a-mixin text=" font:exo2bold; anchor:center; align:center;"></a-mixin> ... </a-assets>
Selanjutnya, tambahkan elemen text
ke platform, tepat sebelum pemutar:
<!-- Score --> <a-text value="" mixin="text" height="40" width="40" position="0 1.2 -3" opacity="0.75"></a-text> <!-- Player --> ...
Ini menambahkan entitas teks ke adegan realitas virtual. Teks saat ini tidak terlihat, karena nilainya disetel ke kosong. Namun, sekarang Anda akan mengisi entitas teks secara dinamis, menggunakan JavaScript. Arahkan ke assets/ergo.js . Setelah bagian collisions
, tambahkan bagian score
, dan tentukan sejumlah variabel global:
-
score
: skor pertandingan saat ini. -
countedTrees
: ID semua pohon yang termasuk dalam skor. (Ini karena uji tabrakan dapat memicu beberapa kali untuk pohon yang sama.) -
scoreDisplay
: referensi ke objek DOM, sesuai dengan objek teks di dunia virtual reality.
/********* * SCORE * *********/ var score; var countedTrees; var scoreDisplay;
Selanjutnya, tentukan fungsi setup untuk menginisialisasi variabel global kita. Dalam nada yang sama, tentukan fungsi teardown
.
... var scoreDisplay; function setupScore() { score = 0; countedTrees = new Set(); scoreDisplay = document.getElementById('score'); } function teardownScore() { scoreDisplay.setAttribute('value', ''); }
Di bagian Game
, perbarui gameOver
, startGame
, dan window.onload
untuk menyertakan pengaturan skor dan pembongkaran.
/******** * GAME * ********/ function gameOver() { ... teardownScore(); } function startGame() { ... setupScore(); addTreesRandomlyLoop(); } window.onload = function() { setupScore(); ... }
Tentukan fungsi yang meningkatkan skor untuk pohon tertentu. Fungsi ini akan memeriksa countedTrees
untuk memastikan bahwa pohon tidak dihitung ganda.
function addScoreForTree(tree_id) { if (countedTrees.has(tree_id)) return; score += 1; countedTrees.add(tree_id); }
Selain itu, tambahkan utilitas untuk memperbarui tampilan skor menggunakan variabel global.
function updateScoreDisplay() { scoreDisplay.setAttribute('value', score); }
Perbarui pengujian tabrakan yang sesuai untuk menjalankan fungsi peningkatan skor ini setiap kali rintangan telah melewati pemain. Masih di assets/ergo.js
, navigasikan ke bagian collisions
. Tambahkan cek berikut dan perbarui.
AFRAME.registerComponent('player', { tick: function() { document.querySelectorAll('.tree').forEach(function(tree) { ... if (position.z > POSITION_Z_LINE_END) { addScoreForTree(tree_id); updateScoreDisplay(); } }) } })
Terakhir, perbarui tampilan skor segera setelah permainan dimulai. Arahkan ke bagian Game
, dan tambahkan updateScoreDisplay();
untuk startGame
:
function startGame() { ... setupScore(); updateScoreDisplay(); ... }
Pastikan assets/ergo.js dan index.html cocok dengan file kode sumber yang sesuai. Kemudian, navigasikan ke pratinjau Anda. Anda akan melihat yang berikut ini:
Ini menyimpulkan tampilan skor. Selanjutnya, kami akan menambahkan menu start dan Game Over yang tepat, sehingga pemain dapat memutar ulang game sesuai keinginan.
Langkah 2: Tambahkan Menu Mulai
Sekarang pengguna dapat melacak kemajuan, Anda akan menambahkan sentuhan akhir untuk menyelesaikan pengalaman permainan. Pada langkah ini, Anda akan menambahkan menu Start dan menu Game Over , membiarkan pengguna memulai dan memulai ulang game.
Mari kita mulai dengan menu Start di mana pemain mengklik tombol "Start" untuk memulai permainan. Untuk paruh kedua langkah ini, Anda akan menambahkan menu Game Over , dengan tombol "Restart":
Arahkan ke index.html di editor Anda. Kemudian, temukan bagian Mixins
. Di sini, tambahkan mixin title
, yang mendefinisikan gaya untuk teks yang sangat besar. Kami menggunakan font yang sama seperti sebelumnya, menyelaraskan teks ke tengah, dan menentukan ukuran yang sesuai untuk jenis teks. (Perhatikan di bawah bahwa anchor
adalah tempat objek teks ditambatkan ke posisinya.)
<a-assets> ... <a-mixin text=" font:exo2bold; height:40; width:40; opacity:0.75; anchor:center; align:center;"></a-mixin> </a-assets>
Selanjutnya, tambahkan mixin kedua untuk heading sekunder. Teks ini sedikit lebih kecil tetapi sebaliknya identik dengan judulnya.
<a-assets> ... <a-mixin text=" font:exo2bold; height:10; width:10; opacity:0.75; anchor:center; align:center;"></a-mixin> </a-assets>
Untuk mixin ketiga dan terakhir, tentukan properti untuk teks deskriptif — bahkan lebih kecil dari heading sekunder.
<a-assets> ... <a-mixin text=" font:exo2bold; height:5; width:5; opacity:0.75; anchor:center; align:center;"></a-mixin> </a-assets>
Dengan semua gaya teks yang ditentukan, Anda sekarang akan menentukan objek teks di dunia. Tambahkan bagian Menus
baru di bawah bagian Score
, dengan wadah kosong untuk menu Mulai :
<!-- Score --> ... <!-- Menus --> <a-entity> <a-entity position="0 1.1 -3"> </a-entity> </a-entity>
Di dalam wadah menu mulai, tentukan judul dan wadah untuk semua teks non-judul:
... <a-entity ...> <a-entity position="0 1 0"> </a-entity> <a-text value="ERGO" mixin="title"></a-text> </a-entity> </a-entity>
Di dalam wadah untuk teks non-judul, tambahkan instruksi untuk memainkan game:
<a-entity...> <a-text value="Turn left and right to move your player, and avoid the trees!" mixin="copy"></a-text> </a-entity>
Untuk melengkapi menu Start , tambahkan tombol yang bertuliskan “Start”:
<a-entity...> ... <a-text value="Start" position="0 0.75 0" mixin="heading"></a-text> <a-box position="0 0.65 -0.05" width="1.5" height="0.6" depth="0.1"></a-box> </a-entity>
Periksa kembali apakah kode HTML menu Mulai Anda cocok dengan yang berikut ini:
<!-- Menus --> <a-entity> <a-entity position="0 1.1 -3"> <a-entity position="0 1 0"> <a-text value="Turn left and right to move your player, and avoid the trees!" mixin="copy"></a-text> <a-text value="Start" position="0 0.75 0" mixin="heading"></a-text> <a-box position="0 0.65 -0.05" width="1.5" height="0.6" depth="0.1"></a-box> </a-entity> <a-text value="ERGO" mixin="title"></a-text> </a-entity> </a-entity>
Arahkan ke pratinjau Anda, dan Anda akan melihat menu Start berikut:
Masih di bagian Menus
(tepat di bawah menu start
), tambahkan menu game-over
menggunakan mixin yang sama:
<!-- Menus --> <a-entity> ... <a-entity position="0 1.1 -3"> <a-text value="?" mixin="heading" position="0 1.7 0"></a-text> <a-text value="Score" mixin="copy" position="0 1.2 0"></a-text> <a-entity> <a-text value="Restart" mixin="heading" position="0 0.7 0"></a-text> <a-box position="0 0.6 -0.05" width="2" height="0.6" depth="0.1"></a-box> </a-entity> <a-text value="Game Over" mixin="title"></a-text> </a-entity> </a-entity>
Arahkan ke file JavaScript Anda, assets/ergo.js . Buat bagian Menus
baru sebelum bagian Game
. Selain itu, tentukan tiga fungsi kosong: setupAllMenus
, hideAllMenus
, dan showGameOverMenu
.
/******** * MENU * ********/ function setupAllMenus() { } function hideAllMenus() { } function showGameOverMenu() { } /******** * GAME * ********/
Selanjutnya, perbarui bagian Game
di tiga tempat. Di gameOver
, tampilkan menu Game Over :
function gameOver() { ... showGameOverMenu(); } ``` In `startGame`, hide all menus: ``` function startGame() { ... hideAllMenus(); }
Selanjutnya, di window.onload
, hapus panggilan langsung ke startGame
dan panggil setupAllMenus
. Perbarui pendengar Anda agar sesuai dengan yang berikut:
window.onload = function() { setupAllMenus(); setupScore(); setupTrees(); }
Arahkan kembali ke bagian Menu
. Simpan referensi ke berbagai objek DOM:
/******** * MENU * ********/ var menuStart; var menuGameOver; var menuContainer; var isGameRunning = false; var startButton; var restartButton; function setupAllMenus() { menuStart = document.getElementById('start-menu'); menuGameOver = document.getElementById('game-over'); menuContainer = document.getElementById('menu-container'); startButton = document.getElementById('start-button'); restartButton = document.getElementById('restart-button'); }
Selanjutnya, ikat tombol “Start” dan “Restart” ke startGame
:
function setupAllMenus() { ... startButton.addEventListener('click', startGame); restartButton.addEventListener('click', startGame); }
Tentukan showStartMenu
dan aktifkan dari setupAllMenus
:
function setupAllMenus() { ... showStartMenu(); } function hideAllMenus() { } function showGameOverMenu() { } function showStartMenu() { }
Untuk mengisi tiga fungsi kosong, Anda memerlukan beberapa fungsi pembantu. Tentukan dua fungsi berikut, yang menerima elemen DOM yang mewakili entitas A-Frame VR dan menampilkan atau menyembunyikannya. Tentukan kedua fungsi di atas showAllMenus
:
... var restartButton; function hideEntity(el) { el.setAttribute('visible', false); } function showEntity(el) { el.setAttribute('visible', true); } function showAllMenus() { ...
Pertama hideAllMenus
. Anda akan menghapus objek dari pandangan, lalu menghapus pendengar klik untuk kedua menu:
function hideAllMenus() { hideEntity(menuContainer); startButton.classList.remove('clickable'); restartButton.classList.remove('clickable'); }
Kedua, showGameOverMenu
. Di sini, pulihkan wadah untuk kedua menu, serta menu Game Over dan pendengar klik tombol 'Restart'. Namun, hapus pendengar klik tombol 'Start', dan sembunyikan menu 'Start'.
function showGameOverMenu() { showEntity(menuContainer); hideEntity(menuStart); showEntity(menuGameOver); startButton.classList.remove('clickable'); restartButton.classList.add('clickable'); }
Ketiga, showStartMenu
. Di sini, balikkan semua perubahan yang dilakukan showGameOverMenu
.
function showStartMenu() { showEntity(menuContainer); hideEntity(menuGameOver); showEntity(menuStart); startButton.classList.add('clickable'); restartButton.classList.remove('clickable'); }
Periksa kembali apakah kode Anda cocok dengan file sumber yang sesuai. Kemudian, navigasikan ke pratinjau Anda, dan Anda akan mengamati perilaku berikut:
Ini mengakhiri menu Start dan Game Over .
Selamat! Anda sekarang memiliki permainan yang berfungsi penuh dengan awal yang tepat dan akhir yang tepat. Namun, kami memiliki satu langkah lagi dalam tutorial ini: Kami perlu menyinkronkan status permainan antara perangkat pemain yang berbeda. Ini akan membuat kita selangkah lebih dekat menuju game multipemain.
Langkah 3: Menyinkronkan Status Game Dengan MirrorVR
Dalam tutorial sebelumnya, Anda telah mempelajari cara mengirim informasi waktu nyata melalui soket, untuk memfasilitasi komunikasi satu arah antara server dan klien. Pada langkah ini, Anda akan membangun di atas produk lengkap dari tutorial itu, MirrorVR, yang menangani server mediasi dalam komunikasi klien-ke-klien.
Catatan : Anda dapat mempelajari lebih lanjut tentang MirrorVR di sini.
Arahkan ke index.html . Di sini, kami akan memuat MirrorVR dan menambahkan komponen ke kamera, yang menunjukkan bahwa itu harus mencerminkan tampilan perangkat seluler jika berlaku. Impor ketergantungan socket.io dan MirrorVR 0.2.3.
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script> <script src="https://cdn.jsdelivr.net/gh/alvinwan/[email protected]/dist/mirrorvr.min.js"></script>
Selanjutnya, tambahkan komponen, camera-listener
, ke kamera:
<a-camera camera-listener ...>
Arahkan ke assets/ergo.js . Pada langkah ini, perangkat seluler akan mengirim perintah, dan perangkat desktop hanya akan mencerminkan perangkat seluler.
Untuk memfasilitasi ini, Anda memerlukan utilitas untuk membedakan antara perangkat desktop dan seluler. Di akhir file Anda, tambahkan fungsi mobileCheck
setelah shuffle
:
/** * Checks for mobile and tablet platforms. */ function mobileCheck() { var check = false; (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[aw])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); return check; };
Pertama, kami akan menyinkronkan awal permainan. Di startGame
, dari bagian Game , tambahkan notifikasi mirrorVR
di bagian akhir.
function startGame() { ... if (mobileCheck()) { mirrorVR.notify('startGame', {}) } }
Klien seluler sekarang mengirimkan pemberitahuan tentang permainan yang dimulai. Anda sekarang akan menerapkan respons desktop.
Di pendengar beban jendela, aktifkan fungsi setupMirrorVR
:
window.onload = function() { ... setupMirrorVR(); }
Tentukan bagian baru di atas bagian Game
untuk pengaturan MirrorVR:
/************ * MirrorVR * ************/ function setupMirrorVR() { mirrorVR.init(); }
Selanjutnya, tambahkan argumen kata kunci ke fungsi inisialisasi untuk mirrorVR. Secara khusus, kami akan menentukan handler untuk notifikasi awal game. Kami juga akan menentukan ID kamar; ini memastikan bahwa siapa pun yang memuat aplikasi Anda segera disinkronkan.
function setupMirrorVR() { mirrorVR.init({ roomId: 'ergo', state: { startGame: { onNotify: function(data) { hideAllMenus(); setupScore(); updateScoreDisplay(); } }, } }); }
Ulangi proses sinkronisasi yang sama untuk Game Over . Di gameOver
di bagian Game
, tambahkan tanda centang untuk perangkat seluler dan kirim pemberitahuan yang sesuai:
function gameOver() { ... if (mobileCheck()) { mirrorVR.notify('gameOver', {}); } }
Arahkan ke bagian MirrorVR
dan perbarui argumen kata kunci dengan pendengar gameOver
:
function setupMirrorVR() { mirrorVR.init({ state: { startGame: {... }, gameOver: { onNotify: function(data) { gameOver(); } }, } }) }
Selanjutnya, ulangi proses sinkronisasi yang sama untuk penambahan pohon. Arahkan ke addTreesRandomly
di bagian Trees
. Lacak jalur mana yang menerima pohon baru. Kemudian, tepat sebelum arahan return
, dan kirim pemberitahuan yang sesuai:
function addTreesRandomly(...) { ... var numberOfTreesAdded ... var position_indices = []; trees.forEach(function (tree) { if (...) { ... position_indices.push(tree.position_index); } }); if (mobileCheck()) { mirrorVR.notify('addTrees', position_indices); } return ... }
Arahkan ke bagian MirrorVR
, dan perbarui argumen kata kunci ke mirrorVR.init
dengan listener baru untuk pohon:
function setupMirrorVR() { mirrorVR.init({ state: { ... gameOver: {... }, addTrees: { onNotify: function(position_indices) { position_indices.forEach(addTreeTo) } }, } }) }
Akhirnya, kami menyinkronkan skor game. Di updateScoreDisplay
dari bagian Score
, kirim pemberitahuan bila berlaku:
function updateScoreDisplay() { ... if (mobileCheck()) { mirrorVR.notify('score', score); } }
Perbarui inisialisasi mirrorVR
untuk terakhir kalinya, dengan pendengar untuk perubahan skor:
function setupMirrorVR() { mirrorVR.init({ state: { addTrees: { }, score: { onNotify: function(data) { score = data; updateScoreDisplay(); } } } }); }
Periksa kembali apakah kode Anda cocok dengan file kode sumber yang sesuai untuk langkah ini. Kemudian, navigasikan ke pratinjau desktop Anda. Selain itu, buka URL yang sama di perangkat seluler Anda. Segera setelah perangkat seluler Anda memuat halaman web, desktop Anda akan segera mulai mencerminkan permainan perangkat seluler.
Berikut adalah demo. Perhatikan bahwa kursor desktop tidak bergerak, menunjukkan perangkat seluler mengontrol pratinjau desktop.
Ini mengakhiri proyek augmented Anda dengan mirrorVR.
Langkah ketiga ini memperkenalkan beberapa langkah sinkronisasi status permainan dasar; untuk membuatnya lebih kuat, Anda dapat menambahkan lebih banyak pemeriksaan kewarasan dan lebih banyak titik sinkronisasi.
Kesimpulan
Dalam tutorial ini, Anda menambahkan sentuhan akhir ke game endless runner Anda dan menerapkan sinkronisasi real-time dari klien desktop dengan klien seluler, yang secara efektif mencerminkan layar perangkat seluler di desktop Anda. Ini mengakhiri seri membangun game endless runner dalam realitas virtual. Seiring dengan teknik A-Frame VR, Anda telah mengambil pemodelan 3D, komunikasi klien-ke-klien, dan konsep lain yang berlaku secara luas.
Langkah selanjutnya dapat mencakup:
- Pemodelan Lebih Lanjut
Ini berarti model 3D yang lebih realistis, berpotensi dibuat dalam perangkat lunak pihak ketiga dan diimpor. Misalnya, (MagicaVoxel) membuat pembuatan seni voxel menjadi sederhana, dan (Blender) adalah solusi pemodelan 3D yang lengkap. - Lebih Kompleks
Game yang lebih kompleks, seperti game strategi waktu nyata, dapat memanfaatkan mesin pihak ketiga untuk meningkatkan efisiensi. Ini mungkin berarti menghindari A-Frame dan webVR sepenuhnya, alih-alih memublikasikan game yang dikompilasi (Unity3d).
Jalan lain termasuk dukungan multipemain dan grafik yang lebih kaya. Dengan kesimpulan dari seri tutorial ini, Anda sekarang memiliki kerangka kerja untuk dijelajahi lebih lanjut.