Apakah loop game C # / Windows standar itu?

32

Saat menulis gim dalam C # yang menggunakan Formulir Windows kuno dan beberapa pembungkus API grafis seperti SlimDX atau OpenTK , bagaimana seharusnya perulangan gim utama disusun?

Aplikasi Windows Forms kanonik memiliki titik masuk yang terlihat seperti

public static void Main () {
  Application.Run(new MainForm());
}

dan sementara seseorang dapat mencapai sebagian dari apa yang diperlukan dengan mengaitkan berbagai peristiwa di Formkelas , peristiwa tersebut tidak memberikan tempat yang jelas untuk meletakkan bit kode untuk melakukan pembaruan berkala yang konstan ke objek logika game atau untuk memulai dan mengakhiri render bingkai.

Teknik apa yang harus digunakan permainan seperti itu untuk mencapai sesuatu yang mirip dengan kanonik

while(!done) {
  update();
  render();
}

lingkaran permainan, dan hubungannya dengan kinerja minimal dan dampak GC?

Josh
sumber

Jawaban:

45

The Application.Runpanggilan drive pompa pesan Windows Anda, yang pada akhirnya kekuatan apa semua peristiwa Anda dapat menghubungkan pada Formkelas (dan lain-lain). Untuk membuat loop game di ekosistem ini, Anda ingin mendengarkan ketika pompa pesan aplikasi kosong, dan sementara itu tetap kosong, lakukan langkah-langkah "proses input negara, perbarui logika game, render adegan" membuat langkah-langkah loop game prototypical .

The Application.Idleperistiwa kebakaran sekali setiap kali antrian pesan aplikasi dikosongkan dan aplikasi transisi ke keadaan idle. Anda dapat mengaitkan acara di konstruktor formulir utama Anda:

class MainForm : Form {
  public MainForm () {
    Application.Idle += HandleApplicationIdle;
  }

  void HandleApplicationIdle (object sender, EventArgs e) {
    //TODO: Implement me.
  }
}

Selanjutnya, Anda perlu untuk dapat menentukan apakah aplikasi tersebut masih menganggur. The Idleevent hanya kebakaran sekali, saat aplikasi menjadi siaga. Itu tidak dipecat lagi sampai pesan masuk ke antrian dan kemudian antrian kosong lagi. Formulir Windows tidak memaparkan metode untuk menanyakan keadaan antrian pesan, tetapi Anda dapat menggunakan layanan permintaan platform untuk mendelegasikan kueri ke fungsi Win32 asli yang dapat menjawab pertanyaan itu . Deklarasi impor untuk PeekMessagedan jenis pendukungnya seperti:

[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
    public IntPtr Handle;
    public uint Message;
    public IntPtr WParameter;
    public IntPtr LParameter;
    public uint Time;
    public Point Location;
}

[DllImport("user32.dll")]
public static extern int PeekMessage(out NativeMessage message, IntPtr window, uint filterMin, uint filterMax, uint remove);

PeekMessagepada dasarnya memungkinkan Anda untuk melihat pesan berikutnya dalam antrian; itu mengembalikan true jika ada, false sebaliknya. Untuk keperluan masalah ini, tidak ada parameter yang relevan: hanya nilai pengembalian yang penting. Ini memungkinkan Anda untuk menulis fungsi yang memberi tahu Anda jika aplikasi tersebut masih menganggur (artinya, masih belum ada pesan dalam antrian):

bool IsApplicationIdle () {
    NativeMessage result;
    return PeekMessage(out result, IntPtr.Zero, (uint)0, (uint)0, (uint)0) == 0;
}

Sekarang Anda memiliki semua yang Anda butuhkan untuk menulis lingkaran permainan lengkap Anda:

class MainForm : Form {
  public MainForm () {
    Application.Idle += HandleApplicationIdle;
  }

  void HandleApplicationIdle (object sender, EventArgs e) {
    while(IsApplicationIdle()) {
      Update();
      Render();
    }
  }

  void Update () {
    // ...
  }

  void Render () {
    // ...
  }

  [StructLayout(LayoutKind.Sequential)]
  public struct NativeMessage
  {
      public IntPtr Handle;
      public uint Message;
      public IntPtr WParameter;
      public IntPtr LParameter;
      public uint Time;
      public Point Location;
  }

  [DllImport("user32.dll")]
  public static extern int PeekMessage(out NativeMessage message, IntPtr window, uint filterMin, uint filterMax, uint remove);
}

Lebih lanjut, pendekatan ini cocok sedekat mungkin (dengan ketergantungan minimal pada P / Invoke) ke loop game Windows asli kanonik , yang terlihat seperti:

while (!done) {
    if (PeekMessage(&message, window, 0, 0, PM_REMOVE)){
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
    else {
        Update();
        Render();
    }
}
Josh
sumber
Apa perlunya berurusan dengan fitur windows apis seperti itu? Membuat blok sementara diperintah oleh stop watch yang tepat (untuk kontrol fps), tidak akan cukup?
Emir Lima
3
Anda harus kembali dari Application.Idle handler di beberapa titik, jika tidak aplikasi Anda akan membeku (karena tidak pernah memungkinkan pesan Win32 lebih lanjut terjadi). Sebagai gantinya, Anda dapat mencoba membuat loop berdasarkan pesan WM_TIMER, tetapi WM_TIMER tidak setepat yang Anda inginkan, dan bahkan jika itu akan memaksa semuanya ke tingkat pembaruan denominator yang paling umum-biasa saja. Banyak gim yang membutuhkan atau ingin memiliki laju pembaruan render dan logika yang independen, beberapa di antaranya (seperti fisika) tetap tetap ada sementara yang lain tidak.
Josh
Loop game asli Windows menggunakan teknik yang sama (Saya telah mengubah jawaban saya untuk memasukkan yang sederhana sebagai pembanding. Pengatur waktu untuk memaksa laju pembaruan tetap kurang fleksibel, dan Anda selalu dapat menerapkan laju pembaruan tetap Anda dalam konteks yang lebih luas dari PeekMessage -style loop (menggunakan timer dengan presisi yang lebih baik dan dampak GC daripada yang WM_TIMERberbasis)
Josh
@ JoshPetrie Agar jelas, pemeriksaan di atas untuk idle menggunakan fungsi untuk SlimDX. Apakah ideal untuk memasukkan ini dalam jawaban? Atau hanya kebetulan Anda mengedit kode untuk membaca 'IsApplicationIdle' yang merupakan mitra SlimDX?
Vaughan Hilts
** Tolong abaikan saya, saya baru sadar Anda mendefinisikannya lebih jauh ... :)
Vaughan Hilts
2

Setuju dengan jawaban dari Josh, hanya ingin menambahkan 5 sen saya. WinForms default message loop (Application.Run) dapat diganti dengan yang berikut (tanpa p / meminta):

[STAThread]
static void Main()
{
    using (Form1 f = new Form1())
    {
        f.Show();
        while (true) // here should be some nice exit condition
        {
            Application.DoEvents(); // default message pump
        }
    }
}

Juga jika Anda ingin menyuntikkan beberapa kode ke pompa pesan, maka gunakan ini:

public partial class Form1 : Form
{
    protected override void WndProc(ref Message m)
    {
        // this code is invoked inside default message pump
        base.WndProc(ref m);
    }
}
infra
sumber
2
Namun, Anda perlu mengetahui DoEvents () menghasilkan overhead sampah jika Anda memilih pendekatan ini.
Josh
0

Saya mengerti bahwa ini adalah utas lama, tetapi saya ingin memberikan dua alternatif untuk teknik yang disarankan di atas. Sebelum saya membahasnya, berikut adalah beberapa jebakan dengan proposal yang dibuat sejauh ini:

  1. PeekMessage membawa overhead yang cukup besar, seperti halnya metode perpustakaan yang menyebutnya (SlimDX IsApplicationIdle).

  2. Jika Anda ingin menggunakan RawInput yang disangga, Anda harus melakukan polling pompa pesan dengan PeekMessage di utas lain selain utas UI, jadi Anda tidak ingin menyebutnya dua kali.

  3. Application.DoEvents tidak dirancang untuk dipanggil dalam loop ketat, masalah GC akan segera muncul.

  4. Saat menggunakan Application.Idle atau PeekMessage, karena Anda hanya melakukan pekerjaan saat Idle, game atau aplikasi Anda tidak akan berjalan ketika memindahkan atau mengubah ukuran jendela Anda, tanpa bau kode.

Untuk mengatasinya (kecuali 2 jika Anda menggunakan RawInput), Anda dapat:

  1. Buat Threading. Baca dan jalankan loop game Anda di sana.

  2. Buat Threading.Tasks.Task dengan bendera IsLongRunning, dan jalankan di sana. Microsoft merekomendasikan bahwa Tugas digunakan sebagai pengganti Threads hari ini dan tidak sulit untuk melihat mengapa.

Kedua teknik ini mengisolasi API grafik Anda dari utas UI dan pompa pesan, seperti pendekatan yang disarankan. Penanganan kerusakan sumber daya / negara dan rekreasi selama pengubahan ukuran Window juga disederhanakan dan secara estetika jauh lebih profesional ketika dilakukan dari selesai (exericising karena hati-hati untuk menghindari kebuntuan dengan pompa pesan) dari luar utas UI.

ROGRat
sumber