ओपनजीएल में गेम लूप बनाने का एक अच्छा तरीका है


31

मैं वर्तमान में स्कूल में ओपनजीएल सीखना शुरू कर रहा हूं, और मैंने दूसरे दिन (अपने दम पर, स्कूल के लिए नहीं) एक सरल खेल बनाना शुरू कर दिया है। मैं फ्रीग्लुट का उपयोग कर रहा हूं, और सी में इसका निर्माण कर रहा हूं, इसलिए अपने गेम लूप के लिए मैं वास्तव में एक फ़ंक्शन का उपयोग कर रहा था जिसे मैंने एक glutIdleFuncड्राइंग और भौतिकी को एक पास में अपडेट करने के लिए पास किया था। यह सरल एनिमेशन के लिए ठीक था कि मुझे फ्रेम दर के बारे में बहुत परवाह नहीं थी, लेकिन चूंकि गेम ज्यादातर भौतिकी आधारित है, इसलिए मैं वास्तव में यह अद्यतन करने के लिए कितनी तेजी से नीचे बाँधना चाहता हूं।

इसलिए मेरा पहला प्रयास यह था कि मैं अपने फंक्शन को पास करने के लिए glutIdleFunc( myIdle()) से पहले कॉल करने के बाद कितना समय बीत चुका हूं , और फिजिक्स (और वर्तमान में ग्राफिक्स) को इतने सारे मिलीसेकेंड में अपडेट करूं। मैं timeGetTime()यह (उपयोग करके <windows.h>) करता था। और यह मुझे सोचने के लिए मिला है, क्या वास्तव में गेम लूप के बारे में जाने का एक अच्छा तरीका है?

मेरा सवाल यह है कि ओपनलॉग में गेम लूप को लागू करने का एक बेहतर तरीका क्या है? क्या मुझे निष्क्रिय फ़ंक्शन का उपयोग करने से बचना चाहिए?



मुझे लगता है कि यह सवाल ओपनगेल के लिए अधिक विशिष्ट है और क्या लूप के लिए GLUT का उपयोग करना उचित है
जेफ

1
यहाँ आदमी , बड़ी परियोजनाओं के लिए GLUT का उपयोग न करें । यह छोटे लोगों के लिए ठीक है, हालांकि।
बोब्बोबो

जवाबों:


29

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

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

इस पर मुझे जो सबसे अच्छा स्पष्टीकरण और ट्यूटोरियल मिला है वह है ग्लेन फिडलर का फिक्स योर टाइमस्टेप

इस ट्यूटोरियल में पूर्ण ट्रीटमेंट है, हालांकि यदि आपके पास वास्तविक भौतिकी सिमुलेशन नहीं है, तो आप सही एकीकरण को छोड़ सकते हैं लेकिन मूल लूप अभी भी (वर्बोज़ स्यूडो-कोड में) को उबालता है:

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

इस तरह से करने से, आपके रेंडर कोड, इनपुट हैंडलिंग, या ऑपरेटिंग सिस्टम में स्टालों के कारण आपका गेम स्टेट पीछे नहीं पड़ता है। यह विधि भी पोर्टेबल और ग्राफिक्स लाइब्रेरी स्वतंत्र है।

GLUT एक अच्छी लाइब्रेरी है, लेकिन यह कड़ाई से संचालित है। आप कॉलबैक रजिस्टर करते हैं और मुख्य लूप को बंद कर देते हैं। आप हमेशा GLUT का उपयोग करके अपने मुख्य लूप का नियंत्रण सौंपते हैं। इसके चारों ओर प्राप्त करने के लिए हैक हैं , आप टाइमर और इस तरह का उपयोग करके एक बाहरी लूप भी नकली कर सकते हैं, लेकिन एक और पुस्तकालय शायद जाने के लिए एक बेहतर (आसान) तरीका है। कई विकल्प हैं, यहाँ कुछ (अच्छे प्रलेखन और त्वरित ट्यूटोरियल वाले) हैं:

  • GLFW जो आपको इनपुट इवेंट इनलाइन (अपने मुख्य लूप में) प्राप्त करने की क्षमता देता है।
  • SDL , हालाँकि इसका जोर विशेष रूप से OpenGL नहीं है।

अच्छा लेख। तो OpenGL में ऐसा करने के लिए, मैं glutMainLoop का उपयोग नहीं करूंगा, लेकिन इसके बजाय मैं स्वयं? अगर मैंने ऐसा किया, तो क्या मैं glutMouseFunc और अन्य कार्यों का उपयोग कर सकूंगा? मुझे उम्मीद है कि समझ में आता है, मैं अभी भी इस सब के लिए बहुत नया हूं
जेएफ

दुर्भाग्य से नहीं। GLUT में पंजीकृत सभी कॉलबैक glutMainLoop के भीतर से निकाल दिए जाते हैं, क्योंकि घटना कतार से बाहर हो जाती है और यह ख़राब हो जाती है। मैंने उत्तर निकाय में विकल्पों के लिए कुछ लिंक जोड़े।
charstar

1
+1 कुछ और के लिए GLUT को डायटिंग करने के लिए। जहां तक ​​मुझे पता है, GLUT का मतलब किसी भी चीज के लिए नहीं बल्कि परीक्षण अनुप्रयोगों के लिए था।
जरी कोमप्पा

आपको अभी भी सिस्टम ईवेंट को संभालना है, नहीं? मेरी समझ यह थी कि glutIdleFunc का उपयोग करके, यह सिर्फ (Windows में) GetMessage-TranslateMessage-DispatchMessage लूप को संभालता है और जब भी कोई संदेश प्राप्त नहीं होता है तब अपने फ़ंक्शन को कॉल करता है। आप वैसे भी खुद ऐसा करेंगे, वरना आपके आवेदन को गैर-ज़िम्मेदार ठहराने के लिए ओएस की अधीरता का सामना करना पड़ेगा?
Ricket

@ टिकट तकनीकी रूप से, glutMainLoopEvent () पंजीकृत कॉलबैक कॉल करके आने वाली घटनाओं को संभालती है। glutMainLoop () प्रभावी रूप से {glutMainLoopEvent () है; IdleCallback (); } यहां एक महत्वपूर्ण विचार यह है कि, ईवेंट लूप के भीतर से रिड्रॉलिंग भी एक कॉलबैक है। आप एक इवेंट-संचालित लाइब्रेरी को समय-चालित एक की तरह व्यवहार कर सकते हैं, लेकिन
मैस्लो

4

मुझे लगता glutIdleFuncहै कि बस ठीक है; यदि आप इसका उपयोग नहीं करते हैं तो यह अनिवार्य रूप से आप किसी भी तरह से हाथ से खत्म कर देंगे। कोई फर्क नहीं पड़ता कि आपके पास एक तंग लूप है जिसे निश्चित पैटर्न में नहीं कहा जाता है, और आपको यह चुनाव करने की आवश्यकता है कि क्या आप इसे एक निश्चित समय के लूप में बदलना चाहते हैं या इसे एक चर-समय लूप में रखना चाहते हैं और बनाते हैं निश्चित समय के लिए अपने सभी गणित खातों को सुनिश्चित करें। या फिर इनका मिश्रण भी किसी तरह।

आपके पास निश्चित रूप से एक myIdleफ़ंक्शन हो सकता है जिसे आप पास करते हैं glutIdleFunc, और उसके भीतर, जो समय बीत चुका है उसे मापें, इसे अपने निश्चित टाइमस्टेप द्वारा विभाजित करें, और एक और फ़ंक्शन को कॉल करें जो कई बार।

यहाँ क्या है नहीं करना है:

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

फिक्स्डस्टेप को हर 10 मिलीसेकंड नहीं कहा जाएगा। वास्तव में यह वास्तविक समय की तुलना में धीमी गति से चलेगा, और आप क्षतिपूर्ति संख्या को समाप्त कर सकते हैं, जब वास्तव में समस्या की जड़ शेष के नुकसान में होती है जब आप timeDifference10 से विभाजित करते हैं ।

इसके बजाय, ऐसा कुछ करें:

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

अब यह लूप संरचना यह सुनिश्चित करती है कि आपका fixedTimestepफ़ंक्शन 10 मिलीसेकंड प्रति एक बार कहलाएगा, बिना किसी मिलीसेकंड को खोए या जैसे कुछ भी।

ऑपरेटिंग सिस्टम की मल्टीटास्किंग प्रकृति के कारण आप एक फ़ंक्शन पर भरोसा नहीं कर सकते हैं जिसे हर 10 मिलीसेकंड कहा जाता है; लेकिन उपरोक्त लूप काफी जल्दी होगा कि यह उम्मीद के काफी करीब होगा, और आप मान सकते हैं कि प्रत्येक कॉल fixedTimestepआपके सिमुलेशन 10 मिलीसेकंड के मूल्य में वृद्धि करेगा।

कृपया इस उत्तर और विशेष रूप से उत्तर में दिए गए लिंक को भी पढ़ें ।

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