मानक C # / Windows प्रपत्र गेम लूप क्या है?


32

सी # में एक गेम लिखते समय जो सादे-पुराने विंडोज फॉर्म और कुछ ग्राफिक्स एपीआई रैपर जैसे कि स्लिमडीएक्स या ओपनटीके का उपयोग करता है , मुख्य गेम लूप को कैसे संरचित किया जाना चाहिए?

विहित विंडोज फॉर्म एप्लीकेशन में एक प्रविष्टि बिंदु होता है जो दिखता है

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

और जब कोई वर्ग की विभिन्न घटनाओं को झुकाकर कुछ इस तरह से पूरा कर सकता है , तो उन घटनाओं को खेल तर्क वस्तुओं में निरंतर आवधिक अपडेट करने के लिए या रेंडर शुरू करने और समाप्त करने के लिए कोड के बिट्स डालने के लिए एक स्पष्ट स्थान प्रदान नहीं करता है। फ्रेम।Form

इस तरह के खेल के लिए किस तकनीक का उपयोग करना चाहिए ताकि कैनोनिकल को कुछ हासिल किया जा सके

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

गेम लूप, और न्यूनतम प्रदर्शन और जीसी प्रभाव के साथ क्या करना है?

जवाबों:


45

Application.Runकॉल ड्राइव आपके Windows संदेश पंप है, जो अंततः है क्या शक्तियों सभी घटनाओं आप पर हुक कर सकते हैं Formवर्ग (और अन्य)। इस पारिस्थितिकी तंत्र में गेम लूप बनाने के लिए, आप तब सुनना चाहते हैं जब एप्लिकेशन का संदेश पंप खाली हो, और जब वह खाली रहता है, तो विशिष्ट "प्रक्रिया इनपुट स्थिति, गेम लॉजिक को अपडेट करें, दृश्य के रेंडर के चरण" को प्रोटोटाइपिकल गेम लूप प्रदान करें। ।

Application.Idleघटना आग हर बार एक बार आवेदन के संदेश कतार खाली कर दिया जाता है और आवेदन एक निष्क्रिय अवस्था में संक्रमण है। आप अपने मुख्य फॉर्म के निर्माणकर्ता में घटना को हुक कर सकते हैं:

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

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

अगला, आपको यह निर्धारित करने में सक्षम होना चाहिए कि क्या एप्लिकेशन अभी भी निष्क्रिय है। Idleघटना केवल एक बार आग, जब आवेदन हो जाता है बेकार। यह तब तक फिर से निकाल नहीं दिया जाता है जब तक कि एक संदेश कतार में नहीं जाता है और फिर कतार फिर से खाली हो जाती है। Windows प्रपत्र संदेश कतार की स्थिति को क्वेरी करने के लिए एक विधि को उजागर नहीं करता है, लेकिन आप क्वेरी को एक देशी Win32 फ़ंक्शन को सौंपने के लिए प्लेटफ़ॉर्म आमंत्रण सेवाओं का उपयोग कर सकते हैं जो उस प्रश्न का उत्तर दे सकते हैं । इसके PeekMessageऔर इसके सहायक प्रकारों के आयात की घोषणा इस प्रकार है:

[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);

PeekMessageमूल रूप से आपको कतार में अगले संदेश को देखने की अनुमति देता है; यह सच है अगर कोई मौजूद है, तो अन्यथा। इस समस्या के उद्देश्यों के लिए, कोई भी पैरामीटर विशेष रूप से प्रासंगिक नहीं है: यह केवल वापसी मूल्य है जो मायने रखता है। यह आपको एक फ़ंक्शन लिखने की अनुमति देता है जो आपको बताता है कि क्या एप्लिकेशन अभी भी निष्क्रिय है (अर्थात, कतार में अभी भी कोई संदेश नहीं हैं):

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

अब आपके पास अपना पूरा गेम लूप लिखने की जरूरत है:

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);
}

इसके अलावा, यह दृष्टिकोण संभव के रूप में करीब से मेल खाता है (पी / इनवोक पर न्यूनतम निर्भरता के साथ) विहित देशी विंडोज गेम लूप के साथ, जो इस तरह दिखता है:

while (!done) {
    if (PeekMessage(&message, window, 0, 0, PM_REMOVE)){
        TranslateMessage(&message);
        DispatchMessage(&message);
    }
    else {
        Update();
        Render();
    }
}

ऐसी विंडोज़ एपिस सुविधाओं से निपटने की क्या आवश्यकता है? एक सटीक रोक घड़ी (एफपीएस नियंत्रण के लिए) द्वारा शासित कुछ समय के लिए, पर्याप्त नहीं होगा?
अमीर लीमा

3
आपको कुछ बिंदु पर Application.Idle हैंडलर से वापस जाने की आवश्यकता है, अन्यथा आपका एप्लिकेशन फ्रीज हो जाएगा (क्योंकि यह आगे Win32 संदेशों को होने की अनुमति नहीं देता है)। इसके बजाय आप WM_TIMER संदेशों के आधार पर एक लूप बनाने का प्रयास कर सकते हैं, लेकिन WM_TIMER लगभग उतना सटीक नहीं है जितना आप वास्तव में चाहते हैं, और भले ही यह सब कुछ उस निम्नतम-सामान्य-भाजक अद्यतन दर के लिए मजबूर कर दे। कई खेलों में स्वतंत्र रेंडर और लॉजिक अपडेट रेट्स की आवश्यकता होती है, जिनमें से कुछ (जैसे भौतिकी) निश्चित रहते हैं जबकि अन्य नहीं होते हैं।
जोश

नेटिव विंडोज गेम लूप्स एक ही तकनीक का उपयोग करते हैं (मैंने तुलना के लिए एक सरल शामिल करने के लिए मेरे जवाब में संशोधन किया है। एक निश्चित अद्यतन दर को लागू करने के लिए टाइमर कम लचीले हैं, और आप हमेशा PeekMessage के व्यापक संदर्भ में अपनी निश्चित अद्यतन दर को लागू कर सकते हैं। शैली लूप (बेहतर परिशुद्धता और से जीसी प्रभाव के साथ टाइमर का उपयोग कर WM_TIMERआधारित वाले)।
जोश

@JoshPetrie स्पष्ट होने के लिए, निष्क्रिय के लिए उपरोक्त जाँच स्लिमडएक्स के लिए एक फ़ंक्शन का उपयोग करती है। क्या उत्तर में इसे शामिल करना आदर्श होगा? या यह सिर्फ संयोग से है कि आपने 'IsApplicationIdle' को पढ़ने के लिए कोड संपादित किया है जो कि स्लिमडीएक्स समकक्ष है?
वॉन हिल्ट्स

** कृपया मुझे अनदेखा करें, मुझे एहसास हुआ कि आप इसे और नीचे परिभाषित करेंगे ... :)
वॉन हिल्ट्स

2

जोश के जवाब से सहमत, बस मेरे 5 सेंट जोड़ना चाहते हैं। WinForms डिफ़ॉल्ट संदेश लूप (Application.Run) को निम्नलिखित के साथ प्रतिस्थापित किया जा सकता है (बिना p / चालान के)

[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
        }
    }
}

इसके अलावा अगर आप कुछ कोड को संदेश पंप में इंजेक्ट करना चाहते हैं, तो इसका उपयोग करें:

public partial class Form1 : Form
{
    protected override void WndProc(ref Message m)
    {
        // this code is invoked inside default message pump
        base.WndProc(ref m);
    }
}

2
यदि आप इस दृष्टिकोण का चयन करते हैं, तो आपको DoEvents () कचरा-पीढ़ी के ओवरहेड के बारे में पता होना चाहिए ।
जोश

0

मैं समझता हूं कि यह एक पुराना धागा है, लेकिन मैं ऊपर दी गई तकनीकों के लिए दो विकल्प प्रदान करना चाहता हूं। इससे पहले कि मैं उनमें जाऊं, यहां अब तक किए गए प्रस्तावों में से कुछ नुकसान हैं:

  1. PeekMessage काफी ओवरहेड कैरी करता है, जैसा कि लाइब्रेरी मेथड्स करते हैं जो इसे कहते हैं (SlimDX IsApplicationIdle)।

  2. यदि आप बफ़र्ड RawInput को नियोजित करना चाहते हैं, तो आपको UI थ्रेड के अलावा किसी अन्य थ्रेड पर PeekMessage के साथ संदेश पंप को प्रदूषित करना होगा, इसलिए आप इसे दो बार कॉल नहीं करना चाहते हैं।

  3. Application.DoEvents एक तंग पाश में बुलाया जा करने के लिए डिज़ाइन नहीं किया गया है, जीसी समस्याओं को जल्दी से उभर जाएगा।

  4. Application.Idle या PeekMessage का उपयोग करते समय, क्योंकि आप केवल तभी काम कर रहे हैं जब Idle, आपका गेम या एप्लिकेशन आपके विंडो के चलते या आकार बदलते समय नहीं चलेगा, बिना कोड बदबू आ रही है।

इन के इर्द-गिर्द काम करने के लिए (2 को छोड़कर अगर आप रॉ इनपुट रोड से नीचे जा रहे हैं तो)

  1. एक थ्रेडिंग बनाएँ। वहां अपने गेम लूप को चलाएं और चलाएं।

  2. एक थ्रेडिंग बनाएँ। IsLongRunning ध्वज के साथ कार्य करें। और इसे वहां चलाएँ। Microsoft इन दिनों थ्रेड्स के स्थान पर टास्क का उपयोग करने की अनुशंसा करता है और यह देखना मुश्किल नहीं है कि क्यों।

ये दोनों तकनीक यूआई थ्रेड और संदेश पंप से आपके ग्राफिक्स एपीआई को अलग करती हैं, जैसा कि अनुशंसित दृष्टिकोण है। विंडो के आकार बदलने के दौरान संसाधन / राज्य विनाश और मनोरंजन से निपटने को भी सरल बनाया गया है और यूआई थ्रेड के बाहर से डेडलॉक (संदेश पंप से बचने के लिए सावधानी बरतने के लिए सावधानी बरतने) किया जाता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.