2013-02-26 139 views
6

मैंने हाल ही में गेम प्रोग्रामिंग में शामिल होने की कोशिश की। मैं जावा के साथ बहुत अनुभवी हूं, लेकिन गेम प्रोग्रामिंग के साथ नहीं। मैं http://www.koonsolo.com/news/dewitters-gameloop/ पढ़ सकते हैं और खेल पाश निम्न कोड के साथ वहाँ प्रस्तावित कार्यान्वित:क्या "डेविटर्स गेम लूप" निरंतर यूपीएस मानता है?

private static int UPDATES_PER_SECOND = 25; 
private static int UPDATE_INTERVAL = 1000/UPDATES_PER_SECOND * 1000000; 
private static int MAX_FRAMESKIP = 5; 

public void run() { 
    while (true) { 
     int skippedFrames = 0; 
     while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) { 
      this.updateGame(); 
      this.nextUpdate += UPDATE_INTERVAL; 
      skippedFrames++; 
     } 

     long currentNanoTime = System.nanoTime(); 
     double interpolation = (currentNanoTime + UPDATE_INTERVAL - this.nextUpdate)/UPDATE_INTERVAL; 
     this.repaintGame(interpolation); 
    } 
} 

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

वेबसाइट पर, प्रक्षेप के लिए निम्न उदाहरण कहा गया है:

10 वीं में हैं gametick स्थिति 500 ​​है, और गति 11 वीं में 100 है, तो स्थिति 600 हो जाएगा gametick। तो जब आप इसे प्रस्तुत करते हैं तो आप अपनी कार कहां रखेंगे? आप अंतिम गैमेटिक की स्थिति ले सकते हैं (इस मामले में 500)। लेकिन एक बेहतर तरीका भविष्यवाणी करने के लिए जहां कार सटीक 10.3 पर होगा है, और यह इस तरह होता है:
view_position = position + (speed * interpolation)
कार तो स्थिति 530.

पर रेंडर किया जाएगा मैं जानता हूँ कि यह सिर्फ एक उदाहरण है, लेकिन क्या इससे कार की गति UPDATES_PER_SECOND पर निर्भर नहीं होगी? तो यूपीएस का मतलब तेज कार होगा? यह सही नहीं हो सकता है ...?

कोई भी मदद, ट्यूटोरियल, जो कुछ भी सराहना की जाती है। (! धन्यवाद) - बहुत अच्छी अब तक (एक चरित्र स्प्राइट बढ़ने के लिए) काम करता है, लेकिन वास्तव में जटिल खेल सामान के लिए प्रतीक्षा करते हैं

अद्यतन/समाधान

सभी मदद करने के बाद, यहाँ मैं वर्तमान में क्या उपयोग कर रहा हूँ है यह तय करने के लिए। फिर भी, मैं इसे साझा करना चाहता था।
मेरे खेल पाश अब इस तरह दिखता है:

private static int UPDATES_PER_SECOND = 25; 
private static int UPDATE_INTERVAL = 1000/UPDATES_PER_SECOND * 1000000; 
private static int MAX_FRAMESKIP = 5; 

private long nextUpdate = System.nanoTime(); 

public void run() { 
    while (true) { 
     int skippedFrames = 0; 
     while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) { 
      long delta = UPDATE_INTERVAL; 
      this.currentState = this.createGameState(delta); 
      this.newPredictedNextState = this.createGameState(delta + UPDATE_INTERVAL, true); 

      this.nextUpdate += UPDATE_INTERVAL; 
      skippedFrames++; 
     } 

     double interpolation = (System.nanoTime() + UPDATE_INTERVAL - this.nextUpdate)/(double) UPDATE_INTERVAL; 
     this.repaintGame(interpolation); 
    } 
} 

आप देख सकते हैं, कुछ परिवर्तन:

  • delta = UPDATE_INTERVAL? हाँ। यह अब तक प्रयोगात्मक है, लेकिन मुझे लगता है कि यह काम करेगा। समस्या यह है कि, जैसे ही आप वास्तव में दो टाइमस्टैम्प से डेल्टा को कैलकुलेट करते हैं, आप फ्लोट गणना त्रुटियों को पेश कर रहे हैं। वे छोटे हैं, लेकिन आपके अपडेट पर विचार करने के लिए लाखों बार कहा जाता है, वे जोड़ते हैं। और चूंकि दूसरी बार-लूप सुनिश्चित करता है कि हम मिस्ड अपडेट्स पर पकड़ लेते हैं (यदि प्रतिपादन उदाहरण के लिए लंबा लगता है) तो हम सुनिश्चित हो सकते हैं कि हमें प्रति सेकंड हमारे 25 अपडेट मिलते हैं। सबसे खराब मामला: हम MAX_FRAMESKIP अपडेट से अधिक याद करते हैं - इस मामले में, अपडेट खो जाएंगे और गेम अंतराल हो जाएगा। फिर भी, जैसा कि मैंने कहा, प्रयोगात्मक। मैं इसे फिर से एक वास्तविक डेल्टा में बदल सकता हूं।
  • भविष्यवाणी अगला खेल राज्य? हाँ। GameState ऑब्जेक्ट है जिसमें सभी प्रासंगिक गेम जानकारी है, रेंडरर इस ऑब्जेक्ट को स्क्रीन पर गेम प्रस्तुत करने के लिए पास कर देता है।मेरे मामले में, मैंने रेंडरर को दो राज्य देने का फैसला किया: हम वर्तमान में वर्तमान खेल स्थिति और भविष्य में भविष्यवाणी की गई स्थिति के साथ UPDATE_INTERVAL के साथ उसे पारित करेंगे। इस तरह से रेंडरर दोनों के बीच आसानी से इंटरपोलेट करने के लिए इंटरपोलेशन मान का उपयोग कर सकता है। भविष्य के खेल की स्थिति की गणना करना वास्तव में काफी आसान है - क्योंकि आपकी अपडेट विधि (createGameState()) किसी भी तरह से डेल्टा मान लेती है, बस डेल्टा को UPDATE_INTERVAL तक बढ़ाएं - इस तरह भविष्य की स्थिति की भविष्यवाणी की जाएगी। भविष्य में राज्य, निश्चित रूप से, उपयोगकर्ता इनपुट आदि मानता है वही रहता है। यदि ऐसा नहीं होता है, तो अगला गेम स्टेट अपडेट परिवर्तनों का ख्याल रखेगा।
  • बाकी बहुत ज्यादा रहता है और डेविटर्स गेम लूप से लिया जाता है। MAX_FRAMESKIP हार्डवेयर वास्तव में धीमा है, यह सुनिश्चित करने के लिए कि हम समय-समय पर कुछ प्रस्तुत करते हैं, वास्तव में एक असफलता है। लेकिन अगर यह अंदर आता है, तो मुझे लगता है कि हम चरम झटके होंगे। इंटरपोलेशन पहले जैसा ही है - लेकिन अब रेंडरर केवल दो गैमेस्टेट्स के बीच इंटरपोलेट कर सकता है, इसमें इंटरपोलिंग नंबर को छोड़कर कोई तर्क नहीं है। यह अच्छा है!

शायद स्पष्टीकरण के लिए, एक उदाहरण।

public GameState createGameState(long delta, boolean ignoreNewInput) { 
    //Handle User Input and stuff if ignoreNewInput=false 

    GameState newState = this.currentState.copy(); 
    Sprite charSprite = newState.getCharacterSprite(); 
    charSprite.moveByX(charSprite.getMaxSpeed() * delta * charSprite.getMoveDirection().getX()); 
    //getMoveDirection().getX() is 1.0 when the right arrow key is pressed, otherwise 0.0 
} 

... तो, विंडो के रंग विधि में

public void paint(Graphics g) { 
    super.paint(g); 

    Graphics2D g2d = (Graphics2D) g; 

    Sprite currentCharSprite = currentGameState.getCharacterSprite(); 
    Sprite nextCharSprite = predictedNextState.getCharacterSprite(); 
    Position currentPos = currentCharSprite.getPosition(); 
    Position nextPos = nextCharSprite.getPosition(); 
    //Interpolate position 
    double x = currentPos.getX() + (nextPos.getX() - currentPos.getX()) * this.currentInterpolation; 
    double y = currentPos.getY() + (nextPos.getY() - currentPos.getY()) * this.currentInterpolation; 
    Position interpolatedCharPos = new Position(x, y); 
    g2d.drawImage(currentCharSprite.getImage(), (int) interpolatedCharPos.getX(), (int) interpolatedCharPos.getY(), null); 
} 

उत्तर

2

... अपने खेल तर्क का आधार नहीं है: यहाँ कैसे मैं चरित्र स्थिति (सरलीकृत एक छोटे) की गणना है धारणा पर कि अद्यतन अंतराल स्थिर हैं। एक गेम घड़ी शामिल करें जो दो अपडेटों के बीच पारित समय को ठीक से मापती है। फिर आप देरी पर अपनी सभी गणनाओं का आधार बना सकते हैं और वास्तविक अपडेट दर के बारे में चिंता करने की ज़रूरत नहीं है।

इस मामले में कारों वेग/इकाइयों में दिया जाएगा दूसरे और डेल्टा अंतिम अद्यतन के बाद से कुल सेकंड होगा:

car.position += car.velocity * delta; 

यह एक आम बात ड्राइंग से खेल तर्क को अद्यतन करने के अलग करने के लिए है फ्रेम। जैसा कि आपने कहा था, इससे हर समय और फिर फ्रेम को प्रतिपादित करके एक स्थिर अद्यतन दर रखना संभव हो जाता है।

लेकिन अद्यतन अंतराल को स्थिर रखने के बारे में इतना कुछ नहीं है। प्रति मिनट इकाई अद्यतनों की न्यूनतम संख्या होना वास्तव में महत्वपूर्ण है। कल्पना करें कि एक वाहन वास्तव में एक बाधा की तरफ तेजी से आगे बढ़ रहा है। यदि अद्यतन आवृत्ति बहुत कम है, तो दो अपडेट के बीच यात्रा दूरी कुल आकार की बाधाओं से अधिक हो सकती है। वाहन इसके माध्यम से आगे बढ़ेगा।

+0

प्रतिक्रिया के लिए धन्यवाद। जो चीज मुझे भ्रमित करती है वह यह है कि डेविटर अपने गेम लूप में डेल्टा (या इंटरपोलेशन, जिसे मूल रूप से डेल्टा से गणना की जाती है) को पास करने के लिए प्रस्ताव प्रस्तुत करता है, लेकिन अपडेट फ़ंक्शन नहीं जो गेम तर्क की देखभाल करता है। तो क्या आप कह रहे हैं कि दोनों कार्यों को डेल्टा मूल्य एक या दूसरे तरीके से प्राप्त करना चाहिए? – BlackWolf

+0

मैं कहूंगा कि डेल्टा को केवल अद्यतन विधि में ही जरूरी है, क्योंकि यह वह जगह है जहां आप गेम की दुनिया को संशोधित करते हैं। यह पर्याप्त होना चाहिए कि केवल रेंडर विधि * * [गेम वर्ल्ड मॉडल] (http://stackoverflow.com/tags/model/info) पढ़ती है। यह दुनिया का प्रतिनिधित्व करता है, लेकिन * इसके राज्य को नहीं बदला जाना चाहिए; इसलिए रेंडर विधि में डेल्टा अनावश्यक लगता है। – Lucius

+1

अच्छी तरह से, हाँ, मुझे लगता है कि मैं इसे इस तरह से लागू कर दूंगा, क्योंकि इससे मुझे और भी समझ आती है। एक sidenote के रूप में, मुझे अभी भी लगता है कि प्रतिपादन के लिए डेल्टा गुजरना अभी भी उपयोगी हो सकता है - यह दो गेम अपडेट के बीच आंदोलनों को अलग करना संभव बनाता है। अन्यथा यदि प्रति सेकंड 25 गेम अपडेट होते हैं लेकिन प्रति सेकंड 200 प्रतिपादन अपडेट होते हैं, तो 175 प्रस्तुतिकरण मूल रूप से बर्बाद हो जाते हैं। जैसा कि आपने कहा था, यह _could_ का मतलब है कि एक कार बाधा के माध्यम से "ड्राइव" करती है, लेकिन संभव है कि अपडेट विधि को अक्सर पर्याप्त कहा जाता है, इसलिए ऐसा नहीं होता/नहीं देखा जाता है। – BlackWolf