SANGAT Bingung dengan Loop Game “Konstan Kecepatan Game Maksimum FPS”

12

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 whilepernyataan yang kami dapatkan:

while( ( 750 > 500 ) && ( 0 < 5 ) )

750 karena waktu telah berlalu sejak next_game_tickditugaskan. loopsadalah 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_FRAMESKIPdan 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.

tsujp
sumber
1
Kata-kata kasar lebih baik ditujukan kepada penulis artikel. Bagian mana yang merupakan pertanyaan objektif Anda ?
Anko
3
Apakah loop game ini bahkan valid, seseorang menjelaskan. Dari tes saya itu tidak memiliki struktur yang benar untuk menjalankan 25 kali per detik. Jelaskan pada saya mengapa demikian. Juga ini bukan kata-kata kasar, ini adalah serangkaian pertanyaan. Haruskah saya menggunakan emotikon, apakah saya terlihat marah?
tsujp
2
Karena pertanyaan Anda bermuara pada "Apa yang saya tidak mengerti tentang loop game ini", dan Anda memiliki banyak kata-kata dengan huruf tebal, itu muncul sebagai setidaknya jengkel.
Kirbinator
@ Kirbinator Saya bisa menghargai itu, tetapi saya mencoba untuk meletakkan semua yang saya temukan tidak biasa dalam artikel ini sehingga itu bukan pertanyaan yang dangkal dan kosong. Saya tidak berpikir itu klasik bahwa, bagaimanapun, saya ingin berpikir saya memiliki beberapa poin yang valid - setelah semua itu adalah artikel yang mencoba untuk mengajar, tetapi tidak melakukan pekerjaan yang baik.
tsujp
7
Jangan salah paham, ini pertanyaan yang bagus, hanya saja bisa 80% lebih pendek.
Kirbinator

Jawaban:

8

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_tickdalam loop dan saya menganggap itu adalah kesalahan lain. Sejak di awal frame Anda dapat menentukan kapan frame berikutnya seharusnya (saat menggunakan frame rate tetap). Tidak next game ticktergantung pada berapa banyak waktu yang tersisa setelah memperbarui dan merender.

Penulis juga membuat kesalahan umum lainnya

dengan apa pun yang tersisa membuat permainan sebanyak mungkin.

Ini berarti rendering frame yang sama beberapa kali. Penulis bahkan sadar akan hal itu:

Gim ini akan diperbarui pada kecepatan tetap 50 kali per detik, dan rendering dilakukan secepat mungkin. Perhatikan bahwa saat rendering dilakukan lebih dari 50 kali per detik, beberapa frame berikutnya akan sama, sehingga frame visual aktual akan ditampilkan pada maksimum 50 frame per detik.

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.

Roy T.
sumber
+ Pilih. Mmmm Ini memang membuat saya bingung tentang apa yang harus dilakukan. Saya berterima kasih atas wawasan Anda. Saya mungkin akan bermain-main, masalahnya adalah pengetahuan tentang membatasi FPS atau mengatur FPS secara dinamis, dan bagaimana melakukannya. Dalam pikiran saya, penanganan input yang tetap diperlukan agar permainan berjalan pada kecepatan yang sama untuk semua orang. Ini hanya MMO Platformer 2D sederhana (dalam jangka panjang)
tsujp
Sementara loop tampaknya benar dan meningkatkan next_game_tick ada untuk itu. Itu ada untuk menjaga simulasi berjalan pada kecepatan konstan pada perangkat keras yang lebih lambat dan lebih cepat. Menjalankan kode rendering "secepat mungkin" (atau lebih cepat dari fisika) masuk akal hanya ketika ada interpolasi dalam rendering kode untuk membuatnya lebih halus untuk objek cepat dll (akhir artikel), tetapi itu hanya membuang energi jika ia menampilkan lebih dari apa yang bisa ditampilkan oleh perangkat keluaran (layar).
Dotti
Jadi kodenya adalah implementasi yang valid, sekarang sudah. Dan kita hanya harus hidup dengan limbah ini? Atau ada metode yang bisa saya cari untuk ini.
tsujp
Menghasilkan ke OS dan menunggu hanya ide yang bagus jika Anda memiliki ide yang baik tentang kapan OS akan mengembalikan kontrol kepada Anda - yang mungkin tidak secepat yang Anda inginkan.
Kylotan
Saya tidak dapat memberikan suara untuk jawaban ini. Dia benar-benar kehilangan hal-hal interpolasi, yang membuat seluruh bagian kedua dari jawaban tidak valid.
AlbeyAmakiir
4

Mungkin yang terbaik jika saya sederhanakan:

while( game_is_running ) {

    current = GetTickCount();
    while(current > next_game_tick) {
        update_game();

        next_game_tick += SKIP_TICKS;
    }
    display_game();
}

whileloop di dalam mainloop digunakan untuk menjalankan langkah-langkah simulasi dari mana pun itu, ke tempat seharusnya sekarang. update_game()fungsi harus selalu mengasumsikan bahwa hanya SKIP_TICKSjumlah 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_tickdengan jumlah SKIP_TICKSgerakan 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 dari next_game_tickitu 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 asumsi GetTickCount()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:

while (game_is_running) {
    current = GetTickCount();
    if (current > next_game_tick) {
        while(current > next_game_tick) {
            update_game();

            next_game_tick += SKIP_TICKS;
        }
    display_game();
    }
    else {
    // could even sleep here
    }
}

Ini juga merupakan artikel bagus tentang ini: http://gafferongames.com/game-physics/fix-your-timestep/

Juga, mungkin alasan mengapa fprintfoutput Anda saat gim Anda berakhir mungkin karena gosipnya tidak memerah.

Maaf tentang bahasa inggris saya.

Dotti
sumber
4

Kode-nya terlihat sepenuhnya valid.

Pertimbangkan whileloop dari set terakhir:

// JS / pseudocode
var current_time = function () { return Date.now(); }, // in ms
    framerate = 1000/30, // 30fps
    next_frame = current_time(),

    max_updates_per_draw = 5,

    iterations;

while (game_running) {

    iterations = 0;

    while (current_time() > next_frame && iterations < max_updates_per_draw) {
        update_game(); // input, physics, audio, etc

        next_frame += framerate;
        iterations += 1;
    }

    draw();
}

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_frameakan 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 lakukan

Jika mesin Anda sangat cepat atau game Anda sangat sederhana, maka current_timemungkin lebih next_framejarang, 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:

if (is_dirty) {
    draw(); 
    is_dirty = false;
}

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

Norguard
sumber