गेम लूप, एक बार स्थितियों की जांच कैसे करें, कुछ करें, फिर न करें


13

उदाहरण के लिए, मेरे पास गेम क्लास है और यह intखिलाड़ी के जीवन को ट्रैक करता है। मेरी एक शर्त है

if ( mLives < 1 ) {
    // Do some work.
}

हालांकि यह स्थिति फायरिंग करती है और काम बार-बार किया जाता है। उदाहरण के लिए, मैं 5 सेकंड में गेम से बाहर निकलने के लिए टाइमर सेट करना चाहता हूं। वर्तमान में, यह हर फ्रेम में इसे 5 सेकंड तक सेट करता रहेगा और खेल कभी समाप्त नहीं होगा।

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

इसका उचित समाधान क्या है (मेरे उदाहरण के लिए नहीं, बल्कि समस्या डोमेन के लिए)? आप एक खेल में यह कैसे करेंगे?


उत्तर के लिए धन्यवाद, मेरा समाधान अब ktodisco और crancran उत्तरों का संयोजन है।
एडीवी 223

जवाबों:


13

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

void subtractPlayerLife() {
    // Generic life losing stuff, such as mLives -= 1
    if (mLives < 1) {
        // Do specific stuff.
    }
}

यह, बदले में, मानता है कि subtractPlayerLifeकेवल विशिष्ट अवसरों पर बुलाया जाएगा, जो शायद उन परिस्थितियों से उत्पन्न होगा जो आपको हर फ्रेम (टकराव, शायद) की जांच करनी चाहिए

ध्यान से नियंत्रित करके कि आपका कोड कैसे निष्पादित होता है, आप स्थिर बूलियन जैसे गन्दे समाधानों से बच सकते हैं और एक ही फ्रेम में कितना कोड निष्पादित किया जाता है, इस पर बिट-बाय-बिट भी काट सकते हैं।

अगर ऐसा कुछ है जो सिर्फ रिफ्लेक्टर को असंभव लगता है, और आपको वास्तव में केवल एक बार जांचना है, तो राज्य (जैसे enum) या स्थिर बूलियन जाने का रास्ता है। खुद को स्टैटिक्स घोषित करने से बचने के लिए, आप निम्न चाल का उपयोग कर सकते हैं:

#define ONE_TIME_IF(condition, statement) \
    static bool once##__LINE__##__FILE__; \
    if(!once##__LINE__##__FILE__ && condition) \
    { \
        once##__LINE__##__FILE__ = true; \
        statement \
    }

जो निम्नलिखित कोड बनायेगा:

while (true) {
    ONE_TIME_IF(1 > 0,
        printf("something\n");
        printf("something else\n");
    )
}

प्रिंट करें somethingऔर something elseकेवल एक बार। यह सबसे अच्छे दिखने वाले कोड का उत्पादन नहीं करता है, लेकिन यह काम करता है। मुझे पूरा यकीन है कि इसे बेहतर बनाने के तरीके भी हैं, शायद बेहतर चर नामों को सुनिश्चित करके। यह #defineकेवल रनटाइम के दौरान एक बार काम करेगा। इसे रीसेट करने का कोई तरीका नहीं है, और इसे बचाने का कोई तरीका नहीं है।

ट्रिक के बावजूद, मैं दृढ़ता से आपके कोड फ़्लो को बेहतर नियंत्रित करने की सलाह देता हूं, और इसे #defineअंतिम उपाय के रूप में या केवल डीबग उद्देश्यों के लिए उपयोग कर रहा हूं ।


+1 अच्छा एक बार अगर समाधान। गन्दा, लेकिन शांत।
जेंडर

1
आपके One_TIME_IF के साथ एक बड़ी समस्या है, आप इसे फिर से नहीं बना सकते। उदाहरण के लिए खिलाड़ी मुख्य मेनू में वापस चला जाता है और बाद में खेल के दृश्य में वापस आ जाता है। वह कथन फिर से आग नहीं लगेगा। और ऐसी स्थितियां हैं जो आपको पहले से ही प्राप्त ट्राफियों की सूची के लिए आवेदन रन के बीच रखने की आवश्यकता हो सकती है। यदि खिलाड़ी दो अलग-अलग एप्लिकेशन रन में इसे प्राप्त करता है, तो आपकी विधि ट्रॉफी को दो बार पुरस्कृत करेगी।
अली

1
@ Gajoo यह सच है, यह एक समस्या है अगर स्थिति को रीसेट करने या सहेजने की आवश्यकता है। मैंने उसे स्पष्ट करने के लिए संपादन किया है। इसलिए मैंने इसे बहुत अंतिम उपाय के रूप में या केवल डिबगिंग के लिए अनुशंसित किया।
केविंटोडिस्को

क्या मैं (0) मुहावरा करते हुए {} सुझाव दे सकता हूं? मुझे पता है कि मैं व्यक्तिगत रूप से कुछ पेंच करूँगा और एक अर्धविराम डालूँगा जहाँ मुझे नहीं करना चाहिए। कूल मैक्रो हालांकि, ब्रावो।
michael.bartnett

5

यह राज्य पैटर्न का उपयोग करने के क्लासिक मामले जैसा लगता है। एक राज्य में आप जीवन के लिए अपनी स्थिति की जांच करते हैं <1. यदि वह स्थिति सच हो जाती है, तो आप देरी की स्थिति में संक्रमण करते हैं। यह विलंबित अवस्था निर्दिष्ट अवधि का इंतजार करती है और फिर आपकी निकास स्थिति में संक्रमण करती है।

सबसे तुच्छ दृष्टिकोण में:

switch(mCurrentState) {
  case LIVES_GREATER_THAN_0:
    if(lives < 1) mCurrentState = LIVES_LESS_THAN_1;
    break;
  case LIVES_LESS_THAN_1:
    timer.expires(5000); // expire in 5 seconds
    mCurrentState = TIMER_WAIT;
    break;
  case TIMER_WAIT:
    if(timer.expired()) mCurrentState = EXIT;
    break;
  case EXIT:
    mQuit = true;
    break;
}

आप एक स्विच स्टेटमेंट के बजाय राज्यों के बीच बदलते / धकेलने / संक्रमण को संभालने के लिए एक StateManager / StateMachine के साथ एक स्टेट क्लास का उपयोग करने पर विचार कर सकते हैं।

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


1

यदि आप प्रदर्शन के बारे में चिंतित हैं, तो कई बार जाँच का प्रदर्शन आमतौर पर महत्वपूर्ण नहीं होता है; बूल की स्थिति होने के लिए ditto।

यदि आप किसी और चीज में रुचि रखते हैं, तो आप इस समस्या को हल करने के लिए अशक्त डिजाइन पैटर्न के समान कुछ का उपयोग कर सकते हैं।

इस स्थिति में, मान लें कि आप एक विधि को कॉल करना चाहते हैं fooयदि खिलाड़ी के पास कोई जीवन नहीं बचा है। आप इसे दो वर्गों के रूप में लागू करेंगे, एक जो कॉल करता है foo, और एक जो कुछ नहीं करता है। उन्हें कॉल करें DoNothingऔर उन्हें CallFooपसंद करें:

class SomeLevel {
  int numLives = 3;
  FooClass behaviour = new DoNothing();
  ...
  void tick() { 
    if (numLives < 1) {
      behaviour = new CallFoo();
    }

    behaviour.execute();
  }
}

यह "कच्चे" उदाहरण का एक सा है; प्रमुख बिंदु हैं:

  • कुछ उदाहरणों पर नज़र रखें FooClassजिनमें से DoNothingया हो सकता है CallFoo। यह एक आधार वर्ग, अमूर्त वर्ग या इंटरफ़ेस हो सकता है।
  • हमेशा executeउस कक्षा की विधि को बुलाओ ।
  • डिफ़ॉल्ट रूप से, वर्ग कुछ भी नहीं करता है ( DoNothingउदाहरण)।
  • जब जीवन समाप्त हो जाता है, तो उदाहरण को उस उदाहरण के लिए स्वैप किया जाता है जो वास्तव में कुछ करता है ( CallFooउदाहरण)।

यह अनिवार्य रूप से रणनीति और अशक्त पैटर्न के मिश्रण की तरह है।


लेकिन यह नया कॉलफू () प्रत्येक फ्रेम बनाता रहेगा, जिसे आपको नए से पहले डिलीट करना होगा, और ऐसा भी होता रहेगा जो समस्या को ठीक नहीं करता है। उदाहरण के लिए अगर मेरे पास CallFoo () एक ऐसा तरीका है जो कुछ ही सेकंड में निष्पादित करने के लिए टाइमर सेट करता है, तो उस टाइमर को प्रत्येक फ्रेम पर उसी समय सेट किया जाएगा। जहां तक ​​मैं बता सकता हूं कि आपका समाधान वैसा ही है, जैसे (संख्याएँ <1) {// करो सामान} बाकी {// खाली}
एडी 422 '

हम्म, मैं देख रहा हूँ कि तुम क्या कह रहे हो। समाधान हो सकता है कि ifचेक को FooClassउदाहरण में ही स्थानांतरित किया जाए, इसलिए इसे केवल एक बार निष्पादित किया जाए, और इंस्टेंटिंग के लिए डिट्टो CallFoo
ashes999
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.