प्रक्षेप और थ्रेडिंग के लिए डेटा संरचनाएं?


20

मैं हाल ही में अपने खेल के साथ कुछ फ्रेम-रेट घबराने वाले मुद्दों के साथ काम कर रहा हूं, और ऐसा लगता है कि सबसे अच्छा समाधान क्लासिक फिक्स योर टाइमस्टेप में ग्लेन फिडलर (गेम्स पर गैफर) द्वारा सुझाया गया होगा ! लेख।

अब - मैं पहले से ही अपने अपडेट के लिए एक निश्चित समय-चरण का उपयोग कर रहा हूं। समस्या यह है कि मैं प्रतिपादन के लिए सुझाए गए प्रक्षेप नहीं कर रहा हूँ। यदि मेरा रेंडर रेट मेरे अपडेट रेट से मेल नहीं खाता है तो मुझे लगता है कि मुझे दोगुना या फ़्लिप किया गया है। ये दृष्टिगोचर हो सकते हैं।

इसलिए मैं अपने खेल में प्रक्षेप जोड़ना चाहूंगा - और मुझे यह जानने में दिलचस्पी है कि दूसरों ने इसका समर्थन करने के लिए अपने डेटा और कोड को कैसे संरचित किया है।

जाहिर है मुझे अपने रेंडर के लिए प्रासंगिक गेम स्टेट की जानकारी की दो प्रतियों को स्टोर (कहां? / कैसे?) करना होगा, ताकि यह उनके बीच अंतर हो सके।

इसके अतिरिक्त - यह थ्रेडिंग जोड़ने के लिए एक अच्छी जगह की तरह लगता है। मुझे लगता है कि एक अद्यतन थ्रेड खेल राज्य की तीसरी प्रति पर काम कर सकता है , अन्य दो प्रतियों को छोड़कर केवल रेंडर थ्रेड के लिए पढ़ा जा सकता है। (यह एक अच्छा विचार है?)

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

विशेष रूप से, मुझे लगता है, खेल राज्य से वस्तुओं को जोड़ने और हटाने के तरीके को संभालने की समस्या है।

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

जवाबों:


4

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

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

प्रतिपादन में आमतौर पर वस्तुओं के बीच तार्किक संबंधों के बारे में कुछ भी जानने की आवश्यकता नहीं होती है, इसलिए प्रतिपादन के लिए उपयोग की जाने वाली संरचना एक सादे वेक्टर, या अधिकांश साधारण पेड़ पर होगी।

उदाहरण

पारंपरिक डिजाइन

class Actor
{
  Matrix4x3 position;
  float fuel;
  float armor;
  float stamina;
  float age;

  void Simulate(float deltaT)
  {
    age += deltaT;
    armor -= HitByAWeapon();
  }
}

दृश्य स्थिति का उपयोग करना

class IVisualState
{
  public:
  virtual void Interpolate(const IVisualState &newVS, float f) {}
};
class Actor
{
  struct VisualState: public IVisualState
  {
    Matrix4x3 position;
    float fuel;
    float armor;
    float stamina;
    float age;

    virtual auto_ptr<IVisualState> Interpolate(const IVisualState &newVS, float f)
    {
      const VisualState &newState = static_cast<const VisualState &>(newVS);
      IVisualState *ret = new VisualState;
      ret->age = lerp(this->age,newState.age);
      // ... interpolate other properties as well, using any suitable interpolation method
      // liner, spline, slerp, whatever works best for the given property
      return ret;
    };
  };

  auto_ptr<VisualState> state_;

  void Simulate(float deltaT)
  {
    state_->age += deltaT;
    state_->armor -= HitByAWeapon();
  }
}

1
यदि आप "नया" (C ++ में एक आरक्षित शब्द) को पैरामीटर नाम के रूप में उपयोग नहीं करते हैं तो आपका उदाहरण पढ़ना आसान होगा।
स्टीव एस

3

मेरा समाधान अब तक की तुलना में कम सुरुचिपूर्ण / जटिल है। मैं अपने भौतिक विज्ञान इंजन के रूप में Box2D का उपयोग कर रहा हूं, इसलिए सिस्टम की एक से अधिक कॉपी रखने की स्थिति नहीं है (भौतिक प्रणाली को क्लोन करके उन्हें सिंक में रखने का प्रयास करें, एक बेहतर तरीका हो सकता है लेकिन मैं साथ नहीं आ सका। एक)।

इसके बजाय मैं फिजिक्स जनरेशन का रनिंग काउंटर रखता हूं । प्रत्येक अद्यतन भौतिकी पीढ़ी को बढ़ाता है, जब भौतिकी प्रणाली डबल अपडेट करती है, तो पीढ़ी डबल अपडेट भी काउंटर करती है।

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

मुझे पता नहीं था कि यदि भौतिकी इंजन बहुत तेज था तो क्या करना चाहिए। मैं लगभग तर्क देता हूं कि आपको तेज गति के लिए प्रक्षेप नहीं करना चाहिए। यदि आप दोनों करते हैं, तो आपको सावधान रहना होगा कि स्प्राइट्स को बहुत धीमी गति से अनुमान लगाने के बाद कूदने की ज़रूरत नहीं है।

जब मैंने इंटरपोलेशन सामग्री लिखी तो मैं 60Hz पर ग्राफिक्स और 30Hz पर भौतिकी चला रहा था। यह पता चला है कि जब यह 120 हर्ट्ज पर चलता है तो Box2D बहुत अधिक स्थिर होता है। इसकी वजह से मेरे प्रक्षेप कोड का बहुत कम उपयोग हो पाता है। लक्ष्य को दोगुना करके प्रति फ्रेम औसतन दो बार भौतिकी को औसत अपडेट अपडेट करें। घबराहट के साथ जो 1 या 3 बार भी हो सकता है, लेकिन लगभग कभी 0 या 4+ नहीं। उच्च भौतिकी दर थोड़े अपने आप से प्रक्षेप समस्या को ठीक करता है। 60 हर्ट्ज पर फिजिक्स और फ्रैमरेट दोनों को चलाने पर आपको प्रति फ्रेम 0-2 अपडेट मिल सकते हैं। 1 और 3 की तुलना में 0 और 2 के बीच का दृश्य अंतर बहुत बड़ा है।


3
मैंने भी यही पाया है। निकट -60 हर्ट्ज फ्रेम अपडेट के साथ 120 हर्ट्ज भौतिकी लूप इंटरपोलेशन को लगभग बेकार कर देता है। दुर्भाग्य से यह केवल उन खेलों के सेट के लिए काम करता है जो 120Hz भौतिकी लूप का खर्च उठा सकते हैं।

मैंने सिर्फ 120Hz अपडेट लूप में स्विच करने की कोशिश की। ऐसा लगता है कि मेरे भौतिकी को और अधिक स्थिर बनाने और मेरे खेल को सुचारू बनाने में दोहरा लाभ है, जो 60Hz फ्रेम दर पर नहीं है। नकारात्मक पक्ष यह है कि यह मेरे सभी सावधानीपूर्वक गेमप्ले भौतिकी को तोड़ता है - इसलिए यह निश्चित रूप से एक विकल्प है जिसे एक परियोजना में जल्दी चुना जाना चाहिए।
एंड्रयू रसेल

इसके अलावा: मैं वास्तव में आपके प्रक्षेप प्रणाली के आपके स्पष्टीकरण को नहीं समझता हूं। यह वास्तव में एक्सट्रपलेशन की तरह थोड़ा सा लगता है?
एंड्रयू रसेल

अच्छा निर्णय। मैंने वास्तव में एक एक्सट्रपलेशन सिस्टम का वर्णन किया है। स्थिति, वेग को देखते हुए, और अंतिम भौतिकी अपडेट के बाद से कब तक, मैं एक्सट्रपलेट करता हूं कि ऑब्जेक्ट कहाँ होगा यदि भौतिकी इंजन बंद नहीं हुआ था।
deft_code

2

मैंने समय-समय पर सुझाए गए टाइमस्टेप के लिए यह दृष्टिकोण सुना है, लेकिन खेलों में 10 वर्षों में, मैंने कभी भी वास्तविक दुनिया की परियोजना पर काम नहीं किया है जो एक निश्चित टाइमस्टेप और प्रक्षेप पर निर्भर था।

यह आमतौर पर एक चर टाइमस्टेप सिस्टम (25Hz-100Hz रेंज में फ्रैमेरेट्स की एक समझदार सीमा को संभालने) की तुलना में अधिक प्रयास लगता है।

मैंने एक बहुत छोटे प्रोटोटाइप के लिए एक बार निश्चित टाइमस्टेप + इंटरपोलेशन दृष्टिकोण की कोशिश की - कोई थ्रेडिंग नहीं, लेकिन फिक्स्ड टाइमस्टेप लॉजिक अपडेट, और उस समय अपडेट नहीं होने पर जितना संभव हो उतना तेज़ रेंडरिंग। मेरा दृष्टिकोण कुछ वर्गों जैसे कि CInterpolatedVector और CInterpolatedMatrix के पास था - जो पिछले / वर्तमान मानों को संग्रहीत करता था, और रेंडर कोड से एक एक्सेसर का उपयोग किया गया था, जो वर्तमान रेंडर समय के लिए मान प्राप्त करने के लिए (जो हमेशा पिछले और पिछले के बीच होगा) वर्तमान समय)

प्रत्येक गेम ऑब्जेक्ट, यह अपडेट के अंत में होगा, यह इन इंटरपोलिटबल वैक्टर / मैट्रिसेस के एक सेट के लिए वर्तमान स्थिति है। इस तरह की चीज़ को थ्रेडिंग का समर्थन करने के लिए बढ़ाया जा सकता है, आपको कम से कम 3 सेट मानों की आवश्यकता होगी - एक जिसे अपडेट किया जा रहा था, और बीच में प्रक्षेप करने के लिए कम से कम 2 पिछले मान ...

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

IMHO, यह केवल परिवर्तनशील टाइमस्टेप पर जाने के लिए सबसे अच्छा है - जब तक आप एक आरटीएस, या अन्य गेम नहीं बना रहे हैं, जहां आपके पास बहुत बड़ी संख्या में ऑब्जेक्ट हैं, और नेटवर्क गेम के लिए सिंक में 2 स्वतंत्र सिमुलेशन रखने हैं (केवल आदेश / आदेश भेजने पर) नेटवर्क, वस्तु स्थिति के बजाय)। उस स्थिति में, फिक्स्ड-टाइमस्टेप एकमात्र विकल्प है।


1
ऐसा लगता है कि कम से कम क्वेक 3 इस दृष्टिकोण का उपयोग कर रहा था, डिफ़ॉल्ट रूप से "टिक" 20 एफपीएस (50 एमएस) था।
सुमा

दिलचस्प। मुझे लगता है कि यह अत्यधिक प्रतिस्पर्धी मल्टीप्लेयर पीसी गेम के लिए फायदे हैं, यह सुनिश्चित करने के लिए कि तेजी से पीसी / उच्च फ्रैमरेट्स को बहुत अधिक लाभ नहीं मिलता है (भौतिकी / टक्कर व्यवहार में अधिक उत्तरदायी नियंत्रण, या छोटे-लेकिन-शोषणकारी अंतर) ?
ब्लूस्कैन

1
क्या आप 10 वर्षों में किसी भी ऐसे खेल में नहीं भागे हैं जो भौतिकी को सिमुलेशन और रेंडरर के साथ लॉकस्टेप में नहीं चलाता है? क्योंकि जिस क्षण आप ऐसा करेंगे कि आपको अपने एनिमेशन में स्पष्ट रूप से अंतरंगता को स्वीकार करना या स्वीकार करना पड़ेगा।
काज

2

जाहिर है मुझे अपने रेंडर के लिए प्रासंगिक गेम स्टेट की जानकारी की दो प्रतियों को स्टोर (कहां? / कैसे?) करना होगा, ताकि यह उनके बीच अंतर हो सके।

हां, शुक्र है कि यहां कुंजी "मेरे रेंडरर के लिए प्रासंगिक" है। यह एक पुरानी स्थिति और मिश्रण में इसके लिए टाइमस्टैम्प जोड़ने से अधिक नहीं हो सकता है। 2 स्थितियों को देखते हुए आप उन दोनों के बीच की स्थिति में अंतर कर सकते हैं, और यदि आपके पास एक 3 डी एनीमेशन सिस्टम है, तो आप आम तौर पर वैसे भी उस सटीक बिंदु पर मुद्रा का अनुरोध कर सकते हैं।

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

इसके अतिरिक्त - यह थ्रेडिंग जोड़ने के लिए एक अच्छी जगह की तरह लगता है। मुझे लगता है कि एक अद्यतन थ्रेड खेल राज्य की तीसरी प्रति पर काम कर सकता है, अन्य दो प्रतियों को छोड़कर केवल रेंडर थ्रेड के लिए पढ़ा जा सकता है। (यह एक अच्छा विचार है?)

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


1

ध्यान दें मैं वास्तव में प्रक्षेप में नहीं देख रहा हूँ इसलिए यह उत्तर इसे संबोधित नहीं करता है; मैं रेंडरिंग थ्रेड के लिए गेम स्टेट की एक प्रति और अपडेट थ्रेड के लिए एक और संबंधित हूं। इसलिए मैं प्रक्षेप के मुद्दे पर टिप्पणी नहीं कर सकता, हालांकि आप प्रक्षेप करने के लिए निम्नलिखित समाधान को संशोधित कर सकते हैं।

मैं इस बारे में सोच रहा था क्योंकि मैं डिजाइन कर रहा हूं और एक मल्टीथ्रेड इंजन के बारे में सोच रहा हूं। इसलिए मैंने स्टैक ओवरफ्लो पर एक सवाल पूछा, "जर्नलिंग" या "लेनदेन" डिज़ाइन पैटर्न को कैसे लागू किया जाए । मुझे कुछ अच्छी प्रतिक्रियाएं मिलीं, और स्वीकार किए गए जवाब ने वास्तव में मुझे सोच लिया।

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

फिर, जैसा कि जेफरी कहते हैं , आपके GameState ऑब्जेक्ट के उदाहरण तेज़, मेमोरी कुशल और थ्रेड सुरक्षित हैं। बड़ा नकारात्मक यह है कि मॉडल के बारे में कुछ भी बदलने के लिए, आपको मॉडल को फिर से बनाने की आवश्यकता है, इसलिए आपको वास्तव में सावधान रहने की आवश्यकता है कि आपका कोड एक बड़ी गड़बड़ी में बदल न जाए। GameState ऑब्जेक्ट के भीतर एक नए मान के लिए एक चर सेट करना var = val;कोड की लाइनों के संदर्भ में, बस की तुलना में अधिक शामिल है ।

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


यह वास्तव में एक दिलचस्प संरचना है। हालांकि मुझे यकीन नहीं है कि यह एक खेल के लिए अच्छा काम करेगा - जैसा कि सामान्य मामला वस्तुओं का एक काफी सपाट पेड़ है जो प्रत्येक फ्रेम के ठीक एक बार बदलता है। इसके अलावा, क्योंकि डायनेमिक मेमोरी एलोकेशन एक बड़ी संख्या है।
एंड्रयू रसेल

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

... कि गतिशील आवंटन नहीं होगा, प्रचारित स्मृति का सिर्फ गतिशील उपयोग;)
काज

1

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

मुझे तब एहसास हुआ कि मुझे केवल तीन राज्यों को रखने की आवश्यकता है जो प्रदान किए जाने वाले थे। मेरा अपडेट थ्रेड अब "रेंडरकोमैंड्स" के तीन छोटे बफ़र्स में से एक को भरता है, और रेंडरर नवीनतम बफर से पढ़ता है जो वर्तमान में नहीं लिखा जा रहा है, जो थ्रेड्स को एक-दूसरे पर इंतजार करने से रोकता है।

मेरे सेटअप में, प्रत्येक रेंडरकोमैंड में 3 डी ज्यामिति / सामग्री, एक परिवर्तन मैट्रिक्स, और रोशनी की एक सूची है जो इसे प्रभावित करती है (अभी भी आगे रेंडरिंग कर रही है)।

मेरे रेंडर थ्रेड को अब किसी भी पुलिंग या हल्की दूरी की गणना नहीं करनी है, और यह बड़े दृश्यों पर काफी हद तक काम करता है।

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