Saya ingin membuat game multiplayer real-time client-server sederhana sebagai proyek untuk kelas jaringan saya.
Saya telah membaca banyak tentang model jaringan multipemain waktu-nyata dan saya memahami hubungan antara klien dan server serta teknik kompensasi-lag.
Apa yang ingin saya lakukan adalah sesuatu yang mirip dengan model jaringan Quake 3: pada dasarnya, server menyimpan snapshot dari keseluruhan kondisi permainan; setelah menerima input dari klien, server membuat snapshot baru yang mencerminkan perubahan. Kemudian, ia menghitung perbedaan antara snapshot baru dan yang terakhir dan mengirimkannya ke klien, sehingga mereka bisa sinkron.
Pendekatan ini tampaknya sangat solid bagi saya - jika klien dan server memiliki koneksi yang stabil, hanya jumlah data minimum yang diperlukan yang akan dikirim untuk menjaga mereka tetap sinkron. Jika klien tidak sinkron, snapshot penuh juga dapat diminta.
Namun, saya tidak dapat menemukan cara yang baik untuk menerapkan sistem snapshot. Saya merasa sangat sulit untuk menjauh dari arsitektur pemrograman pemain tunggal dan berpikir tentang bagaimana saya bisa menyimpan status permainan sedemikian rupa sehingga:
- Semua data dipisahkan dari logika
- Perbedaan dapat dihitung antara snapshot dari status game
- Entitas game masih dapat dengan mudah dimanipulasi melalui kode
Bagaimana kelas snapshot diterapkan? Bagaimana entitas dan datanya disimpan? Apakah setiap entitas klien memiliki ID yang cocok dengan ID di server?
Bagaimana perbedaan snapshot dihitung?
Secara umum: bagaimana sistem snapshot keadaan-game diimplementasikan?
sumber
Jawaban:
Anda dapat menghitung snapshot delta (perubahan ke kondisi yang disinkronkan sebelumnya) dengan menjaga dua instance snapshot: yang sekarang dan yang terakhir disinkronkan.
Ketika input klien tiba, Anda memodifikasi snapshot saat ini. Kemudian ketika saatnya untuk mengirim delta ke klien, Anda menghitung snapshot terakhir yang disinkronkan dengan satu bidang-per-bidang saat ini (secara rekursif) dan menghitung dan membuat serial delta. Untuk serialisasi, Anda dapat menetapkan ID unik untuk setiap bidang dalam ruang lingkup kelasnya (sebagai lawan ruang lingkup keadaan global). Klien dan server harus berbagi struktur data yang sama untuk keadaan global sehingga klien memahami apa ID tertentu diterapkan.
Kemudian, ketika delta dihitung, Anda mengkloning kondisi saat ini dan menjadikannya yang terakhir disinkronkan, jadi sekarang Anda memiliki kondisi saat ini dan yang terakhir disinkronkan tetapi berbeda sehingga Anda dapat mengubah kondisi saat ini dan tidak mempengaruhi yang lain.
Pendekatan ini dapat lebih mudah diimplementasikan, terutama dengan bantuan refleksi (jika Anda memiliki kemewahan), tetapi bisa lambat, bahkan jika Anda sangat opitimise bagian refleksi (dengan membangun skema data Anda untuk cache sebagian besar panggilan refleksi). Terutama karena Anda perlu membandingkan dua salinan negara berpotensi besar. Tentu saja tergantung bagaimana Anda menerapkan perbandingan dan bahasa Anda. Ini bisa cepat di C ++ dengan komparator hardcoded tetapi tidak begitu fleksibel: setiap perubahan struktur keadaan global Anda memerlukan modifikasi komparator ini, dan perubahan ini sangat sering pada tahap proyek awal.
Pendekatan lain adalah dengan menggunakan bendera kotor. Setiap kali input klien tiba, Anda menerapkannya pada salinan tunggal negara global Anda dan menandai bidang yang sesuai sebagai kotor. Kemudian ketika saatnya untuk menyinkronkan klien Anda membuat serial bidang kotor (secara rekursif) menggunakan ID unik yang sama. Kelemahan (kecil) adalah bahwa kadang-kadang Anda mengirim lebih banyak data dari yang dibutuhkan: misalnya
int field1
awalnya 0, kemudian ditetapkan 1 (dan ditandai kotor) dan setelah itu ditugaskan 0 lagi (tetapi tetap kotor). Manfaatnya adalah memiliki struktur data hierarkis yang besar Anda tidak perlu menganalisis sepenuhnya untuk menghitung delta, hanya jalur kotor.Secara umum, tugas ini bisa sangat rumit, tergantung seberapa fleksibelnya solusi final. Misalnya Unity3D 5 (akan datang) akan menggunakan atribut untuk menentukan data yang harus disinkronkan secara otomatis ke klien (pendekatan yang sangat fleksibel, Anda tidak perlu melakukan apa pun kecuali menambahkan atribut ke bidang Anda) dan kemudian menghasilkan kode sebagai langkah post-build. Lebih detail di sini.
sumber
Pertama, Anda perlu tahu cara merepresentasikan data relevan Anda dengan cara yang sesuai protokol. Ini tergantung pada data yang relevan dengan game. Saya akan menggunakan game RTS sebagai contoh.
Untuk keperluan jaringan, semua entitas dalam game disebutkan (misalnya pickup, unit, bangunan, sumber daya alam, destructible).
Para pemain harus memiliki data yang relevan dengan mereka (misalnya semua unit yang terlihat):
Pada awalnya pemain harus menerima status penuh sebelum dia dapat memasuki permainan (atau sebagai alternatif semua informasi yang relevan dengan pemain itu).
Setiap unit memiliki ID integer. Atribut dihitung dan karenanya memiliki pengidentifikasi integral juga. ID unit tidak harus sepanjang 32 bit (bisa jadi jika kita tidak hemat). Bisa jadi 20 bit (meninggalkan 10 bit untuk atribut). ID unit harus unik, bisa saja ditugaskan oleh penghitung ketika unit instantiated dan / atau ditambahkan ke dunia game (bangunan dan sumber daya dianggap sebagai unit tidak bergerak dan sumber daya dapat diberi ID ketika peta dimuat).
Server menyimpan keadaan global saat ini. Keadaan terbaru setiap pemain diwakili oleh pointer ke
list
perubahan terbaru (semua perubahan setelah pointer belum dikirim ke pemain itu). Perubahan ditambahkan kelist
saat mereka terjadi. Setelah server selesai mengirimkan pembaruan terakhir, server dapat mulai mengulangi daftar: server memindahkan pointer pemain di sepanjang daftar ke ekornya, mengumpulkan semua perubahan di sepanjang jalan dan menempatkannya dalam buffer yang akan dikirim ke pemain (yaitu format protokol dapat berupa sesuatu seperti ini: unit_id; attr_id; new_value) Unit baru juga dianggap perubahan dan dikirim dengan semua nilai atributnya ke pemain yang menerima.Jika Anda tidak menggunakan bahasa dengan pengumpul sampah, Anda perlu mengatur pointer malas yang akan tertinggal dan kemudian mengejar pointer pemain yang paling ketinggalan zaman dalam daftar, membebaskan benda-benda di sepanjang jalan. Anda dapat mengingat pemain mana yang paling ketinggalan jaman di dalam tumpukan prioritas atau hanya mengulanginya dan membebaskan sampai pointer malas sama (yaitu menunjuk ke item yang sama dengan salah satu pointer pemain).
Beberapa pertanyaan yang tidak Anda ajukan dan menurut saya menarik adalah:
sumber