Cara yang baik untuk membangun lingkaran game di OpenGL

31

Saat ini saya mulai belajar OpenGL di sekolah, dan saya mulai membuat permainan sederhana beberapa hari yang lalu (sendirian, bukan untuk sekolah). Saya menggunakan freeglut, dan saya sedang membuatnya dalam C, jadi untuk loop game saya, saya benar-benar baru saja menggunakan fungsi yang saya buat glutIdleFuncuntuk memperbarui semua gambar dan fisika dalam satu pass. Ini bagus untuk animasi sederhana yang saya tidak terlalu peduli tentang frame rate, tetapi karena permainan ini sebagian besar berbasis fisika, saya benar-benar ingin (perlu) mengikat seberapa cepat pembaruannya.

Jadi upaya pertama saya adalah memiliki fungsi yang saya lewati glutIdleFunc( myIdle()) untuk melacak berapa banyak waktu yang telah berlalu sejak panggilan sebelumnya, dan memperbarui fisika (dan saat ini grafik) setiap milidetik. Saya biasa timeGetTime()melakukan ini (dengan menggunakan <windows.h>). Dan ini membuat saya berpikir, apakah menggunakan fungsi idle benar-benar cara yang baik untuk melakukan loop game?

Pertanyaan saya adalah, apa cara yang lebih baik untuk mengimplementasikan loop game di OpenGL? Haruskah saya menghindari menggunakan fungsi idle?

Jeff
sumber
Saya merasa pertanyaan ini lebih spesifik untuk OpenGL dan apakah disarankan menggunakan GLUT untuk perulangan
Jeff
1
Di sini manusia , jangan gunakan GLUT untuk proyek yang lebih besar . Tapi itu baik untuk yang lebih kecil.
bobobobo

Jawaban:

29

Jawaban sederhananya adalah tidak, Anda tidak ingin menggunakan panggilan balik glutIdleFunc dalam gim yang memiliki semacam simulasi. Alasan untuk ini adalah bahwa fungsi ini menceraikan animasi dan menggambar kode dari penanganan acara jendela tetapi tidak asinkron. Dengan kata lain, menerima dan menyerahkan acara jendela warung menggambar kode (atau apa pun yang Anda masukkan dalam panggilan balik ini), ini sangat baik untuk aplikasi interaktif (berinteraksi, kemudian merespons), tetapi tidak untuk permainan di mana keadaan fisika atau permainan harus maju independen dari interaksi atau render waktu.

Anda ingin sepenuhnya memisahkan penanganan input, status gim, dan menggambar kode. Ada solusi mudah dan bersih untuk ini yang tidak melibatkan perpustakaan grafis secara langsung (yaitu portabel dan mudah divisualisasikan); Anda ingin seluruh loop permainan menghasilkan waktu dan membuat simulasi menghabiskan waktu yang dihasilkan (dalam potongan). Namun kuncinya adalah untuk mengintegrasikan jumlah waktu simulasi Anda dikonsumsi ke animasi Anda.

Penjelasan dan tutorial terbaik yang saya temukan tentang ini adalah Fix Your Timestep milik Glenn Fiedler

Tutorial ini memiliki perawatan lengkap, namun jika Anda tidak memiliki simulasi fisika yang sebenarnya , Anda dapat melewatkan integrasi sebenarnya tetapi loop dasar masih bermuara pada (dalam kode-pseudo-verbose):

// The amount of time we want to simulate each step, in milliseconds
// (written as implicit frame-rate)
timeDelta = 1000/30
timeAccumulator = 0
while ( game should run )
{
  timeSimulatedThisIteration = 0
  startTime = currentTime()

  while ( timeAccumulator >= timeDelta )
  {
    stepGameState( timeDelta )
    timeAccumulator -= timeDelta
    timeSimulatedThisIteration += timeDelta
  }

  stepAnimation( timeSimulatedThisIteration )

  renderFrame() // OpenGL frame drawing code goes here

  handleUserInput()

  timeAccumulator += currentTime() - startTime 
}

Dengan melakukannya dengan cara ini, warung dalam kode render, penanganan input, atau sistem operasi Anda tidak menyebabkan status permainan Anda tertinggal. Metode ini juga portabel dan perpustakaan grafis independen.

GLUT adalah pustaka yang bagus namun sangat dikendalikan oleh peristiwa. Anda mendaftarkan panggilan balik dan mematikan loop utama. Anda selalu menyerahkan kontrol loop utama Anda menggunakan GLUT. Ada hack untuk mengatasinya , Anda juga dapat memalsukan loop eksternal menggunakan timer dan semacamnya, tetapi perpustakaan lain mungkin merupakan cara yang lebih baik (lebih mudah). Ada banyak alternatif, berikut adalah beberapa (yang dengan dokumentasi bagus dan tutorial cepat):

  • GLFW yang memberi Anda kemampuan untuk mendapatkan inline acara input (di loop utama Anda sendiri).
  • SDL , namun penekanannya tidak secara khusus OpenGL.
charstar
sumber
Artikel bagus Jadi untuk melakukan ini di OpenGL, saya tidak akan menggunakan glutMainLoop, tetapi milik saya sendiri? Jika saya melakukan itu, apakah saya dapat menggunakan glutMouseFunc dan fungsi lainnya? Saya harap itu masuk akal, saya masih cukup baru untuk semua ini
Jeff
Sayangnya tidak. Semua panggilan balik yang terdaftar di GLUT dikeluarkan dari dalam glutMainLoop saat antrian acara dikuras dan iterates. Saya menambahkan beberapa tautan ke alternatif di badan jawaban.
charstar
1
+1 untuk membuang GLUT untuk hal lain. Sejauh yang saya tahu, GLUT tidak pernah dimaksudkan untuk apa pun selain aplikasi uji.
Jari Komppa
Anda masih harus menangani acara sistem, bukan? Pemahaman saya adalah bahwa dengan menggunakan glutIdleFunc, itu hanya menangani loop (di Windows) GetMessage-TranslateMessage-DispatchMessage dan memanggil fungsi Anda setiap kali tidak ada pesan yang didapat. Anda akan melakukannya sendiri, atau menderita ketidaksabaran OS dalam menandai aplikasi Anda sebagai tidak responsif, bukan?
Ricket
@Ricket Secara teknis, glutMainLoopEvent () menangani acara yang masuk dengan menelepon callback terdaftar. glutMainLoop () secara efektif {glutMainLoopEvent (); IdleCallback (); } Pertimbangan utama di sini adalah bahwa, menggambar kembali juga merupakan callback yang ditangani dari dalam loop peristiwa. Anda dapat membuat perpustakaan yang dikendalikan oleh peristiwa berperilaku seperti perpustakaan yang didorong oleh waktu, tetapi Maslow's Hammer dan semua itu ...
charstar
4

Saya pikir glutIdleFuncbaik-baik saja; itu pada dasarnya apa yang akhirnya Anda lakukan dengan tangan, jika Anda tidak menggunakannya. Tidak peduli apa yang akan Anda miliki dengan loop ketat yang tidak disebut dalam pola tetap, dan Anda harus membuat pilihan apakah Anda ingin mengubahnya menjadi loop waktu tetap atau menjadikannya loop variabel waktu dan membuat yakin semua akun matematika Anda untuk waktu interpolasi. Atau bahkan campuran dari ini, entah bagaimana.

Anda tentu dapat memiliki myIdlefungsi yang Anda masukiglutIdleFunc , dan di dalamnya, mengukur waktu yang telah berlalu, membaginya dengan stempel waktu Anda yang tetap, dan memanggil fungsi lain yang berkali-kali.

Inilah yang tidak boleh dilakukan:

void myFunc() {
    // (calculate timeDifference here)
    int numOfTimes = timeDifference / 10;
    for(int i=0; i<numOfTimes; i++) {
        fixedTimestep();
    }
}

fixedTimestep tidak akan dipanggil setiap 10 milidetik. Bahkan itu akan berjalan lebih lambat daripada waktu nyata, dan Anda mungkin berakhir dengan angka-angka palsu untuk mengkompensasi ketika benar-benar akar masalahnya terletak pada hilangnya sisanya ketika Anda membagitimeDifference dengan 10.

Sebagai gantinya, lakukan sesuatu seperti ini:

long queuedMilliseconds; // static or whatever, this must persist outside of your loop

void myFunc() {
    // (calculate timeDifference here)
    queuedMilliseconds += timeDifference;
    while(queuedMilliseconds >= 10) {
        fixedTimestep();
        queuedMilliseconds -= 10;
    }
}

Sekarang struktur loop ini memastikan bahwa Anda fixedTimestep fungsi akan dipanggil sekali per 10 milidetik, tanpa kehilangan milidetik sebagai sisa atau semacamnya.

Karena sifat multitasking sistem operasi, Anda tidak dapat mengandalkan fungsi yang dipanggil tepat setiap 10 milidetik; tetapi loop di atas akan terjadi cukup cepat sehingga diharapkan akan cukup dekat, dan Anda dapat mengasumsikan bahwa setiap panggilanfixedTimestep akan meningkatkan simulasi Anda senilai 10 milidetik.

Baca juga jawaban ini dan terutama tautan yang disediakan dalam jawaban.

Ricket
sumber