Cara Membuat Game Real-Time Multiplayer Virtual Reality (Bagian 2)
Diterbitkan: 2022-03-10Dalam seri tutorial ini, kita akan membangun game realitas virtual multipemain berbasis web, di mana pemain harus berkolaborasi untuk memecahkan teka-teki. Di bagian pertama dari seri ini, kami merancang orb yang ditampilkan dalam game. Di bagian seri ini, kami akan menambahkan mekanisme permainan dan mengatur protokol komunikasi antara pasangan pemain.
Deskripsi permainan di sini dikutip dari bagian pertama dari seri: Setiap pasangan pemain diberikan cincin bola. Tujuannya adalah untuk "menghidupkan" semua orb, di mana orb "on" jika ditinggikan dan cerah. Sebuah bola dikatakan “mati” jika lebih rendah dan redup. Namun, bola "dominan" tertentu mempengaruhi tetangganya: jika ia beralih status, tetangganya juga beralih status. Pemain 2 dapat mengontrol bola bernomor genap, dan pemain 1 dapat mengontrol bola ganjil. Hal ini memaksa kedua pemain untuk berkolaborasi memecahkan teka-teki.
8 langkah dalam tutorial ini dikelompokkan menjadi 3 bagian:
- Mengisi Antarmuka Pengguna (Langkah 1 dan 2)
- Tambahkan Mekanika Game (Langkah 3 hingga 5)
- Atur Komunikasi (Langkah 6 hingga 8)
Bagian ini akan ditutup dengan demo online yang berfungsi penuh, untuk dimainkan siapa saja. Anda akan menggunakan A-Frame VR dan beberapa ekstensi A-Frame.
Anda dapat menemukan kode sumber yang sudah jadi di sini.
1. Tambahkan Indikator Visual
Untuk memulai, kami akan menambahkan indikator visual dari ID orb. Sisipkan elemen VR a-text
baru sebagai anak pertama dari #container-orb0
, pada L36.
<a-entity ...> <a-text class="orb-id" opacity="0.25" rotation="0 -90 0" value="4" color="#FFF" scale="3 3 3" position="0 -2 -0.25" material="side:double"></a-text> ... <a-entity position...> ... </a-entity> </a-entity>
"Ketergantungan" sebuah orb adalah orb yang akan dialihkan, ketika diaktifkan: misalnya, katakanlah orb 1 memiliki dependensi sebagai orbs 2 dan 3. Ini berarti bahwa jika orb 1 diaktifkan, orb 2 dan 3 juga akan diaktifkan. Kami akan menambahkan indikator visual dependensi, sebagai berikut, langsung setelah .animation-position
.
<a-animation class="animation-position" ... /> <a-text class="dep-right" opacity="0.25" rotation="0 -90 0" value="4" color="#FFF" scale="10 10 10" position="0 0 1" material="side:double" ></a-text> <a-text class="dep-left" opacity="0.25"rotation="0 -90 0" value="1" color="#FFF" scale="10 10 10" position="0 0 -3" material="side:double" ></a-text>
Verifikasi bahwa kode Anda cocok dengan kode sumber kami untuk Langkah 1. Orb Anda sekarang harus cocok dengan yang berikut:
Ini menyimpulkan indikator visual tambahan yang kita perlukan. Selanjutnya, kita akan menambahkan bola secara dinamis ke adegan VR, menggunakan bola template ini.
2. Tambahkan Orb Secara Dinamis
Pada langkah ini, kita akan menambahkan bola sesuai dengan spesifikasi level JSON-esque. Ini memungkinkan kita untuk dengan mudah menentukan dan menghasilkan level baru. Kami akan menggunakan orb dari langkah terakhir di bagian 1 sebagai template.
Untuk memulai, impor jQuery, karena ini akan membuat modifikasi DOM, dan dengan demikian modifikasi adegan VR, lebih mudah. Langsung setelah impor A-Frame, tambahkan berikut ini ke L8:
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
Tentukan level menggunakan array. Array akan berisi literal objek yang mengkodekan "dependensi" setiap bola. Di dalam <head>
, tambahkan konfigurasi level berikut, :
<script> var orbs = [ {left: 1, right: 4}, {}, {on: true}, {}, {on: true} ]; </script>
Untuk saat ini, setiap bola hanya dapat memiliki satu ketergantungan di "kanan" dan satu di "kiri". Segera setelah mendeklarasikan orbs
di atas, tambahkan handler yang akan berjalan pada pemuatan halaman. Handler ini akan (1) menduplikasi bola template dan (2) menghapus bola template, menggunakan konfigurasi level yang disediakan:
$(document).ready(function() { function populateTemplate(orb, template, i, total) {} function remove(selector) {} for (var i=0; i < orbs.length; i++) { var orb = orbs[i]; var template = $('#template').clone(); template = populateTemplate(orb, template, i, orbs.length); $('#carousel').append(template); } remove('#template'); } function clickOrb(i) {}
Selanjutnya, isi fungsi remove
, yang hanya menghapus item dari adegan VR, dengan pemilih. Untungnya, A-Frame mengamati perubahan pada DOM, dan dengan demikian, menghapus item dari DOM sudah cukup untuk menghapusnya dari adegan VR. Isi fungsi remove
sebagai berikut.
function remove(selector) { var el = document.querySelector(selector); el.parentNode.removeChild(el); }
Isi fungsi clickOrb
, yang hanya memicu tindakan klik pada bola.
function clickOrb(i) { document.querySelector("#container-orb" + i).click(); }
Selanjutnya, mulailah menulis fungsi populateTemplate
. Dalam fungsi ini, mulailah dengan mendapatkan .container
. Wadah untuk bola ini juga berisi indikator visual yang kami tambahkan di langkah sebelumnya. Selanjutnya, kita perlu memodifikasi perilaku onclick
orb, berdasarkan dependensinya. Jika ada ketergantungan kiri, ubah indikator visual dan perilaku onclick
untuk mencerminkannya; hal yang sama berlaku untuk dependensi kanan:
function populateTemplate(orb, template, i, total) { var container = template.find('.container'); var onclick = 'document.querySelector("#light-orb' + i + '").emit("switch");'; if (orb.left || orb.right) { if (orb.left) { onclick += 'clickOrb(' + orb.left + ');'; container.find('.dep-left').attr('value', orb.left); } if (orb.right) { onclick += 'clickOrb(' + orb.right + ');'; container.find('.dep-right').attr('value', orb.right); } } else { container.find('.dep-left').remove(); container.find('.dep-right').remove(); } }
Masih dalam fungsi populateTemplate
, atur ID orb dengan benar di semua elemen orb dan containernya.
container.find('.orb-id').attr('value', i); container.attr('id', 'container-orb' + i); template.find('.orb').attr('id', 'orb' + i); template.find('.light-orb').attr('id', 'light-orb' + i); template.find('.clickable').attr('data-id', i);
Masih dalam fungsi populateTemplate
, atur perilaku onclick
, atur seed acak sehingga setiap orb berbeda secara visual, dan terakhir, atur posisi rotasi orb berdasarkan ID-nya.
container.attr('onclick', onclick); container.find('lp-sphere').attr('seed', i); template.attr('rotation', '0 ' + (360 / total * i) + ' 0');
Di akhir fungsi, kembalikan template
dengan semua konfigurasi di atas.
return template;
Di dalam document load handler dan setelah menghapus template dengan remove('#template')
, nyalakan orbs yang telah dikonfigurasi pada awalnya.
$(document).ready(function() { ... setTimeout(function() { for (var i=0; i < orbs.length; i++) { var orb = orbs[i]; if (orb.on) { document.querySelector("#container-orb" + i).click(); } } }, 1000); });
Ini menyimpulkan modifikasi Javascript. Selanjutnya, kita akan mengubah pengaturan default template menjadi 'off' orb. Ubah posisi dan skala untuk #container-orb0
menjadi berikut:
position="8 0.5 0" scale="0.5 0.5 0.5"
Kemudian, ubah intensitas untuk #light-orb0
menjadi 0.
intensity="0"
Verifikasi bahwa kode sumber Anda cocok dengan kode sumber kami untuk Langkah 2.
Adegan VR Anda sekarang harus menampilkan 5 bola, terisi secara dinamis. Salah satu orb selanjutnya harus memiliki indikator ketergantungan visual, seperti di bawah ini:
Ini menyimpulkan bagian pertama dalam menambahkan bola secara dinamis. Di bagian selanjutnya, kita akan menghabiskan tiga langkah menambahkan mekanisme permainan. Secara khusus, pemain hanya akan dapat mengaktifkan bola tertentu tergantung pada ID pemain.
3. Tambahkan Status Terminal
Pada langkah ini, kita akan menambahkan status terminal. Jika semua bola berhasil dinyalakan, pemain melihat halaman "kemenangan". Untuk melakukan ini, Anda perlu melacak status semua bola. Setiap kali sebuah bola dinyalakan atau dimatikan, kita perlu memperbarui keadaan internal kita. Katakan bahwa fungsi pembantu toggleOrb
memperbarui status untuk kami. Aktifkan fungsi toggleOrb
setiap kali orb berubah status: (1) tambahkan pendengar klik ke pengendali onload dan (2) tambahkan toggleOrb(i);
permintaan ke clickOrb
. Terakhir, (3) tentukan toggleOrb
kosong.
$(document).ready(function() { ... $('.orb').on('click', function() { var id = $(this).attr('data-id') toggleOrb(id); }); }); function toggleOrb(i) {} function clickOrb(i) { ... toggleOrb(i); }
Untuk kesederhanaan, kami akan menggunakan konfigurasi level kami untuk menunjukkan status permainan. Gunakan toggleOrb
untuk mengaktifkan status on
untuk bola ke-i. toggleOrb
juga dapat memicu status terminal jika semua bola dihidupkan.
function toggleOrb(i) { orbs[i].on = !orbs[i].on; if (orbs.every(orb => orb.on)) console.log('Victory!'); }
Periksa kembali apakah kode Anda cocok dengan kode sumber kami untuk Langkah 3.
Ini mengakhiri mode "pemain tunggal" untuk game. Pada titik ini, Anda memiliki game realitas virtual yang berfungsi penuh. Namun, Anda sekarang perlu menulis komponen multipemain dan mendorong kolaborasi melalui mekanisme permainan.
4. Buat Objek Pemain
Pada langkah ini, kita akan membuat abstraksi untuk pemain dengan ID pemain. ID pemain ini akan diberikan oleh server nanti.
Untuk saat ini, ini hanya akan menjadi variabel global. Langsung setelah mendefinisikan orbs
, tentukan ID pemain:
var orbs = ... var current_player_id = 1;
Periksa kembali apakah kode Anda cocok dengan kode sumber kami untuk Langkah 4. Pada langkah berikutnya, ID pemain ini kemudian akan digunakan untuk menentukan bola mana yang dapat dikontrol oleh pemain.
5. Beralih Bola Bersyarat
Pada langkah ini, kita akan memodifikasi perilaku toggling bola. Secara khusus, pemain 1 dapat mengontrol bola bernomor ganjil dan pemain 2 dapat mengontrol bola bernomor genap. Pertama, terapkan logika ini di kedua tempat di mana orbs berubah status:
$('.orb').on('click', function() { var id = ... if (!allowedToToggle(id)) return false; ... } ... function clickOrb(i) { if (!allowedToToggle(id)) return; ... }
Kedua, tentukan fungsi allowedToToggle
, tepat setelah clickOrb
. Jika pemain saat ini adalah pemain 1, id bernomor ganjil akan mengembalikan nilai kebenaran-y dan dengan demikian, pemain 1 akan diizinkan untuk mengontrol bola bernomor ganjil. Kebalikannya berlaku untuk pemain 2. Semua pemain lain tidak diperbolehkan untuk mengontrol bola.
function allowedToToggle(id) { if (current_player_id == 1) { return id % 2; } else if (current_player_id == 2) { return !(id % 2); } return false; }
Periksa kembali apakah kode Anda cocok dengan kode sumber kami untuk Langkah 5. Secara default, pemainnya adalah pemain 1. Ini berarti Anda sebagai pemain 1 hanya dapat mengontrol bola bernomor ganjil di pratinjau Anda. Ini menyimpulkan bagian tentang mekanika game.
Di bagian selanjutnya, kami akan memfasilitasi komunikasi antara kedua pemain melalui server.
6. Atur Server Dengan WebSocket
Pada langkah ini, Anda akan menyiapkan server sederhana untuk (1) melacak ID pemain dan (2) menyampaikan pesan. Pesan-pesan ini akan menyertakan status permainan, sehingga pemain dapat yakin bahwa masing-masing melihat apa yang dilihat oleh yang lain.
Kami akan merujuk ke index.html
Anda sebelumnya sebagai kode sumber sisi klien. Kami akan merujuk ke kode dalam langkah ini sebagai kode sumber sisi server. Arahkan ke glitch.com, klik "proyek baru" di kanan atas, dan di tarik-turun, klik "hello-express".
Dari panel sebelah kiri, pilih “package.json,” dan tambahkan socket-io
ke dependencies
. Kamus dependencies
Anda sekarang harus cocok dengan yang berikut ini.
"dependencies": { "express": "^4.16.4", "socketio": "^1.0.0" },
Dari panel sebelah kiri, pilih “index.js,” dan ganti konten file itu dengan socket.io minimal Hello World berikut:
const express = require("express"); const app = express(); var http = require('http').Server(app); var io = require('socket.io')(http); /** * Run application on port 3000 */ var port = process.env.PORT || 3000; http.listen(port, function(){ console.log('listening on *:', port); });
Di atas mengatur socket.io pada port 3000 untuk aplikasi ekspres dasar. Selanjutnya, tentukan dua variabel global, satu untuk mempertahankan daftar pemain aktif dan satu lagi untuk mempertahankan ID pemain terkecil yang belum ditetapkan.
/** * Maintain player IDs */ var playerIds = []; var smallestPlayerId = 1;
Selanjutnya, tentukan fungsi getPlayerId
, yang menghasilkan ID pemain baru dan menandai ID pemain baru sebagai "diambil" dengan menambahkannya ke larik playerIds
. Secara khusus, fungsi hanya menandai smallestPlayerId
dan kemudian memperbarui smallestPlayerId
dengan mencari bilangan bulat terkecil berikutnya yang tidak diambil.
function getPlayerId() { var playerId = smallestPlayerId; playerIds.push(playerId); while (playerIds.includes(smallestPlayerId)) { smallestPlayerId++; } return playerId; }
Tentukan fungsi removePlayer
, yang mengupdate smallestPlayerId
sesuai dan membebaskan playerId
yang disediakan sehingga pemain lain dapat mengambil ID tersebut.
function removePlayer(playerId) { if (playerId < smallestPlayerId) { smallestPlayerId = playerId; } var index = playerIds.indexOf(playerId); playerIds.splice(index, 1); }
Terakhir, tentukan sepasang pengendali kejadian soket yang mendaftarkan pemain baru dan membatalkan pendaftaran pemain yang terputus, menggunakan pasangan metode di atas.
/** * Handle socket interactions */ io.on('connection', function(socket) { socket.on('newPlayer', function() { socket.playerId = getPlayerId(); console.log("new player: ", socket.playerId); socket.emit('playerId', socket.playerId); }); socket.on('disconnect', function() { if (socket.playerId === undefined) return; console.log("disconnected player: ", socket.playerId); removePlayer(socket.playerId); }); });
Periksa kembali apakah kode Anda cocok dengan kode sumber kami untuk Langkah 6. Ini mengakhiri pendaftaran dan pembatalan pendaftaran pemain dasar. Setiap klien sekarang dapat menggunakan ID pemain yang dibuat server.
Pada langkah berikutnya, kami akan memodifikasi klien untuk menerima dan menggunakan ID pemain yang dipancarkan server.
7. Terapkan ID Pemain
Dalam dua langkah berikutnya, kami akan menyelesaikan versi pengalaman multipemain yang belum sempurna. Untuk memulai, integrasikan sisi klien penetapan ID pemain. Secara khusus, setiap klien akan meminta server ID pemain. Navigasikan kembali ke index.html
sisi klien yang kami kerjakan dalam Langkah 4 dan sebelumnya.
Impor socket.io
di head
di L7:
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script>
Setelah penangan pemuatan dokumen, buat instance soket dan pancarkan acara newPlayer
. Sebagai tanggapan, sisi server akan menghasilkan ID pemain baru menggunakan peristiwa playerId
. Di bawah, gunakan URL untuk pratinjau proyek Glitch Anda alih-alih lightful.glitch.me
. Anda dipersilakan untuk menggunakan URL demo di bawah ini, tetapi perubahan kode apa pun yang Anda buat tentu saja tidak akan terlihat.
$(document).ready(function() { ... }); socket = io("https://lightful.glitch.me"); socket.emit('newPlayer'); socket.on('playerId', function(player_id) { current_player_id = player_id; console.log(" * You are now player", current_player_id); });
Verifikasi bahwa kode Anda cocok dengan kode sumber kami untuk Langkah 7. Sekarang, Anda dapat memuat game Anda di dua browser atau tab berbeda untuk memainkan dua sisi game multipemain. Pemain 1 akan dapat mengontrol bola bernomor ganjil dan pemain 2 akan dapat mengontrol bola bernomor genap.
Namun, perhatikan bahwa beralih bola untuk pemain 1 tidak akan memengaruhi status bola untuk pemain 2. Selanjutnya, kita perlu menyinkronkan status permainan.
8. Sinkronisasi Status Game
Pada langkah ini, kami akan menyinkronkan status permainan sehingga pemain 1 dan 2 melihat status bola yang sama. Jika orb 1 menyala untuk pemain 1, itu juga harus menyala untuk pemain 2 juga. Di sisi klien, kami akan mengumumkan dan mendengarkan sakelar bola. Untuk mengumumkannya, kami hanya akan memberikan ID orb yang di-toggle.
Sebelum kedua pemanggilan toggleOrb
, tambahkan panggilan socket.emit
berikut.
$(document).ready(function() { ... $('.orb').on('click', function() { ... socket.emit('toggleOrb', id); toggleOrb(id); }); }); ... function clickOrb(i) { ... socket.emit('toggleOrb', i); toggleOrb(i); }
Selanjutnya, dengarkan sakelar bola, dan alihkan bola yang sesuai. Tepat di bawah event listener soket playerId
, tambahkan listener lain untuk event toggleOrb
.
socket.on('toggleOrb', function(i) { document.querySelector("#container-orb" + i).click(); toggleOrb(i); });
Ini menyimpulkan modifikasi kode sisi klien. Periksa kembali apakah kode Anda cocok dengan kode sumber kami untuk Langkah 8.
Sisi server sekarang perlu menerima dan menyiarkan ID bola mati. Di sisi server index.js
, tambahkan pendengar berikut. Listener ini harus ditempatkan langsung di bawah socket disconnect
listener.
socket.on('toggleOrb', function(i) { socket.broadcast.emit('toggleOrb', i); });
Periksa kembali apakah kode Anda cocok dengan kode sumber kami untuk Langkah 8. Sekarang, pemain 1 yang dimuat di satu jendela dan pemain 2 yang dimuat di jendela kedua akan melihat status permainan yang sama. Dengan itu, Anda telah menyelesaikan game realitas virtual multipemain. Kedua pemain, selanjutnya, harus berkolaborasi untuk menyelesaikan tujuan. Produk akhir akan cocok dengan yang berikut ini.
Kesimpulan
Ini mengakhiri tutorial kami tentang membuat game realitas virtual multipemain. Dalam prosesnya, Anda telah menyentuh sejumlah topik, termasuk pemodelan 3-D dalam A-Frame VR dan pengalaman multipemain waktu nyata menggunakan WebSockets.
Membangun dari konsep yang telah kami sentuh, bagaimana Anda memastikan pengalaman yang lebih lancar untuk kedua pemain? Ini dapat mencakup pemeriksaan bahwa status game disinkronkan dan memperingatkan pengguna jika sebaliknya. Anda juga dapat membuat indikator visual sederhana untuk status terminal dan status koneksi pemain.
Mengingat kerangka kerja yang telah kami buat dan konsep yang telah kami perkenalkan, Anda sekarang memiliki alat untuk menjawab pertanyaan ini dan membangun lebih banyak lagi.
Anda dapat menemukan kode sumber yang sudah jadi di sini.