Cara mendesain sistem replay


75

Jadi bagaimana saya mendesain sistem replay?

Anda mungkin mengetahuinya dari game tertentu seperti Warcraft 3 atau Starcraft di mana Anda dapat menontonnya lagi setelah dimainkan.

Anda berakhir dengan file replay yang relatif kecil. Jadi pertanyaan saya adalah:

  • Bagaimana cara menyimpan data? (format khusus?) (ukuran file kecil)
  • Apa yang harus diselamatkan?
  • Bagaimana membuatnya generik sehingga bisa digunakan di gim lain untuk merekam periode waktu (dan bukan pertandingan yang lengkap misalnya)?
  • Memungkinkan untuk maju dan mundur (WC3 tidak bisa mundur sejauh yang saya ingat)

3
Meskipun jawaban di bawah ini memberikan banyak wawasan yang berharga, saya hanya ingin menekankan pentingnya mengembangkan permainan / mesin Anda untuk menjadi sangat deterministik ( en.wikipedia.org/wiki/Deterministic_algorithm ), karena sangat penting untuk mencapai tujuan Anda.
Ari Patrick

2
Perhatikan juga bahwa mesin fisika tidak deterministik (Havok mengklaim itu ...) sehingga solusi untuk hanya menyimpan input dan cap waktu akan menghasilkan hasil yang berbeda setiap kali jika game Anda menggunakan fisika.
Samaursa

5
Sebagian besar mesin fisika bersifat deterministik selama Anda menggunakan stempel waktu tetap, yang seharusnya Anda lakukan. Saya akan sangat terkejut jika Havok tidak. Non-determinisme cukup sulit didapat di komputer ...

4
Deterministik berarti input yang sama = output yang sama. Jika Anda memiliki float di satu platform dan menggandakannya di platform lain (misalnya), atau dengan sengaja menonaktifkan implementasi standar IEEE floating point Anda, itu berarti Anda tidak menjalankan dengan input yang sama, bukan karena itu tidak deterministik.

3
Apakah ini saya, atau apakah pertanyaan ini mendapat hadiah setiap minggu?
Bebek Komunis

Jawaban:


39

Artikel yang luar biasa ini membahas banyak masalah: http://www.gamasutra.com/view/feature/2029/developing_your_own_replay_system.php

Beberapa hal yang disebutkan dan dilakukan dengan baik oleh artikel ini:

  • gim Anda harus deterministik.
  • ia merekam keadaan awal sistem game pada frame pertama, dan hanya input pemain selama gameplay.
  • mengkuantisasi input untuk menurunkan # bit. Yaitu. mewakili float dalam berbagai rentang (mis. [0, 1] atau [-1, 1] jangkauan dalam bit lebih sedikit. Input yang dihitung harus diperoleh selama bermain game yang sebenarnya juga.
  • gunakan bit tunggal untuk menentukan apakah aliran input memiliki data baru. Karena beberapa aliran tidak akan sering berubah, ini mengeksploitasi koherensi temporal dalam input.

Salah satu cara untuk lebih meningkatkan rasio kompresi untuk sebagian besar kasus adalah dengan memisahkan semua aliran input Anda dan sepenuhnya menyandiaksakan panjangnya secara terpisah. Ini akan menjadi kemenangan atas teknik pengkodean delta jika Anda menyandikan proses Anda dalam 8-bit dan proses itu sendiri melebihi 8 frame (sangat mungkin kecuali permainan Anda adalah tombol hidung belang yang nyata). Saya telah menggunakan teknik ini dalam permainan balapan untuk mengompres input 8 menit dari 2 pemain saat balapan di trek turun menjadi hanya beberapa ratus byte.

Dalam hal membuat sistem seperti itu dapat digunakan kembali, saya telah membuat sistem replay berurusan dengan stream input generik, tetapi juga menyediakan kait untuk memungkinkan logika spesifik game untuk menggerakkan input keyboard / gamepad / mouse ke stream ini.

Jika Anda ingin memutar cepat atau mencari secara acak, Anda dapat menyimpan pos pemeriksaan (gamestate penuh Anda) setiap N bingkai. N harus dipilih untuk meminimalkan ukuran file replay dan juga memastikan waktu yang pemain tunggu untuk masuk akal ketika negara diputar ulang ke titik yang dipilih. Salah satu cara untuk mengatasi ini adalah untuk memastikan bahwa pencarian acak hanya dapat dilakukan ke lokasi pos pemeriksaan yang tepat ini. Memundurkan ulang adalah masalah mengatur kondisi permainan ke pos pemeriksaan tepat sebelum bingkai yang dimaksud, lalu memutar ulang input hingga Anda mencapai frame saat ini. Namun, jika N terlalu besar, Anda bisa memasang setiap beberapa frame. Salah satu cara untuk menghaluskan halangan ini adalah dengan melakukan pra-cache frame yang tidak sinkron antara 2 pos pemeriksaan sebelumnya saat Anda memutar ulang bingkai yang di-cache dari wilayah pos pemeriksaan saat ini.


jika ada RNG yang terlibat maka sertakan hasil RNG tersebut di sungai
ratchet freak

1
@ scratchet freak: Dengan penggunaan PRNG yang deterministik Anda dapat bertahan dengan hanya menyimpan bijinya selama pos-pos pemeriksaan.
NonNumerik

22

Selain solusi "pastikan penekanan tombol dapat diputar ulang", yang bisa sangat sulit, Anda bisa merekam seluruh kondisi permainan di setiap frame. Dengan sedikit kompresi cerdas Anda dapat menekannya secara signifikan. Beginilah cara Braid menangani kode rewinding-waktunya dan berfungsi dengan baik.

Karena bagaimanapun Anda perlu memeriksa titik pemeriksaan untuk memutar ulang, Anda mungkin ingin mencoba mengimplementasikannya dengan cara sederhana sebelum menyulitkan.


2
+1 Dengan beberapa kompresi pintar, Anda benar-benar dapat menurunkan jumlah data yang perlu Anda simpan (misalnya, jangan menyimpan status jika tidak berubah dibandingkan dengan keadaan terakhir yang Anda simpan untuk objek saat ini) . Saya sudah mencoba ini dengan fisika dan bekerja dengan sangat baik. Jika Anda tidak memiliki fisika dan tidak ingin memutar ulang permainan lengkap, saya akan pergi dengan solusi Joe hanya karena itu akan menghasilkan file sekecil mungkin dalam hal ini jika Anda ingin mundur juga, Anda dapat menyimpan hanya ndetik-detik terakhir dari permainan.
Samaursa

@Samaursa - Jika Anda menggunakan pustaka kompresi standar (misalnya gzip) maka Anda akan mendapatkan kompresi yang sama (mungkin lebih baik) tanpa perlu melakukan hal-hal seperti memeriksa secara manual untuk melihat apakah keadaan telah berubah atau tidak.
Justin

2
@ Kragen: Tidak sepenuhnya benar. Pustaka kompresi standar tentu baik tetapi seringkali tidak dapat memanfaatkan pengetahuan khusus domain. Jika Anda dapat membantu mereka sedikit, dengan menempatkan data serupa berdekatan dan menghapus hal-hal yang benar-benar tidak berubah, Anda dapat mengelompokkannya secara substansial.
ZorbaTHut

1
@ZorbaTHut Secara teori ya, tetapi dalam praktiknya apakah ini sepadan dengan usaha?
Justin

4
Apakah itu sepadan dengan usaha tergantung sepenuhnya pada berapa banyak data yang Anda miliki. Jika Anda memiliki RTS dengan ratusan atau ribuan unit, itu mungkin penting. Jika Anda perlu menyimpan replay dalam memori seperti Braid, itu mungkin penting.

21

Anda dapat melihat sistem Anda seolah-olah terdiri dari serangkaian status dan fungsi, di mana fungsi f[j]dengan input x[j]mengubah status sistem s[j]menjadi status s[j+1], seperti:

s[j+1] = f[j](s[j], x[j])

Keadaan adalah penjelasan seluruh dunia Anda. Lokasi pemain, lokasi musuh, skor, amunisi yang tersisa, dll. Semua yang Anda butuhkan untuk menggambar bingkai permainan Anda.

Fungsi adalah segala sesuatu yang dapat mempengaruhi dunia. Perubahan bingkai, penekanan tombol, paket jaringan.

Input adalah data fungsi yang diambil. Perubahan bingkai mungkin memerlukan waktu sejak frame terakhir berlalu, penekanan tombol mungkin menyertakan tombol yang sebenarnya ditekan, serta apakah tombol shift ditekan atau tidak.

Demi penjelasan ini, saya akan membuat asumsi berikut:

Asumsi 1:

Jumlah status untuk menjalankan game tertentu jauh lebih besar dari jumlah fungsi. Anda mungkin memiliki ratusan ribu status, tetapi hanya beberapa fungsi (perubahan frame, penekanan tombol, paket jaringan, dll). Tentu saja, jumlah input harus sama dengan jumlah status minus satu.

Asumsi 2:

Biaya spasial (memori, disk) menyimpan satu negara jauh lebih besar daripada menyimpan fungsi dan inputnya.

Asumsi 3:

Biaya temporal (waktu) untuk mempresentasikan suatu negara adalah serupa, atau hanya satu atau dua kali lipat yang lebih panjang daripada menghitung suatu fungsi daripada suatu negara.

Bergantung pada persyaratan sistem replay Anda, ada beberapa cara untuk mengimplementasikan sistem replay, sehingga kami dapat mulai dengan yang paling sederhana. Saya juga akan membuat contoh kecil menggunakan permainan catur, yang direkam pada selembar kertas.

Metode 1:

Toko s[0]...s[n]. Ini sangat sederhana, sangat mudah. Karena asumsi 2, biaya spasial ini cukup tinggi.

Untuk catur, ini akan dicapai dengan menggambar seluruh papan untuk setiap gerakan.

Metode 2:

Jika Anda hanya perlu meneruskan replay, Anda cukup menyimpan s[0], lalu menyimpan f[0]...f[n-1](ingat, ini hanya nama id dari fungsi) dan x[0]...x[n-1](apa input untuk masing-masing fungsi ini). Untuk memutar ulang, Anda cukup memulai s[0], dan menghitung

s[1] = f[0](s[0], x[0])
s[2] = f[1](s[1], x[1])

dan seterusnya...

Saya ingin membuat anotasi kecil di sini. Beberapa komentator lain mengatakan bahwa permainan "harus deterministik". Siapa pun yang mengatakan bahwa perlu mengambil Ilmu Komputer 101 lagi, karena kecuali permainan Anda dimaksudkan untuk dijalankan pada komputer kuantum, SEMUA PROGRAM KOMPUTER ADALAH DETERMINISTIK¹. Itulah yang membuat komputer sangat mengagumkan.

Namun, karena program Anda kemungkinan besar tergantung pada program eksternal, mulai dari pustaka hingga implementasi CPU yang sebenarnya, memastikan bahwa fungsi Anda berperilaku sama di antara platform mungkin cukup sulit.

Jika Anda menggunakan angka pseudo-acak, Anda bisa menyimpan angka yang dihasilkan sebagai bagian dari input Anda x, atau menyimpan status fungsi prng sebagai bagian dari negara Anda s, dan implementasinya sebagai bagian dari fungsi f.

Untuk catur, ini akan dicapai dengan menggambar papan awal (yang diketahui) dan kemudian menggambarkan setiap gerakan mengatakan bagian mana yang pergi ke mana. Begitulah cara mereka melakukannya.

Metode 3:

Sekarang, Anda kemungkinan besar ingin mencari replay Anda. Artinya, hitung s[n]untuk sewenang-wenang n. Dengan menggunakan metode 2, Anda perlu menghitung s[0]...s[n-1]sebelum dapat menghitung s[n], yang, menurut asumsi 2, mungkin cukup lambat.

Untuk mengimplementasikan ini, metode 3 adalah generalisasi dari metode 1 dan 2: store f[0]...f[n-1]dan x[0]...x[n-1]sama seperti metode 2, tetapi juga store s[j], untuk semua j % Q == 0untuk konstanta yang diberikan Q. Dalam istilah yang lebih mudah, ini berarti Anda menyimpan bookmark di satu dari setiap Qnegara. Misalnya, untuk Q == 100, Anda menyimpans[0], s[100], s[200]...

Untuk menghitung s[n]sewenang-wenang n, Anda pertama-tama memuat yang disimpan sebelumnya s[floor(n/Q)], dan kemudian menghitung semua fungsi dari floor(n/Q)hingga n. Paling banyak, Anda akan menghitung Qfungsi. Nilai yang lebih kecil Qlebih cepat untuk dihitung tetapi mengkonsumsi lebih banyak ruang, sementara nilai yang lebih besar Qmengkonsumsi lebih sedikit ruang, tetapi membutuhkan waktu lebih lama untuk menghitung.

Metode 3 dengan Q==1sama dengan metode 1, sedangkan metode 3 dengan Q==infsama dengan metode 2.

Untuk catur, ini akan dicapai dengan menggambar setiap gerakan, serta satu dari setiap 10 papan (untuk Q==10).

Metode 4:

Jika Anda ingin membalikkan replay, Anda dapat membuat variasi kecil metode 3. Misalkan Q==100, dan Anda ingin menghitung s[150]melalui s[90]secara terbalik. Dengan metode 3 yang tidak dimodifikasi, Anda perlu membuat 50 perhitungan untuk mendapatkan s[150]dan kemudian 49 perhitungan lagi untuk mendapatkan s[149]dan seterusnya. Tetapi karena Anda sudah menghitung s[149]untuk mendapatkannya s[150], Anda bisa membuat cache dengan s[100]...s[150]ketika Anda menghitung s[150]untuk pertama kalinya, dan kemudian Anda sudah ada s[149]dalam cache saat Anda perlu menampilkannya.

Anda hanya perlu membuat ulang cache setiap kali Anda perlu menghitung s[j], j==(k*Q)-1untuk apa pun yang diberikan k. Kali ini, peningkatan Qakan menghasilkan ukuran yang lebih kecil (hanya untuk cache), tetapi kali lebih lama (hanya untuk membuat ulang cache). Nilai optimal untuk Qdapat dihitung jika Anda mengetahui ukuran dan waktu yang diperlukan untuk menghitung status dan fungsi.

Untuk catur, ini akan dicapai dengan menggambar setiap gerakan, serta satu dari setiap 10 papan (untuk Q==10), tetapi juga, itu perlu menggambar dalam selembar kertas terpisah, 10 papan terakhir yang telah Anda hitung.

Metode 5:

Jika status hanya menghabiskan terlalu banyak ruang, atau fungsi menghabiskan terlalu banyak waktu, Anda dapat membuat solusi yang benar-benar menerapkan (bukan palsu) membalikkan memutar ulang. Untuk melakukan ini, Anda harus membuat fungsi terbalik untuk masing-masing fungsi yang Anda miliki. Namun, ini mensyaratkan bahwa masing-masing fungsi Anda adalah injeksi. Jika ini bisa dilakukan, maka untuk f'menunjukkan fungsi terbalik f, menghitung s[j-1]sesederhana

s[j-1] = f'[j-1](s[j], x[j-1])

Perhatikan bahwa di sini, fungsi dan input keduanya j-1, tidak j. Fungsi dan input yang sama ini akan menjadi yang akan Anda gunakan jika Anda menghitung

s[j] = f[j-1](s[j-1], x[j-1])

Membuat kebalikan dari fungsi-fungsi ini adalah bagian yang sulit. Namun, Anda biasanya tidak bisa, karena beberapa data keadaan biasanya hilang setelah setiap fungsi dalam permainan.

Metode ini, seperti apa adanya, dapat membalikkan perhitungan s[j-1], tetapi hanya jika Anda memilikinya s[j]. Ini berarti Anda hanya dapat menonton tayangan mundur, mulai dari titik di mana Anda memutuskan untuk memutar ulang. Jika Anda ingin memutar ulang mundur dari titik arbitrer, Anda harus mencampur ini dengan metode 4.

Untuk catur, ini tidak dapat diterapkan, karena dengan papan yang diberikan dan gerakan sebelumnya, Anda bisa tahu bagian mana yang dipindahkan, tetapi bukan dari mana ia dipindahkan.

Metode 6:

Akhirnya, jika Anda tidak dapat menjamin semua fungsi Anda adalah suntikan, Anda dapat membuat trik kecil untuk melakukannya. Alih-alih setiap fungsi hanya mengembalikan yang baru, status, Anda juga dapat mengembalikannya ke data yang dibuang, seperti:

s[j+1], r[j] = f[j](s[j], x[j])

Di mana r[j]data yang dibuang. Dan kemudian buat fungsi terbalik Anda sehingga mereka mengambil data yang dibuang, seperti:

s[j] = f'[j](s[j+1], x[j], r[j])

Selain f[j]dan x[j], Anda juga harus menyimpan r[j]untuk setiap fungsi. Sekali lagi, jika Anda ingin dapat mencari, Anda harus menyimpan bookmark, seperti dengan metode 4.

Untuk catur, ini akan sama dengan metode 2, tetapi tidak seperti metode 2, yang hanya mengatakan bagian mana yang pergi, Anda juga perlu menyimpan dari mana masing-masing bagian berasal.

Penerapan:

Karena ini berfungsi untuk semua jenis negara, dengan semua jenis fungsi, untuk game tertentu, Anda dapat membuat beberapa asumsi, yang akan membuatnya lebih mudah untuk diterapkan. Sebenarnya, jika Anda menerapkan metode 6 dengan seluruh kondisi permainan, tidak hanya Anda akan dapat memutar ulang data, tetapi juga kembali ke waktu dan melanjutkan bermain dari setiap saat. Itu akan sangat luar biasa.

Alih-alih menyimpan semua kondisi gim, Anda cukup menyimpan minimum yang diperlukan untuk menggambar kondisi tertentu, dan membuat serialisasi data ini setiap jumlah waktu yang tetap. Status Anda akan menjadi serialisasi ini, dan input Anda sekarang akan menjadi perbedaan antara dua serialisasi. Mereka kunci untuk ini bekerja adalah bahwa serialisasi harus sedikit berubah jika negara dunia berubah sedikit juga. Perbedaan ini sepenuhnya tidak dapat dibalik, sehingga penerapan metode 5 dengan bookmark sangat dimungkinkan.

Saya telah melihat ini diimplementasikan dalam beberapa permainan utama, sebagian besar untuk memutar ulang instan data terbaru ketika sebuah peristiwa (frag di fps, atau skor dalam permainan olahraga) terjadi.

Saya harap penjelasan ini tidak terlalu membosankan.

¹ Ini tidak berarti beberapa program bertindak seperti non-deterministik (seperti MS Windows ^^). Sekarang serius, jika Anda dapat membuat program non-deterministik pada komputer deterministik, Anda dapat yakin Anda akan secara bersamaan memenangkan medali Fields, penghargaan Turing dan mungkin bahkan Oscar dan Grammy untuk semua yang bernilai.


Pada "SEMUA PROGRAM KOMPUTER ADALAH DETERMINISTIK," Anda lalai untuk mempertimbangkan program yang mengandalkan threading. Meskipun threading sebagian besar digunakan untuk memuat sumber daya atau untuk memisahkan loop render, ada pengecualian untuk itu, dan pada saat itu Anda mungkin tidak dapat mengklaim determinisme sejati lagi, kecuali jika Anda benar-benar ketat dalam menegakkan determinisme. Mekanisme penguncian saja tidak akan cukup. Anda tidak akan dapat membagikan data APA SAJA yang dapat berubah tanpa bekerja ekstra. Dalam banyak skenario, gim tidak membutuhkan tingkat keketatan itu sendiri, tetapi bisa untuk hal-hal seperti replay.
krdluzni

1
@krdluzni Threading, paralelisme, dan angka acak dari sumber acak sejati tidak membuat program menjadi non-deterministik. Pengaturan waktu utas, kebuntuan, memori tidak diinisialisasi dan bahkan kondisi balapan hanyalah input tambahan yang diambil program Anda. Pilihan Anda untuk membuang input ini atau bahkan tidak menganggapnya sama sekali (untuk alasan apa pun) tidak akan memengaruhi fakta bahwa program Anda akan menjalankan persis sama dengan diberi input yang sama persis. "non-deterministik" adalah istilah Ilmu Komputer yang sangat tepat, jadi harap hindari menggunakannya jika Anda tidak tahu artinya.

@oscar (Mungkin agak singkat, sibuk, dapat diedit nanti): Meskipun dalam beberapa hal, Anda secara teoritis dapat mengklaim pengaturan waktu, dll. sebagai input, ini tidak berguna dalam arti praktis, karena biasanya tidak dapat diamati oleh program itu sendiri atau sepenuhnya dikendalikan oleh pengembang. Lebih lanjut, suatu program yang tidak bersifat deterministik sangat berbeda dengan program yang tidak bersifat deterministik (dalam pengertian mesin negara). Saya mengerti arti istilah ini. Saya berharap mereka memilih sesuatu yang lain, daripada kelebihan istilah yang sudah ada sebelumnya.
krdluzni

@ krdluzni Maksud saya dalam merancang sistem replay dengan elemen yang tidak dapat diprediksi seperti timing thread (jika mereka mempengaruhi kemampuan Anda untuk menghitung replay secara akurat), adalah memperlakukannya seperti sumber input lainnya, seperti input pengguna. Saya tidak melihat ada orang yang mengeluh bahwa suatu program adalah "non-deterministik" karena ini membutuhkan input pengguna yang sama sekali tidak dapat diprediksi. Adapun istilah, itu tidak akurat dan membingungkan. Saya lebih suka mereka menggunakan sesuatu seperti "praktis tidak dapat diprediksi" atau sesuatu seperti itu. Dan tidak, itu bukan tidak mungkin, periksa debugging replay VMWare.

9

Satu hal yang belum dijawab oleh jawaban lain adalah bahaya mengapung. Anda tidak dapat membuat aplikasi sepenuhnya deterministik menggunakan pelampung.

Menggunakan pelampung, Anda dapat memiliki sistem yang sepenuhnya deterministik, tetapi hanya jika:

  • Menggunakan biner yang persis sama
  • Menggunakan CPU yang persis sama

Ini karena representasi internal float bervariasi dari satu CPU ke CPU lainnya - paling dramatis antara AMD dan CPU intel. Selama nilainya ada di register FPU, nilainya lebih akurat daripada yang terlihat dari sisi C, sehingga setiap perhitungan menengah dilakukan dengan presisi yang lebih tinggi.

Sudah cukup jelas bagaimana ini akan mempengaruhi AMD vs intel bit - katakanlah seseorang menggunakan floats 80 bit dan 64 lainnya, misalnya - tetapi mengapa persyaratan biner yang sama?

Seperti yang saya katakan, presisi yang lebih tinggi digunakan selama nilai-nilai berada di register FPU . Ini berarti bahwa setiap kali Anda mengkompilasi ulang, optimisasi kompiler Anda dapat bertukar nilai masuk dan keluar dari register FPU, menghasilkan hasil yang sedikit berbeda.

Anda mungkin dapat membantu ini dengan mengatur flag _control87 () / _ controlfp () untuk menggunakan presisi serendah mungkin. Namun, beberapa perpustakaan mungkin juga menyentuh ini (setidaknya beberapa versi d3d melakukannya).


3
Dengan GCC Anda dapat menggunakan -float-store untuk memaksa nilai keluar dari register dan memotong ke 32/64 bit presisi, tanpa perlu khawatir tentang perpustakaan lain yang mengacaukan bendera kontrol Anda. Jelas, ini akan berdampak negatif terhadap kecepatan Anda (tetapi juga kuantisasi lainnya).

8

Simpan status awal generator nomor acak Anda. Kemudian simpan, cap waktu, setiap input (mouse, keyboard, jaringan, apa pun). Jika Anda memiliki game berjaringan, Anda mungkin sudah memiliki ini semua.

Atur ulang RNG dan putar inputnya. Itu dia.

Ini tidak menyelesaikan pemutaran ulang, yang tidak ada solusi umum, selain memutar ulang dari awal secepat mungkin. Anda dapat meningkatkan kinerja untuk ini dengan memeriksa seluruh kondisi game setiap X detik, maka Anda hanya perlu memutar ulang sebanyak itu, tetapi seluruh kondisi game mungkin juga sangat mahal untuk didapatkan.

Khususnya format file tidak masalah, tetapi sebagian besar mesin memiliki cara untuk membuat serialisasi perintah dan menyatakan - untuk jaringan, penyimpanan, atau apa pun. Gunakan saja itu.


4

Saya akan memilih menentang replaying deterministik. JAUH lebih sederhana dan JAUH lebih tidak rentan kesalahan untuk menyimpan status setiap entitas setiap 1/1 detik.

Simpan apa yang ingin Anda tampilkan pada pemutaran - jika itu hanya posisi dan tajuk, baiklah, jika Anda juga ingin menampilkan statistik, simpan juga, tetapi secara umum simpan sesedikit mungkin.

Tweak pengodeannya. Gunakan sesedikit mungkin bit untuk semuanya. Replay tidak harus sempurna asalkan terlihat cukup bagus. Bahkan jika Anda menggunakan float untuk, katakanlah, heading, Anda dapat menyimpannya dalam byte dan mendapatkan 256 nilai yang mungkin (ketelitian 1.4º). Itu mungkin cukup atau bahkan terlalu banyak untuk masalah khusus Anda.

Gunakan pengkodean delta. Kecuali jika entitas Anda berteleportasi (dan jika mereka melakukannya, memperlakukan kasing secara terpisah), menyandikan posisi sebagai perbedaan antara posisi baru dan posisi lama - untuk gerakan pendek, Anda dapat melepaskan bit yang jauh lebih sedikit daripada yang Anda perlukan untuk posisi penuh .

Jika Anda ingin mundur cepat, tambahkan kerangka kunci (data lengkap, tanpa delta) setiap N bingkai. Dengan cara ini Anda bisa lolos dengan presisi yang lebih rendah untuk delta dan nilai lainnya, kesalahan pembulatan tidak akan terlalu bermasalah jika Anda mengatur ulang ke nilai "true" secara berkala.

Akhirnya, gzip semuanya :)


1
Ini sedikit tergantung pada jenis game.
Jari Komppa

Saya akan sangat berhati-hati dengan pernyataan ini. Terutama untuk proyek-proyek yang lebih besar dengan dependensi pihak ketiga yang menyelamatkan negara bisa mustahil. Sementara mengatur ulang dan memutar ulang input selalu memungkinkan.
TomSmartBishop

2

Sulit. Pertama dan yang terutama membaca jawaban Jari Komppa.

Replay yang dibuat di komputer saya mungkin tidak berfungsi di komputer Anda karena hasil float sedikit berbeda. Ini masalah besar.

Tetapi setelah itu, jika Anda memiliki angka acak adalah menyimpan nilai seed di replay. Kemudian muat semua status default dan atur nomor acak ke seed itu. Dari sana Anda bisa merekam keadaan tombol / mouse saat ini dan lamanya waktu seperti itu. Kemudian jalankan semua acara menggunakan itu sebagai input.

Untuk melompati file (yang jauh lebih sulit) Anda harus membuang MEMORY THE. Seperti, di mana setiap unit berada, uang, lamanya waktu berlalu, semua kondisi permainan. Kemudian maju cepat tetapi memutar ulang semuanya kecuali melewatkan rendering, suara dll sampai Anda mencapai tujuan waktu yang Anda inginkan. Ini bisa terjadi setiap menit atau 5 menit tergantung seberapa cepat untuk maju.

Poin utama adalah - Berurusan dengan angka acak - Menyalin input (pemain), dan pemain jarak jauh (s)) - Keadaan membuang untuk melompat-lompat file dan ... - MEMILIKI FLOAT BUKAN HAL-HAL YANG BREAK (ya, saya harus berteriak)


2

Saya agak terkejut bahwa tidak ada yang menyebutkan opsi ini, tetapi jika game Anda memiliki komponen multipemain, Anda mungkin telah melakukan banyak kerja keras yang diperlukan untuk fitur ini. Lagipula, apa itu multi-pemain tetapi upaya untuk memutar ulang gerakan orang lain pada waktu yang (sedikit) berbeda di komputer Anda sendiri?

Ini juga memberi Anda manfaat dari ukuran file yang lebih kecil sebagai efek samping, sekali lagi dengan asumsi Anda telah bekerja pada kode jaringan yang ramah bandwidth.

Dalam banyak hal, ini menggabungkan opsi "menjadi sangat deterministik" dan "menyimpan catatan segalanya". Anda masih akan membutuhkan determinisme - jika permainan ulang Anda pada dasarnya adalah bot yang memainkan permainan lagi persis seperti yang Anda mainkan semula, tindakan apa pun yang mereka lakukan yang dapat memiliki hasil acak harus memiliki hasil yang sama.

Format data bisa sesederhana dump lalu lintas jaringan, meskipun saya membayangkan tidak ada salahnya untuk membersihkannya sedikit (Anda tidak perlu khawatir tentang lag pada pemutaran ulang, setelah semua). Anda dapat memainkan ulang hanya sebagian dari permainan dengan menggunakan mekanisme pos pemeriksaan yang telah disebutkan oleh orang lain - biasanya permainan multipemain akan mengirimkan pembaruan permainan sepenuhnya setiap kali, jadi sekali lagi Anda mungkin sudah melakukan pekerjaan ini.


0

Untuk mendapatkan file replay sekecil mungkin, Anda perlu memastikan game Anda deterministik. Biasanya ini melibatkan melihat generator nomor acak Anda dan melihat di mana ia digunakan dalam logika permainan.

Anda kemungkinan besar harus memiliki logika permainan RNG dan RNG segalanya untuk hal-hal seperti GUI, efek partikel, suara. Setelah Anda selesai, Anda perlu merekam keadaan awal logika permainan RNG, lalu perintah game dari semua pemain setiap frame.

Untuk banyak game ada tingkat abstraksi antara input dan logika game di mana input diubah menjadi perintah. Misalnya menekan tombol A pada pengontrol menghasilkan "lompatan" perintah digital yang disetel ke true dan logika gim bereaksi terhadap perintah tanpa memeriksa pengontrol secara langsung. Dengan melakukan ini, Anda hanya perlu merekam perintah yang berdampak pada logika game (tidak perlu merekam perintah "Jeda") dan kemungkinan besar data ini akan lebih kecil daripada merekam data pengontrol. Anda juga tidak perlu khawatir tentang keadaan skema kontrol jika pemain memutuskan untuk memetakan kembali tombol.

Menggulung mundur adalah masalah yang sulit menggunakan metode deterministik dan selain menggunakan snapshot dari kondisi permainan dan meneruskan cepat ke titik waktu yang ingin Anda lihat tidak ada banyak yang dapat Anda lakukan selain merekam seluruh kondisi permainan setiap frame.

Di sisi lain, fast-forwarding tentu bisa dilakukan. Selama logika game Anda tidak bergantung pada rendering, Anda dapat menjalankan logika game sebanyak yang Anda inginkan sebelum merender frame baru game. Kecepatan penerusan cepat hanya akan terikat oleh mesin Anda. Jika Anda ingin melompat maju dengan peningkatan besar, Anda harus menggunakan metode snapshot yang sama seperti yang Anda perlukan untuk memutar ulang.

Mungkin bagian terpenting dari penulisan sistem replay yang bergantung pada determinisme adalah merekam aliran data debug. Aliran debug ini berisi snapshot informasi sebanyak mungkin setiap frame (RNG seed, entitas transforms, animations, dll) dan dapat menguji aliran debug yang direkam terhadap keadaan gim selama replay. Ini akan memungkinkan Anda untuk dengan cepat memberi tahu Anda ketidakcocokan di akhir bingkai apa pun yang diberikan. Ini akan menghemat berjam-jam keinginan untuk menarik rambut Anda keluar dari bug non-deterministik yang tidak diketahui. Sesuatu yang sederhana seperti variabel yang tidak diinisialisasi akan mengacaukan semuanya pada jam ke-11.

CATATAN: Jika game Anda melibatkan streaming konten yang dinamis atau Anda memiliki logika permainan pada beberapa utas atau pada inti yang berbeda ... semoga berhasil.


0

Untuk mengaktifkan perekaman dan pemutaran ulang, rekam semua peristiwa (yang dibuat pengguna, yang dihasilkan timer, komunikasi yang dihasilkan, ...).

Untuk setiap acara, catat waktu acara, apa yang diubah, nilai sebelumnya, nilai baru.

Nilai yang dihitung tidak perlu direkam kecuali jika penghitungannya acak
(Dalam kasus ini Anda dapat merekam nilai yang dihitung juga, atau mencatat perubahan pada seed setelah setiap perhitungan acak).

Data yang disimpan adalah daftar perubahan.
Perubahan dapat disimpan dalam berbagai format (biner, xml, ...).
Perubahan terdiri dari id entitas, nama properti, nilai lama, nilai baru.

Pastikan sistem Anda dapat memutar ulang perubahan ini (mengakses entitas yang diinginkan, mengubah properti yang diinginkan meneruskan ke status baru atau mundur ke status lama).

Contoh:

  • waktu dari awal = t1, entitas = pemain 1, properti = posisi, berubah dari a ke b
  • waktu dari awal = t1, entitas = sistem, properti = mode permainan, berubah dari c ke d
  • waktu dari awal = t2, entitas = pemain 2, properti = keadaan, berubah dari e ke f
  • Untuk mengaktifkan rewinding / fastforwarding atau perekaman hanya rentang waktu tertentu,
    bingkai kunci diperlukan - jika merekam setiap saat, setiap sekarang dan kemudian simpan seluruh kondisi permainan.
    Jika merekam hanya rentang waktu tertentu, di awal simpan kondisi awal.


    -1

    Jika Anda memerlukan ide tentang bagaimana menerapkan sistem replay Anda, cari google untuk bagaimana menerapkan undo / redo dalam suatu aplikasi. Mungkin jelas bagi sebagian orang, tetapi mungkin tidak bagi semua, bahwa undo / redo secara konseptual sama dengan memutar ulang untuk game. Ini hanya kasus khusus di mana Anda dapat mundur, dan tergantung pada aplikasi, mencari titik waktu tertentu.

    Anda akan melihat bahwa tidak ada yang mengimplementasikan undo / redo mengeluh tentang deterministik / non-deterministik, variabel float, atau CPU tertentu.


    Undo / redo terjadi pada aplikasi yang secara fundamental deterministik, event-driven, dan state-light (mis. Status dokumen pengolah kata hanyalah teks dan seleksi, bukan seluruh tata letak, yang dapat dihitung ulang).

    Maka jelas Anda tidak pernah menggunakan aplikasi CAD / CAM, perangkat lunak desain sirkuit, perangkat lunak pelacak gerak atau aplikasi apa pun dengan undo / redo yang lebih canggih daripada pengolah kata. Saya tidak mengatakan kode untuk undo / redo dapat disalin untuk replay pada permainan, hanya saja secara konsep sama (simpan status dan putar ulang nanti). Namun, struktur data utama bukanlah antrian melainkan tumpukan.
    Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
    Licensed under cc by-sa 3.0 with attribution required.