Saya baru-baru ini membaca artikel ini di Game Loops: http://www.koonsolo.com/news/dewitters-gameloop/
Dan implementasi terakhir yang direkomendasikan sangat membingungkan saya. Saya tidak mengerti cara kerjanya, dan sepertinya berantakan total.
Saya mengerti prinsipnya: Perbarui game dengan kecepatan konstan, dengan apa pun yang tersisa membuat game sebanyak mungkin.
Saya kira Anda tidak bisa menggunakan:
- Dapatkan input untuk 25 ticks
- Render game untuk 975 ticks
Pendekatan karena Anda akan mendapatkan input untuk bagian pertama dari yang kedua dan ini akan terasa aneh? Atau apakah itu yang terjadi dalam artikel?
Pada dasarnya:
while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)
Bagaimana itu bahkan valid?
Mari kita asumsikan nilainya.
MAX_FRAMESKIP = 5
Mari kita asumsikan next_game_tick, yang diberikan beberapa saat setelah inisialisasi, sebelum loop game utama mengatakan ... 500.
Akhirnya, karena saya menggunakan SDL dan OpenGL untuk game saya, dengan OpenGL yang digunakan untuk rendering saja, mari kita asumsikan bahwa GetTickCount()
mengembalikan waktu sejak SDL_Init dipanggil, yang memang berhasil.
SDL_GetTicks -- Get the number of milliseconds since the SDL library initialization.
Sumber: http://www.libsdl.org/docs/html/sdlgetticks.html
Penulis juga mengasumsikan ini:
DWORD next_game_tick = GetTickCount();
// GetTickCount() returns the current number of milliseconds
// that have elapsed since the system was started
Jika kami memperluas while
pernyataan yang kami dapatkan:
while( ( 750 > 500 ) && ( 0 < 5 ) )
750 karena waktu telah berlalu sejak next_game_tick
ditugaskan. loops
adalah nol seperti yang Anda lihat di artikel.
Jadi kita telah memasuki loop while, mari kita lakukan beberapa logika dan menerima beberapa input.
Yadayadayada.
Di akhir loop sementara, yang saya ingatkan Anda ada di dalam loop game utama kami adalah:
next_game_tick += SKIP_TICKS;
loops++;
Mari kita perbarui seperti apa iterasi selanjutnya dari kode sementara
while( ( 1000 > 540 ) && ( 1 < 5 ) )
1000 karena waktu telah berlalu mendapatkan input dan melakukan hal-hal sebelum kita mencapai ineterasi loop berikutnya, di mana GetTickCount () dipanggil kembali.
540 karena, dalam kode 1000/25 = 40, oleh karena itu, 500 + 40 = 540
1 karena perulangan kita telah diulang satu kali
5 , kamu tahu kenapa.
Jadi, karena loop Sementara ini JELAS tergantung MAX_FRAMESKIP
dan tidak dimaksudkan TICKS_PER_SECOND = 25;
bagaimana game seharusnya berjalan dengan benar?
Tidak mengherankan bagi saya bahwa ketika saya menerapkan ini ke dalam kode saya, dengan benar saya dapat menambahkan karena saya hanya mengubah nama fungsi saya untuk menangani input pengguna dan menggambar permainan dengan apa yang penulis artikel dalam kode contohnya, permainan tidak melakukan apa pun .
Saya menempatkan fprintf( stderr, "Test\n" );
di dalam loop sementara yang tidak bisa dicetak sampai permainan berakhir.
Bagaimana putaran game ini berjalan 25 kali per detik, dijamin, sambil merender secepat mungkin?
Bagi saya, kecuali saya kehilangan sesuatu BESAR, sepertinya ... tidak ada.
Dan bukankah struktur ini, dari loop sementara ini, seharusnya berjalan 25 kali per detik dan kemudian memperbarui permainan persis seperti yang saya sebutkan sebelumnya di awal artikel?
Jika itu masalahnya mengapa kita tidak bisa melakukan sesuatu yang sederhana seperti:
while( loops < 25 )
{
getInput();
performLogic();
loops++;
}
drawGame();
Dan hitung interpolasi dengan cara lain.
Maafkan pertanyaan saya yang sangat panjang, tetapi artikel ini telah melakukan lebih banyak kerusakan daripada kebaikan bagi saya. Saya sangat bingung sekarang - dan tidak tahu bagaimana menerapkan loop game yang tepat karena semua pertanyaan ini muncul.
sumber
Jawaban:
Saya pikir penulis membuat kesalahan kecil:
while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)
seharusnya
while( GetTickCount() < next_game_tick && loops < MAX_FRAMESKIP)
Yaitu: selama belum waktunya untuk menggambar bingkai kami berikutnya dan sementara kami belum melewatkan frame sebanyak MAX_FRAMESKIP kita harus menunggu.
Saya juga tidak mengerti mengapa dia memperbarui
next_game_tick
dalam loop dan saya menganggap itu adalah kesalahan lain. Sejak di awal frame Anda dapat menentukan kapan frame berikutnya seharusnya (saat menggunakan frame rate tetap). Tidaknext game tick
tergantung pada berapa banyak waktu yang tersisa setelah memperbarui dan merender.Penulis juga membuat kesalahan umum lainnya
Ini berarti rendering frame yang sama beberapa kali. Penulis bahkan sadar akan hal itu:
Ini hanya menyebabkan GPU melakukan pekerjaan yang tidak perlu dan jika rendering membutuhkan waktu lebih lama dari yang diharapkan, hal itu dapat menyebabkan Anda mulai bekerja pada frame berikutnya lebih lambat dari yang dimaksudkan sehingga lebih baik untuk menghasilkan OS dan menunggu.
sumber
Mungkin yang terbaik jika saya sederhanakan:
while
loop di dalam mainloop digunakan untuk menjalankan langkah-langkah simulasi dari mana pun itu, ke tempat seharusnya sekarang.update_game()
fungsi harus selalu mengasumsikan bahwa hanyaSKIP_TICKS
jumlah waktu yang telah berlalu sejak panggilan terakhir. Ini akan membuat fisika game tetap berjalan pada kecepatan konstan pada perangkat keras yang lambat dan cepat.Bertambah
next_game_tick
dengan jumlahSKIP_TICKS
gerakan yang mendekat ke waktu saat ini. Ketika ini menjadi lebih besar dari waktu sekarang, itu rusak (current > next_game_tick
) dan mainloop terus membuat bingkai saat ini.Setelah rendering, panggilan berikutnya
GetTickCount()
akan mengembalikan waktu saat ini. Jika waktu ini lebih tinggi darinext_game_tick
itu berarti kita sudah ketinggalan 1-N langkah dalam simulasi dan kita harus mengejar ketinggalan, menjalankan setiap langkah dalam simulasi dengan kecepatan konstan yang sama. Dalam hal ini jika lebih rendah, itu hanya akan membuat frame yang sama lagi (kecuali ada interpolasi).Kode asli telah membatasi jumlah loop jika kita dibiarkan terlalu jauh (
MAX_FRAMESKIP
). Ini hanya membuatnya benar-benar menunjukkan sesuatu dan tidak tampak seperti dikunci jika misalnya melanjutkan kembali dari penangguhan atau permainan dijeda dalam debugger untuk waktu yang lama (dengan asumsiGetTickCount()
tidak berhenti selama waktu itu) sampai telah menyusul waktu.Untuk menghilangkan rendering frame yang sama tidak berguna jika Anda tidak menggunakan interpolasi di dalamnya
display_game()
, Anda bisa membungkusnya di dalam jika pernyataan seperti:Ini juga merupakan artikel bagus tentang ini: http://gafferongames.com/game-physics/fix-your-timestep/
Juga, mungkin alasan mengapa
fprintf
output Anda saat gim Anda berakhir mungkin karena gosipnya tidak memerah.Maaf tentang bahasa inggris saya.
sumber
Kode-nya terlihat sepenuhnya valid.
Pertimbangkan
while
loop dari set terakhir:Saya memiliki sistem yang menyatakan "saat game sedang berjalan, periksa waktu saat ini - jika itu lebih besar dari penghitungan framerate kami, dan kami telah melewatkan menggambar kurang dari 5 frame, lalu lewati menggambar dan cukup perbarui input dan fisika: lain gambar adegan dan mulai pembaruan iterasi berikutnya "
Ketika setiap pembaruan terjadi, Anda menambah waktu "next_frame" oleh framerate ideal Anda. Kemudian Anda memeriksa waktu Anda lagi. Jika waktu Anda sekarang kurang dari kapan next_frame harus diperbarui, maka Anda melewatkan pembaruan dan menggambar apa yang Anda dapatkan.
Jika current_time Anda lebih besar (bayangkan bahwa proses pengundian terakhir membutuhkan waktu yang sangat lama, karena ada beberapa gangguan di suatu tempat, atau sekelompok pengumpulan sampah dalam bahasa yang dikelola, atau implementasi memori yang dikelola dalam C ++ atau apa pun), maka menarik akan dilewati dan
next_frame
akan diperbarui dengan yang lain senilai bingkai ekstra waktu, sampai baik update mengejar di mana kita harus pada jam, atau kita sudah melewatkan menggambar cukup frame yang kita benar-benar harus menarik satu, sehingga pemain dapat melihat apa yang mereka lakukanJika mesin Anda sangat cepat atau game Anda sangat sederhana, maka
current_time
mungkin lebihnext_frame
jarang, yang artinya Anda tidak memperbarui selama poin-poin itu.Di situlah interpolasi masuk. Demikian juga, Anda bisa memiliki bool terpisah, dideklarasikan di luar loop, untuk menyatakan ruang "kotor".
Di dalam lingkaran pembaruan, Anda akan menetapkan
dirty = true
, menandakan bahwa Anda telah benar-benar melakukan pembaruan.Kemudian, alih-alih hanya menelepon
draw()
, Anda akan mengatakan:Kemudian Anda kehilangan interpolasi untuk gerakan yang mulus, tetapi Anda memastikan bahwa Anda hanya memperbarui ketika Anda memiliki pembaruan yang sebenarnya terjadi (bukan interpolasi antar negara).
Jika Anda berani, ada artikel berjudul "Perbaiki Waktu Anda!" oleh GafferOnGames.
Ini mendekati masalah sedikit berbeda, tetapi saya menganggapnya sebagai solusi yang lebih cantik yang melakukan sebagian besar hal yang sama (tergantung pada fitur bahasa Anda, dan seberapa besar Anda peduli dengan perhitungan fisika game Anda).
sumber