Bagaimana sistem snapshot keadaan-game diimplementasikan untuk game real-time berjaringan?

12

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?

Vittorio Romeo
sumber
4
+1. Ini agak terlalu luas untuk satu pertanyaan, tetapi IMO itu adalah topik yang menarik yang dapat dicakup secara kasar dalam sebuah jawaban.
Kromster
Mengapa Anda tidak menyimpan 1 Snapshot saja (dunia sebenarnya), menyimpan setiap perubahan yang masuk ke negara-dunia biasa DAN menyimpan perubahan dalam daftar atau sesuatu. Kemudian, ketika tiba waktunya untuk mengirim perubahan ke semua klien, cukup kirim konten daftar ke semuanya dan kosongkan daftar, mulai dari nol (perubahan). Mungkin ini tidak sebagus untuk menyimpan 2 snapshot tetapi dengan pendekatan ini Anda tidak perlu khawatir tentang algoritma tentang cara mempercepat 2 snapshot.
tkausl
Sudahkah Anda membaca ini: fabiensanglard.net/quake3/network.php - tinjauan model jaringan gempa 3 mencakup diskusi tentang implementasi.
Steven
Gim seperti apa yang coba dibangun? Pengaturan jaringan sangat tergantung pada jenis permainan yang Anda buat. A RTS tidak berperilaku seperti FPS dalam hal jaringan.
AturSams

Jawaban:

3

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 field1awalnya 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.

Andriy Tylychko
sumber
2

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):

  • Apakah mereka hidup atau mati?
  • Jenis apa mereka?
  • Seberapa banyak kesehatan yang tersisa?
  • Posisi saat ini, rotasi, kecepatan (kecepatan + arah), jalur dalam waktu dekat ...
  • Kegiatan: Menyerang, berjalan, membangun, memperbaiki, menyembuhkan, dll ...
  • efek status buff / debuff
  • dan mungkin statistik lain seperti mana, perisai dan apa yang tidak?

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 listperubahan terbaru (semua perubahan setelah pointer belum dikirim ke pemain itu). Perubahan ditambahkan ke listsaat 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:

  1. Haruskah klien menerima snapshot dengan semua data di tempat pertama? Bagaimana dengan barang-barang di luar garis visi mereka? Bagaimana dengan kabut perang di game RTS? Jika Anda mengirim semua data, klien bisa diretas untuk menampilkan data yang seharusnya tidak tersedia untuk pemain (tergantung pada langkah-langkah keamanan lain yang Anda ambil). Jika Anda hanya mengirim data yang relevan, masalah ini teratasi.
  2. Kapan penting untuk mengirim perubahan daripada mengirim semua informasi? Mempertimbangkan bandwidth yang tersedia pada mesin modern, apakah kita memperoleh sesuatu dari mengirim "delta" daripada mengirim semua informasi, jika demikian kapan?
AturSams
sumber