Menyiapkan Permainan Multiplayer dengan Socket.io
Bahagian 1: Pengenalan
Prasyarat yang Dikehendaki
Dalam tutorial ini, kami akan memberi tumpuan kepada cara menubuhkan pelayan dan menyambungkan permainan anda kepadanya supaya anda dapat membolehkan fungsi multiplayer untuk projek anda melalui perilaku Socket.io. Kami akan membincangkan ciri dan perilaku yang lebih maju dalam hyperPad dan adalah amat disyorkan untuk meninjau beberapa tutorial lain terlebih dahulu dan memahami perisian dengan baik sebelum meneruskan.
Tutorial ini mengandaikan anda mempunyai pemahaman yang baik tentang fungsi asas hyperPad serta pemahaman minimum tentang pemrograman bertulis, kerana kami akan memberi tumpuan kepada penulisan skrip Javascript untuk membina pelayan permainan. Tutorial ini juga mengandaikan sedikit pemahaman tentang rangkaian dan perbezaan antara pelayan dan kliennya.
Tujuan utama tutorial ini adalah untuk mengajar anda apa yang perlu dilakukan untuk menambah fungsi multiplayer ke dalam permainan anda; bukan untuk mencipta permainan itu sendiri. Kami akan meneroka hubungan antara klien-klien Socket.io dan pelayan, dan bagaimana mereka berkomunikasi antara satu sama lain dengan perilaku Socket.io.
Keperluan
Untuk mencipta dan hoskan pelayan, anda memerlukan komputer yang mampu menjalankan Node.JS (Mac, Windows, dll). Pemasangan Node.JS dan menetapkannya untuk tujuan kami akan dibincangkan dalam tutorial ini. Jika anda menghoskan pelayan dalam rangkaian rumah anda, anda mungkin perlu mengubah tetapan pemajuan port gateway anda jika anda ingin mempunyai sambungan masuk dari luar rangkaian lokal anda.
Untuk tutorial ini, kami sudah mencipta permainan demo sederhana. Dalamnya, kami mempunyai dua pemain bermain permainan tag dalam labirin kecil. Kami akan meneroka cara perilaku Socket.io digunakan dalam projek contoh, jadi adalah disyorkan agar anda memuat turun dan membuka projek tersebut dalam hyperPad.
Projek contoh hyperPad yang telah disiapkan boleh dimuat turun di sini: Multiplayer tag tutorial.tap
Tinjauan
Dalam tutorial ini, kami akan membincangkan asas-asas mencipta pelayan untuk permainan anda. Pelayan akan mengendalikan kebanyakan butiran dalam permainan seperti pengiraan mata, lobi permainan, dll. Kemudian, kami akan mencipta perilaku yang menghantar maklumat dari permainan kami ke pelayan dan sebaliknya.
Ini adalah aliran umum permainan kami:
1. Dari menu utama, kami akan menyambung ke pelayan kami dan membolehkan pengguna memutuskan sama ada untuk mencipta atau menyertai permainan.
a. Jika mencipta permainan, muatkan bilik menunggu.
b. Jika menyertai permainan, muatkan senarai bilik permainan yang tersedia.
c. Apabila dua pemain berada dalam bilik menunggu, permainan dilancarkan.
2. Dalam permainan, pelayan secara rawak meletakkan pemain di salah satu daripada empat kawasan dan menetapkan salah seorang daripada mereka sebagai "It".
a. Menandakan pemain lain akan memrandomkan lokasi mereka, menukar status It dan menambah satu mata kepada pemain yang menandakan pemain lain.
b. Jika tiada tag berlaku dalam tempoh masa tertentu, pelayan akan mengurangkan 1 mata dari pemain yang kini It dan menukar status It sebelum meletakkan pemain secara rawak dalam zon spaw.
3. Setelah seorang pemain mencapai jumlah mata tertentu, ia akan memuatkan overlay yang mengisytiharkan pemenang. Kemudian, pemain akan terputus sambungan dari bilik dan kembali ke menu utama.
Bahagian 2: Menyediakan Pelayan
Mencipta pelayan Socket.io memerlukan kami untuk menjalankan aplikasi Javascript menggunakan perpustakaan Socket.io, mendengar sambungan pemain. Socket.io adalah perpustakaan rangkaian Javascript yang menyederhanakan banyak aspek pembangunan aplikasi berasaskan rangkaian untuk kami. Butiran lanjut akan menyusul.
Muat Turun dan Pasang Node.JS
Untuk memulakan, pergi ke Node.JS dan muat turunnya ke komputer (Mac, Windows, dll.) yang anda ingin hoskan pelayan. Setelah selesai memuat turun, jalankan pemasang dan ikuti arahan. Semua pilihan semasa pemasangan boleh ditinggalkan sebagai default. Node.JS memberi kami keupayaan untuk menjalankan aplikasi Javascript secara bebas tanpa memerlukan pelayar web.
Muat Turun dan Jalankan Contoh Pelayan
Seterusnya, muat turun Contoh Pelayan Multiplayer tutorial ini di GitHub:
https://github.com/hyperPad/multiplayerServerExample
Klik butang "Clone or download" dan pilih "Download ZIP". Ini akan memuat turun salinan kod untuk contoh pelayan.
Ekstrak ZIP yang dimuat turun. Di dalam anda akan menemui beberapa fail kecil, tetapi fail yang paling ketara di sini adalah fail "index.js" yang merupakan kod untuk pelayan kami. Selanjutnya, buka baris arahan/terminal anda dalam folder contoh pelayan.
Ketik "npm install" dan tekan enter, kemudian biarkan ia berjalan. npm adalah pengurus pakej yang membaca fail package.json dan memuat turun pakej yang diperlukan untuk pelayan kami. (Termasuk Socket.io!)
Apabila arahan selesai, kami mempunyai semua yang kami perlukan untuk menjalankan pelayan. Dalam terminal, taip "node ." dan pelayan akan bermula.
Itu sahaja! Pelayan kami kini mendengar pada port 3000 bagi sambungan Socket.io masuk.
"node ." melancarkan Node.JS untuk direktori semasa, di mana ia akan mencari fail indeks direktori dan menjalankannya. Secara default, ini adalah fail Javascript "index.js", di mana kebanyakan kod pelayan kami terletak dan akan dianalisis sepanjang tutorial ini.
Biarkan terminal terbuka, kerana menutupnya juga akan menutup pelayan. Anda akan melihatnya mencetak mesej apabila pemain bersambung atau terputus, dan apabila bilik dicipta atau dimusnahkan, serta acara lain.
Nota: Untuk membolehkan sambungan masuk dari luar rangkaian rumah, anda mungkin perlu membuka port 3000 pada gerbang rumah anda. Proses ini berbeza dari satu rangkaian ke rangkaian lain, tetapi panduan biasanya boleh didapati dengan melakukan carian internet untuk panduan pemajuan port untuk modem/router rumah anda. Anda mungkin perlu menutup dan memulakan semula pelayan Node.JS apabila anda mengubah tetapan pemajuan port.
Bahagian 3: Menyambung ke Pelayan
Setelah anda mempunyai asas pelayan yang dimulakan, kami sekarang boleh menyambung ke dalam permainan hyperPad. Ini harus disediakan sebagai perkara pertama yang berlaku dalam projek anda tidak kira apa scene anda (kerana segala-galanya perlu berkomunikasi dengan pelayan). Adalah lebih baik untuk melampirkan perilaku ini kepada objek di Lapisan Global supaya ia terpakai di semua scene.
Dalam projek contoh, label Lapisan Global "Pelayan" mengandungi yang di atas. (Muat turun projek boleh didapati dalam Bahagian 1 tutorial ini di bawah seksyen Keperluan.)
Kedua-dua perilaku ini benar-benar adalah semua yang anda perlukan untuk menyambung ke pelayan. Pertama sekali, di bawah tab khusus, ambil perilaku Klien Socket.io dan seret ke bawah. Di sini, anda akan memasukkan URL pelayan anda di tab URL dalam tetingkap propertinya. Dalam gambar di atas, URL pelayan kami berada pada "http://192.168.0.191:3000", termasuk protokol dan port. Anda akan ingin mengubah ini untuk mencocokkan URL pelayan anda atau ia mungkin tidak berfungsi apabila anda mula permainan.
Sekarang kami mempunyai maklumat pelayan yang kami inginkan, tetapi kami masih perlu menyambung ke pelayan. Oleh itu, semua yang kami perlukan ialah perilaku Sambung ke Socket. Seret salah satu ke bawah, buka propertinya dan pilih klien dalam kotak kosong dan setkan sifat Fungsi kepada "Sambung".
Sekarang, apabila projek kami di muatkan, kami akan secara automatik menyambung ke pelayan kami.
Bahagian 4: Mencipta dan Menyertai Bilik
Langkah seterusnya adalah untuk mencipta lobi permainan kami di mana pemain boleh menyertai dan bermain bersama.
Berikut adalah skrin Menu Utama kami yang sederhana. Yang perlu dilakukan ialah ketuk salah satu butang untuk sama ada memulakan bilik atau mencari bilik yang tersedia.
Mencipta Bilik
Marilah kita lihat bagaimana kami berkomunikasi dengan pelayan untuk mencipta bilik kami.
Agak mudah bukan? Jadi untuk membahas ini dengan cepat, setelah kami menyentuh butang kami, kami akan diprompt untuk menaip nama bilik. Setelah itu selesai, nama itu dikeluarkan kepada pelayan di mana bilik akan dicipta dan kami memuatkan scene bilik menunggu.
Sejak dari sini, kami akan sering menggunakan Emit ke Socket. Itu kerana ia adalah cara utama kami menghantar data kepada pelayan. Fikirkan ia sebagai "versi dalam talian" bagi Pesanan Siaran dan Terima Mesej (Emit ke Socket melakukan kedua-dua tindakan sekaligus).
Sekarang, kami perlu menambah beberapa maklumat kepada pelayan kami supaya ia akan mencipta bilik sebaik sahaja ia menerima mesej daripada perilaku Emit kami.
socket.on('createRoom', (roomName, callback) => {
Baris ini mencipta acara soket (sebagai ekspresi lambda) di pelayan, mendengar Emits dengan acara 'createRoom'.
Parameter pertama adalah nilai yang kami pass melalui perilaku Emit ke Socket, dalam kes ini nama bilik yang ditaip oleh pengguna. Di sini, kami menamakan parameter itu 'roomName'.
Parameter kedua adalah fungsi yang kami panggil kemudian dalam acara soket untuk memaklumkan klien. Perilaku Emit ke Socket hanya akan meneruskan pelaksanaan jika fungsi callback dipanggil di pelayan. Di sini, kami menamakan parameter itu 'callback'.
const room = {
id: uuid(),
name: roomName,
sockets: []
};
Ini akan mencipta satu contoh struktur yang akan mengandungi maklumat penting tentang bilik.
'id' akan menjadi pengenal unik yang dijana oleh fungsi utiliti 'uuid()'. Ini akan membantu kami mengenal pasti secara khusus bilik ini berbanding dengan banyak bilik lain yang dicipta kemudian.
'name' akan ditetapkan kepada nama yang ditaip pengguna sebelum ini.
'sockets' akan diinisialisasikan sebagai array kosong. Kemudian, ini akan menyimpan jejak soket pemain yang disambungkan ke bilik itu.
rooms[room.id] = room;
'rooms' adalah senarai global bilik yang sedang aktif. Oleh kerana kami mencipta bilik baru, kami akan menyimpannya dalam senarai dengan IDnya.
joinRoom(socket, room);
Ini akan memanggil fungsi global 'joinRoom' (baris 29), yang akan menambah soket pemain ke dalam array 'sockets' bilik tersebut. Oleh kerana pemain mencipta bilik, ini juga akan membuat mereka menyertainya juga.
callback();
});
Akhir sekali, kami memanggil fungsi callback supaya klien dimaklumkan bahawa bilik telah dicipta. Ini akan membolehkan Emit ke Socket meneruskan pelaksanaan, yang akan memuatkan scene Bilik Menunggu seterusnya. Ini juga menandakan akhir acara soket 'createRoom'.
Menyertai Bilik
Menyertai bilik adalah sedikit berbeza kerana kami perlu mengakses maklumat daripada scene yang berbeza dari butang.
Untuk bilik yang ingin disertai, kami telah meletakkan permulaan perilaku kami di lapisan global di samping tempat kami menyambung ke pelayan. Dengan cara ini kami boleh mengakses maklumat daripada mana-mana scene kami, dalam kes ini senarai bilik kami. Untuk butang, mengetuknya hanya akan memuatkan pemain ke dalam scene senarai bilik.
Di sini kami mempunyai perilaku kedua untuk berkomunikasi dengan pelayan. Acara Socket boleh dianggap sebagai Terima Mesej kerana ia hanya akan diaktifkan setelah mesej yang ditetapkan disiarkan dari pelayan.
Cara terbaik untuk berfikir ketika menggunakan Acara Socket dan Emit ke Socket adalah bahwa Acara Socket hanya bertindak balas kepada maklumat yang datang dari pelayan, manakala Emit ke Socket dipanggil sebagai reaksi kepada tindakan yang dilakukan secara tempatan.
Untuk logik kami, setelah kami menyambung ke pelayan, kami akan Emit permintaan 'getRoomNames' untuk mendapatkan sebarang nama bilik yang tersedia.
Seterusnya, kami akan menetapkan label yang pemain boleh ketuk untuk masuk.
Ini adalah scene Senarai Bilik kami. Di sini adalah tempat sebarang bilik permainan yang tersedia akan dimuat dan dipaparkan. Dengan hanya mengetuk nama bilik akan memuatkan pemain itu. Terima kasih kepada perilaku kami sebelumnya, senarai itu akan secara automatik memuatkan sebarang bilik terbuka dan menampilkan label nama bilik di skrin. Sekiranya tiada apa-apa yang muncul, kami mempunyai butang Segar yang hanya mengulang perilaku untuk memuatkan mereka sekali lagi.
Di sini kami mempunyai logik untuk menetapkan senarai. Kami tidak akan membincangkan terlalu banyak di sini kerana kami hanya ingin tahu bagaimana ini berhubung dengan pelayan kami.
Untuk memulakan, kami mempunyai perilaku Emit ke Socket. Ini memanggil pelayan untuk menghantar maklumat tentang bilik yang tersedia. Dari situ, kami mempunyai perilaku Ambil Nilai Array. Semua data yang datang dari pelayan akan dihantar sebagai Array dan maklumat yang diperlukan akan berada pada nilai pertama. Oleh itu, kami menetapkan Ambil Nilai Array kami untuk mengambil nilai pada indeks 0. Dari situ, perilaku kami akan mengekstrak data tersebut dan mencipta label untuk setiap bilik, memunculkannya pada skrin kami.
Seterusnya, kami akan memeriksa objek yang perlu kami ketuk, dalam scene kami ialah label yang dipanggil Nama Bilik.
Text ini berfungsi sebagai butang kami apabila tiba masanya, tetapi kami masih perlu melampirkan ID bilik yang kami ingin sambungkan. Untuk melakukan itu, kami perlu mendapatkan ID bilik, dan kami melakukannya dengan perilaku Ambil Atribut dan menetapkannya kepada dinamik. Kemudian, kami menghantar ke pelayan bahawa kami ingin menyertai bilik ini dan memuatkan ke dalam bilik menunggu.
socket.on('joinRoom', (roomId, callback) => {
Ini adalah titik masuk untuk acara soket 'joinRoom'. Nilai untuk parameter pertama adalah ID bilik yang kami ingin sertai yang telah dikeluarkan kepada pelayan. Di sini, kami menamakan parameter itu 'roomId'.
const room = rooms[roomId];
joinRoom(socket, room);
Dengan menggunakan roomId yang diberikan oleh klien, kami dapat mencari instans bilik yang betul di pelayan. Dengan itu, kami memanggil fungsi global 'joinRoom' (baris 29) dengan soket pemain yang ingin bersambung, dan instans bilik itu sendiri. Kami akan melihat 'joinRoom' dalam sedikit masa.
callback();
});
Akhirnya, kami memanggil fungsi callback untuk memaklumkan kepada klien bahawa mereka boleh meneruskan untuk memuatkan scene Bilik Menunggu, menandakan akhir acara soket 'joinRoom'.
Jadi, apa sebenarnya yang berlaku di dalam fungsi 'joinRoom'? Mari kita lihat.
room.socket.push(socket);
Seperti yang dinyatakan sebelum ini, array anggota 'room.socket' menyimpan jejak soket yang disambungkan dalam bilik. Baris ini melakukan perkara itu dengan menambah soket ke dalam array.
socket.join(room.id, () => {
socket.roomId = room.id;
console.log(socket.id, "Joined", room.id);
});
Ini adalah panggilan rasmi untuk menyambungkan klien ke bilik. Pertama kami memberitahu soket untuk menyertai sebuah bilik dengan IDnya. Setelah itu selesai, panggilan balik berikutnya dipanggil, di mana kami melampirkan ID bilik ke soket. Terakhir, kami mencetak dalam konsol bahawa seorang pemain telah menyertai bilik!
Bilik Menunggu
Bilik menunggu adalah sekadar sebuah scene yang kami muatkan ke dalam pemain semasa mereka menunggu pemain lain untuk menyertai, atau untuk permainan bermula.
Setelah memasuki bilik, kami akan menghantar mesej 'sedia' ke pelayan. Setelah pelayan menerima dua daripada ini, ia akan menghantar mesej bahawa ia sedang memulakan permainan ('initGame'). Kami mengambil mesej itu dengan Acara Soket kami dan dengan itu, memuatkan tahap permainan kami. Untuk perilaku kami, itu sahaja. Anda boleh menambah butang yang akan memutuskan sambungan anda dari bilik dan menghantar anda kembali ke menu utama jika anda mahu.
Di bahagian pelayan, mari kita analisis kod untuk melihat apa yang terjadi di sana.
Ini adalah acara 'ready' yang dipanggil apabila klien telah menyertai bilik dan bersedia untuk bersambung.
const room = rooms[socket.roomId];
Oleh kerana kami telah melampirkan ID bilik pada soket, kami dapat memperoleh bilik untuk memeriksa jika permainan boleh dimulakan.
if (room.sockets.length == 2) {
Di sini kami memeriksa sama ada kini terdapat dua pemain yang menunggu di dalam bilik. Katakan itu benar sekarang, dan kami meneruskan untuk memulakan permainan.
for (const client of room.sockets) {
client.emit('initGame');
}
Kini terdapat dua pemain, kami mengiterasi setiap soket dan menghantar acara 'initGame' supaya setiap klien memuatkan scene Tahap, seperti yang ditunjukkan terdahulu.
Bahagian 5: Permainan
Sekarang, untuk masuk ke dalam substansinya. Ini adalah di mana 90% daripada kerja kami akan pergi. Di bawah adalah tahap permainan yang kami reka untuk tutorial ini.
Sebelum kami masuk ke situ, kami mempunyai label bertajuk "Logik Permainan", mari kita buka dan lihat.
Wow, itu banyak perilaku! Jangan risau, ini semata-mata bagaimana kami menempatkan pemain kami ke dalam permainan. Mari kita lihat dengan lebih dekat;
Kami memulakan dengan perilaku Emit ke Socket yang memberitahu pelayan permainan kami telah bermula, dengan acara 'startGame'. Kemudian kami mengambil array yang dikembalikan pelayan, mengambil nilai pertama dan dengan Mengambil Nilai Kamus, kami menangkap pelbagai atribut yang diperlukan oleh objek kami. Pohon terpisah melakukan perkara yang sama, kecuali untuk pemain lawan. Akhirnya, kami menyebarkan mesej 'init' kepada objek pemain kami untuk memulakan.
Mari kita lihat apa yang berlaku di bahagian pelayan apabila kami menghantar acara 'startGame'.
Bahagian pertama acara soket ini anda lihat di sini adalah menetapkan beberapa nilai awal pada setiap klien, kemudian menambahkan mana-mana klien yang bukan klien yang menghantar ke dalam senarai peminat tempatan 'others'.
Dalam bahagian kedua, senarai kamus tempatan yang dipanggil 'ack' dibuat. Di dalamnya, kami mempunyai maklumat tentang diri kami, dan klien lain. Kami kemudian menghantar maklumat itu kembali kepada klien dengan menghantar kamus kami 'ack' kepada fungsi panggilan balik, yang menjadi nilai hasil perilaku Emit ke Socket yang dipanggil.
Selepas itu, panggilan timeout selama 5 saat dibuat untuk akhirnya memulakan pusingan, kini bahawa semua orang telah melalui semua maklumat yang mereka perlukan untuk bermain permainan. Fungsi 'beginRound' (baris 99) mengawal logik khusus permainan untuk projek ini. Kami tidak akan mengulas terlalu mendalam mengenainya, tetapi secara asasnya ia mengawal di mana untuk meletakkan pemain, memeriksa skor, serta memberitahu klien siapa yang menjadi It.
Seperti yang dinyatakan sebelum ini, mesej 'init' dipanggil pada label "Logik Permainan" apabila semuanya sudah bersedia untuk pergi. Dalam objek pemain, kami akan sekarang melihat pada perilaku di mana ia menerima mesej 'init'.
Di sini anda dapat melihat kami mempunyai beberapa pohon perilaku pada pemain kami. Pertama, kami akan memulakan dengan pohon kiri atas.
Ini adalah perilaku yang memulakan hampir segala-galanya dalam permainan kami.
Pertama, kami menerima mesej 'init' yang dihantar dari label "Logik Permainan". Dari sana, kami mengambil ID server objek kami dan menghidupkan salah satu Acara Soket kami serta menetapkan skrin permainan kami supaya kami dapat dengan tepat mengikuti watak kami.
Penyelarasan Pergerakan
Di sini adalah salah satu perilaku paling penting kami. Pohon kecil ini direka untuk mengemas kini kedudukan pemain kami pada pelayan setiap kali kami menggerakkan joystick. Anda mungkin telah perasan bahawa ia juga merujuk kepada beberapa nilai kamus. Kami mendapatkan nilai-nilai tersebut dari perilaku kamus yang berdiri sendiri yang mengandungi kedudukan X dan Y pemain kami.
Mari kita lihat acara 'moved' di pelayan.
data = JSON.parse(data);
Kamusa, apabila dihantar dari klien kepada pelayan, perlu diparse untuk membaca data dengan mudah. Ini kerana kamus dalam hyperPad dienkod ke dalam struktur JSON apabila dikeluarkan kepada pelayan. Baris ini memparse struktur string JSON dan menyimpan senarai kamus kembali ke variabel tempatan 'data' yang sama.
socket.x = data.x;
socket.y = data.y;
Di sini, kami mengemas kini kedudukan X dan Y yang disimpan pada soket dengan nilai baru yang diberi oleh klien.
for (const client of room.sockets) {
if (client == socket) {
continue;
}
client.emit(socket.id, {
x: socket.x,
y: socket.y,
score: socket.score,
isIt: socket.isIt
});
}
Seterusnya, kami mengiterasi melalui semua klien untuk mengemas kini mereka tentang kedudukan baru kami dan butiran lain, tidak termasuk diri kami sendiri (iaitu, klien yang mengeluarkan tidak perlu tahu kedudukan mereka sendiri).
Pohon ini mengawal sebahagian besar permainan kami. Kami menggunakan Acara Soket apabila pelayan memeriksa pemain yang ditandakan sebagai It, yang dikeluarkan oleh 'beginRound' (baris 99) di pelayan.
Kemudian, kami mengambil ID server objek kami dari array dan menggunakan Nilai Kamus untuk memecahkannya kepada pelbagai kepingan data yang mengandungi objek kami. Dari situ, kami mengambil Skor kami, sama ada kami ditandakan sebagai It, serta posisi x dan y objek kami kemudian menggunakan mereka pada atribut objek itu. Sebahagian besar perilaku adalah untuk menetapkan dan mengawal UI dalam permainan kami.
Kesimpulan
Ini adalah banyak perkara yang perlu diambil di sini, tetapi semoga jika anda menjadi jauh hingga ke sini anda seharusnya mempunyai pemahaman tentang bagaimana untuk menggunakan perilaku Socket.io untuk mencipta pengalaman multiplayer untuk pemain anda.
Cubalah sendiri! Ambil permainan yang sudah anda cipta dan cuba berikan beberapa fungsionan dalam talian, seperti scene papan pendahulu skor tertinggi yang menghubungkan kepada pelayan dan meminta senarai 10 skor tertinggi dengan nama pemain untuk dipaparkan.
Adalah sukar untuk mengajar bahasa skrip seperti Javascript dalam satu artikel. Beruntung, jika anda menghadapi masalah terdapat banyak sumber lain yang dapat membantu anda dengan penulisan aplikasi Javascript untuk Node.JS dan Socket.io;
Pelajari Javascript - https://developer.mozilla.org/bm/docs/Web/JavaScript
Pelajari Socket.io - https://socket.io/docs/
Pelajari Node.JS - https://nodejs.org/en/docs/

