Cara Membuat Game Endless Runner Dalam Virtual Reality (Bagian 2)

Diterbitkan: 2022-03-10
Ringkasan cepat Jika Anda pernah bertanya-tanya bagaimana game dengan dukungan tanpa keyboard untuk headset VR dibuat, maka tutorial ini menjelaskan apa yang Anda cari. Inilah cara Anda juga dapat menghidupkan game VR dasar yang berfungsi.

Di 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.)
Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

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 templat bergerak menuju pemain
Pohon template bergerak menuju pemain (Pratinjau besar)

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.

Pohon templat untuk rintangan
Pohon templat untuk rintangan (Pratinjau besar)

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.

Pohon templat bergerak menuju pemain
Pohon templat bergerak ke arah pemainPohon templat bergerak ke arah pemain (Pratinjau besar)

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.

Bagian 1 produk jadi
Bagian 1 produk jadi (Pratinjau besar)

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 
Memanggil addTreeTo secara manual
Aktifkan addTreeTo secara manual (Pratinjau besar)

Sekarang, Anda akan menulis algoritme yang memunculkan pohon secara acak:

  1. Pilih jalur secara acak (yang belum dipilih, untuk langkah waktu ini);
  2. Memunculkan pohon dengan kemungkinan tertentu;
  3. 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.

Pemijahan pohon secara acak
Pohon bertelur secara acak (Pratinjau besar)

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.