मैं C ++ इंजन प्रोग्रामिंग में सिंगललेट्स का सही उपयोग कैसे करूं?


16

मुझे पता है कि सिंगलनेट खराब हैं, मेरे पुराने गेम इंजन ने एक सिंगलटन गेम गेम का इस्तेमाल किया, जो सभी डेटा को वास्तविक गेम लूप में रखने से सब कुछ संभालता है। अब मैं नया बना रहा हूं।

समस्या यह है कि एसएफएमएल में कुछ आकर्षित करने के लिए आप window.draw(sprite)जहां विंडो ए का उपयोग करते हैं sf::RenderWindow। यहाँ मैं देख रहा हूँ 2 विकल्प हैं:

  1. एक सिंगलटन गेम ऑब्जेक्ट बनाएं जो गेम की प्रत्येक इकाई को पुनः प्राप्त करता है (जो मैंने पहले इस्तेमाल किया था)
  2. संस्थाओं के लिए यह निर्माण करें: Entity(x, y, window, view, ...etc)(यह सिर्फ हास्यास्पद और कष्टप्रद है)

एंटिटी के कंस्ट्रक्टर को सिर्फ x और y पर रखते हुए ऐसा करने का उचित तरीका क्या होगा?

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


1
आप 'रेंडर' फ़ंक्शन के तर्क के रूप में विंडो पास कर सकते हैं।
डारी

25
सिंगलेट्स खराब नहीं हैं! वे उपयोगी और कभी-कभी आवश्यक हो सकते हैं (बेशक यह बहस का विषय है)।
22

3
सादे ग्लोबल्स के साथ सिंगलटन को बदलने के लिए स्वतंत्र महसूस करें। वैश्विक स्तर पर आवश्यक संसाधनों को "मांग पर" बनाने का कोई मतलब नहीं है, उन्हें पास करने का कोई मतलब नहीं है। संस्थाओं के लिए, आप एक "स्तर" वर्ग का उपयोग कर सकते हैं, हालांकि कुछ चीजें रखने के लिए जो उन सभी के लिए प्रासंगिक हैं।
सांप 5

मैं अपनी खिड़की और अपने मुख्य में अन्य निर्भरता की घोषणा करता हूं, और फिर मेरी अन्य कक्षाओं में संकेत हैं।
करेज़ १०

1
@JAB आसानी से मुख्य () से मैनुअल इनिशियलाइज़ेशन के साथ तय किया गया है। आलसी आरंभीकरण एक अज्ञात क्षण में होता है, जो कोर सिस्टम के लिए एक अच्छा विचार नहीं है, कभी भी।
सांप

जवाबों:


3

केवल प्रत्येक इकाई के अंदर स्प्राइट को प्रस्तुत करने के लिए आवश्यक डेटा संग्रहीत करें, फिर इसे इकाई से पुनर्प्राप्त करें और इसे रेंडरिंग के लिए विंडो में पास करें। संस्थाओं के अंदर कोई विंडो स्टोर करने या डेटा देखने की आवश्यकता नहीं है।

आपके पास एक शीर्ष-स्तरीय गेम या इंजन वर्ग हो सकता है जो एक स्तर वर्ग रखता है (वर्तमान में उपयोग की जा रही सभी इकाइयाँ रखता है), और एक रेंडरर वर्ग (इसमें रेंडर करने के लिए विंडो, दृश्य और कुछ भी शामिल है)।

तो आपके शीर्ष-स्तरीय वर्ग में गेम अपडेट लूप जैसा दिख सकता है:

EntityList entities = mCurrentLevel.getEntities();
for(auto& i : entities){
  // Run game logic...
  i->update(...);
}
// Render all the entities
for(auto& i : entities){
  mRenderer->draw(i->getSprite());
}

3
एक सिंगलटन के बारे में कुछ भी आदर्श नहीं है। जब आपको नहीं करना है तो कार्यान्वयन इंटर्न को सार्वजनिक क्यों करें? Logger::getInstance().Log(...)सिर्फ लिखने के बजाय क्यों Log(...)? कक्षा को बेतरतीब ढंग से आरंभ क्यों करें जब पूछा जाए कि क्या आप इसे केवल एक बार मैन्युअल रूप से कर सकते हैं? स्थैतिक ग्लोबल्स को संदर्भित करने वाला एक वैश्विक कार्य केवल बनाना और उपयोग करना अधिक सरल है।
साँप ५

@ snake5 स्टाॅक एक्सचेंज पर सिंग्लेट को सही ठहराना हिटलर के प्रति सहानुभूति रखने जैसा है।
विली बकरी

30

सरल तरीका सिर्फ उस चीज को बनाना है जो कभी हुआ करती थी Singleton<T> एक वैश्विक हुआ करती Tथी। ग्लोबल्स की समस्याएं भी हैं, लेकिन वे एक तुच्छ बाधा को लागू करने के लिए अतिरिक्त काम और बॉयलरप्लेट कोड के एक समूह का प्रतिनिधित्व नहीं करते हैं। यह मूल रूप से एकमात्र समाधान है जो इकाई निर्माणकर्ता को छूने (संभावित रूप से) को शामिल नहीं करेगा।

अधिक कठिन, लेकिन संभवतः बेहतर दृष्टिकोण यह है कि आप अपनी आवश्यकताओं के लिए अपनी निर्भरता को पास करें । हां, इसमें पासिंग ए शामिल हो सकती हैWindow * ऐसी वस्तुओं का एक समूह (आपकी इकाई की तरह) जो सकल दिखती हैं। तथ्य यह है कि यह सकल दिखता है आपको कुछ बताना चाहिए: आपका डिज़ाइन सकल हो सकता है।

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

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

यदि आप अपने मौजूदा डिज़ाइन के साथ ऐसा करने का प्रयास करना चाहते हैं, तो आपको अपने वर्तमान कार्यान्वयन के बारे में और अधिक विवरण पोस्ट करने की आवश्यकता हो सकती है क्योंकि इन परिवर्तनों को करने के लिए वास्तव में एक सामान्य चेकलिस्ट नहीं है। या चैट में इस पर चर्चा करें ।

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


3
मैं C ++ से परिचित नहीं हूं, लेकिन क्या इस भाषा के लिए सहज निर्भरता इंजेक्शन फ्रेमवर्क नहीं हैं?
13

1
मैं उनमें से किसी को भी "आरामदायक" के रूप में वर्णित नहीं करूंगा, और मैं उन्हें सामान्य रूप से विशेष रूप से उपयोगी नहीं मानता, लेकिन दूसरों को उनके साथ अलग अनुभव हो सकता है इसलिए उन्हें लाने के लिए यह एक अच्छा बिंदु है।

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

1
+1 के लिए "तथ्य यह है कि यह सकल दिखता है, आपको कुछ बताना चाहिए: आपका डिज़ाइन सकल हो सकता है।"
शैडोहॉक

आदर्श केस और व्यावहारिक उत्तर दोनों देने के लिए +1।

6

Sf से वंशानुगत :: रेंडरविंडो

एसएफएमएल वास्तव में आपको इसकी कक्षाओं से विरासत में लेने के लिए प्रोत्साहित करता है।

class GameWindow: public sf::RenderWindow{};

यहां से, आप आरेखण संस्थाओं के लिए सदस्य ड्रा फ़ंक्शन बनाते हैं।

class GameWindow: public sf::RenderWindow{
public:
 void draw(const Entity& entity);
};

अब आप यह कर सकते हैं:

GameWindow window;
Entity entity;

window.draw(entity);

आप इसे एक कदम आगे भी ले जा सकते हैं, यदि आपके निकाय Sf :: स्प्राइट से Entity विरासत बनाकर अपने स्वयं के अनूठे स्प्राइट को धारण करने जा रहे हैं।

class Entity: public sf::Sprite{};

अब sf::RenderWindowकेवल संस्थाओं को आकर्षित कर सकते हैं, और संस्थाओं के पास अब जैसे कार्य setTexture()और हैं setColor()। इकाई भी स्प्राइट की स्थिति का उपयोग अपनी स्थिति के रूप में कर सकती है, जिससे आप setPosition()एंटिटी और उसके स्प्राइट को स्थानांतरित करने के लिए फ़ंक्शन का उपयोग कर सकते हैं ।


अंत में , यह बहुत अच्छा है यदि आपके पास बस है:

window.draw(game);

नीचे कुछ त्वरित उदाहरण कार्यान्वयन हैं

class GameWindow: public sf::RenderWindow{
 sf::Sprite entitySprite; //assuming your Entities don't need unique sprites.
public:
 void draw(const Entity& entity){
  entitySprite.setPosition(entity.getPosition());
  sf::RenderWindow::draw(entitySprite);
 }
};

या

class GameWindow: public sf::RenderWindow{
public:
 void draw(const Entity& entity){
  sf::RenderWindow::draw(entity.getSprite()); //assuming Entities hold their own sprite.
 }
};

3

आप गेम डेवलपमेंट में एकल से बचते हैं, उसी तरह जैसे आप हर दूसरे तरह के सॉफ्टवेयर डेवलपमेंट में उनसे बचते हैं: आप निर्भरता से गुजरते हैं

जिस तरह की है कि बाहर के साथ, आप निर्भरता नंगे प्रकार (जैसे के रूप में सीधे पारित करने के लिए चुन सकते हैं int, Window*, आदि) या आप (जैसे प्रकार के आवरण एक या अधिक कस्टम में उन्हें पारित करने के लिए चुन सकते हैं EntityInitializationOptions)।

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


3

सिंगलटन खराब नहीं हैं। इसके बजाय वे दुरुपयोग करना आसान है। दूसरी ओर, ग्लोबल्स का दुरुपयोग करना और भी आसान है और इसमें अधिक समस्याएं हैं।

एक एकल को वैश्विक के साथ बदलने के लिए एकमात्र वैध कारण धार्मिक सिंगलटन हैटर्स को शांत करना है।

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

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


1
मैं एक धार्मिक गायक से नफरत करने वाला हूं और मैं एक वैश्विक समाधान भी नहीं मानता। : एस
डैन पेंट्री

1

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

RenderSystem(IWindow* window);

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

अब यह अधिक परीक्षण योग्य, मॉड्यूलर है, और यह एक विशिष्ट कार्यान्वयन से भी अलग है। यह केवल एक इंटरफ़ेस पर निर्भर है, न कि एक ठोस कार्यान्वयन।

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