एक घटक आधारित खेल डिजाइनिंग


16

मैं एक शूटर (जैसे 1942, क्लासिक 2 डी ग्राफिक्स) लिख रहा हूं और मैं एक घटक आधारित दृष्टिकोण का उपयोग करना चाहता हूं। अब तक मैंने निम्नलिखित डिज़ाइन के बारे में सोचा:

  1. प्रत्येक खेल तत्व (एयरशिप, प्रोजेक्टाइल, पावरअप, दुश्मन) एक इकाई है

  2. प्रत्येक इकाई घटकों का एक समूह है जिसे रन-टाइम पर जोड़ा या हटाया जा सकता है। उदाहरण स्थिति, स्प्राइट, स्वास्थ्य, आईए, नुकसान, बाउंडिंगबॉक्स आदि हैं।

यह विचार है कि Airship, Projectile, Enemy, Powerup खेल वर्ग नहीं हैं। एक इकाई केवल उन घटकों द्वारा परिभाषित की जाती है जो इसके मालिक हैं (और जो समय के दौरान बदल सकते हैं)। तो खिलाड़ी Airship स्प्राइट, स्थिति, स्वास्थ्य और इनपुट घटकों के साथ शुरू होता है। पॉवरअप में स्प्राइट, पोजिशन, बाउंडिंगबॉक्स होता है। और इसी तरह।

मुख्य लूप गेम "भौतिकी" का प्रबंधन करता है, अर्थात घटक एक दूसरे से कैसे बातचीत करते हैं:

foreach(entity (let it be entity1) with a Damage component)
    foreach(entity (let it be entity2) with a Health component)
    if(the entity1.BoundingBox collides with entity2.BoundingBox)
    {
        entity2.Health.decrease(entity1.Damage.amount());
    }

foreach(entity with a IA component)
    entity.IA.update(); 

foreach(entity with a Sprite component)
    draw(entity.Sprite.surface()); 

...

घटक मुख्य C ++ एप्लिकेशन में हार्डकोड किए गए हैं। एक्सएमएल फ़ाइल में एंटिटीज़ को परिभाषित किया जा सकता है (एक लुआ या पायथन फ़ाइल में आईए हिस्सा)।

मुख्य लूप संस्थाओं के बारे में बहुत परवाह नहीं करता है: यह केवल घटकों का प्रबंधन करता है। सॉफ्टवेयर डिज़ाइन को इसकी अनुमति देनी चाहिए:

  1. एक घटक को देखते हुए, वह इकाई प्राप्त करें जिसका वह संबंधित है

  2. एक इकाई को देखते हुए, "टाइप" का घटक प्राप्त करें

  3. सभी संस्थाओं के लिए, कुछ करो

  4. सभी इकाई के घटक के लिए, कुछ करें (जैसे: क्रमबद्ध करें)

मैं निम्नलिखित के बारे में सोच रहा था:

class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component {...};
class Position : public Component {...};
class IA : public Component {... virtual void update() = 0; };

// I don't remember exactly the boost::fusion map syntax right now, sorry.
class Entity
{
   int id; // entity id
   boost::fusion::map< pair<Sprite, Sprite*>, pair<Position, Position*> > components;
   template <class C> bool has_component() { return components.at<C>() != 0; }
   template <class C> C* get_component() { return components.at<C>(); }
   template <class C> void add_component(C* c) { components.at<C>() = c; }
   template <class C> void remove_component(C* c) { components.at<C>() = 0; }
   void serialize(filestream, op) { /* Serialize all componets*/ }
...
};

std::list<Entity*> entity_list;

इस डिजाइन के साथ मैं # 1, # 2, # 3 (बढ़ावा देने के लिए धन्यवाद :: फ्यूजन :: मैप एल्गोरिदम) और # 4 प्राप्त कर सकता हूं। इसके अलावा सब कुछ हे (1) है (ठीक है, बिल्कुल नहीं, लेकिन यह अभी भी बहुत तेज है)।

एक और "सामान्य" दृष्टिकोण भी है:

class Entity;
class Component { Entity* entity; ... virtual void serialize(filestream, op) = 0; ...}
class Sprite : public Component { static const int type_id = 0; };
class Position : public Component { static const int type_id = 1; };

class Entity
{
   int id; // entity id
   std::vector<Component*> components;
   bool has_component() { return components[i] != 0; }
   template <class C> C* get_component() { return dynamic_cast<C> components[C::id](); } // It's actually quite safe
...
};

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

आपको कौन सा दृष्टिकोण बेहतर लगता है? बढ़ावा है :: फ्यूजन मैप को उस तरह से उपयोग करने के लिए अनुकूल है?


2
एक नीचता क्यों? इस सवाल में क्या गलत है?
एमिलियानो

जवाबों:


6

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

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

निश्चित रूप से डेटा-उन्मुख डिज़ाइन के लिए Google आस-पास है क्योंकि यह घटक-आधारित प्रणालियों से संबंधित है, क्योंकि यह विषय बहुत ऊपर आता है और वहाँ पर काफी चर्चा और वास्तविक डेटा है।


"डेटा-ओरिएंटेड" से आपका क्या तात्पर्य है?
एमिलियानो

Google पर बहुत सारी जानकारी है, लेकिन यहां एक सभ्य लेख है जो पॉप अप करता है जो एक उच्च-स्तरीय अवलोकन प्रदान करता है, इसके बाद एक चर्चा होती है क्योंकि यह घटक प्रणालियों से संबंधित है: gamesfromwithin.com/data-oriented-design , gamedev। net / विषय /…
Skyler यॉर्क

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

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

-1

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

// declare components here------------------------------
class component
{
};

class health:public component
{
public:
    int value;
};

class boundingbox:public component
{
public :
    int left,right,top,bottom;
    bool collision(boundingbox& other)
    {
        if (left < other.right || right > other.left)
            if (top < other.bottom || bottom > other.top)
                return true;
        return false;
    }
};

class damage : public component
{
public:
    int value;
};

// declare enteties here------------------------------

class entity
{
    virtual int id() = 0;
    virtual int size() = 0;
};

class aircraft :public entity, public health,public boundingbox
{
    virtual int id(){return 1;}
    virtual int size() {return sizeof(*this);};
};

class bullet :public entity, public damage, public boundingbox
{
    virtual int id(){return 2;}
    virtual int size() {return sizeof(*this);};
};

int main()
{
    entity* gameobjects[3];
    gameobjects[0] = new aircraft;
    gameobjects[1] = new bullet;
    gameobjects[2] = new bullet;
    for (int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            if (dynamic_cast<boundingbox*>(gameobjects[i]) && dynamic_cast<boundingbox*>(gameobjects[j]) &&
                dynamic_cast<boundingbox*>(gameobjects[i])->collision(*dynamic_cast<boundingbox*>(gameobjects[j])))
                if (dynamic_cast<health*>(gameobjects[i]) && dynamic_cast<damage*>(gameobjects[j]))
                    dynamic_cast<health*>(gameobjects[i])->value -= dynamic_cast<damage*>(gameobjects[j])->value;
}

इस दृष्टिकोण में प्रत्येक घटक एक इकाई के लिए एक आधार होता है, इसलिए यह घटक जो सूचक है वह भी एक इकाई है! दूसरी बात जो आप पूछते हैं, वह कुछ इकाई के घटकों तक प्रत्यक्ष पहुंच है। जब मुझे मेरे द्वारा उपयोग की जाने वाली किसी एक संस्था में क्षति का उपयोग करने की आवश्यकता होती है dynamic_cast<damage*>(entity)->value, तो यदि entityक्षति घटक है तो यह मूल्य वापस कर देगा। यदि आप सुनिश्चित नहीं हैं कि entityघटक क्षति है या नहीं, तो आप आसानी से if (dynamic_cast<damage*> (entity))रिटर्न वैल्यू की जांच कर सकते हैं dynamic_cast, हमेशा NULL है यदि कास्ट मान्य नहीं है और एक ही पॉइंटर है लेकिन अनुरोधित प्रकार के साथ यदि यह वैध है। तो सभी के साथ कुछ करने के लिए entitiesजिसके पास कुछ है componentआप इसे नीचे की तरह कर सकते हैं

for (int i=0;i<enteties.size();i++)
    if (dynamic_cast<component*>(enteties[i]))
        //do somthing here

अगर कोई अन्य प्रश्न है तो मुझे उत्तर देने में खुशी होगी।


मुझे डाउन वोट क्यों मिला? मेरे समाधान में क्या गलत था?
अली

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

सबसे पहले सवाल संरचना के लिए पूछता है कि हर घटक उदाहरण केवल एक इकाई से संबंधित है, और आप केवल bool isActiveआधार कमेंट वर्ग में जोड़कर घटकों को सक्रिय और निष्क्रिय कर सकते हैं । जब आप एंटाइटिस को परिभाषित कर रहे हैं, तब भी प्रयोग करने योग्य घटकों के परिचय की आवश्यकता है, लेकिन मैं इसे एक समस्या के रूप में नहीं मानता हूं, और फिर भी आपके पास इटैलियन अपडेट अपडेट (जैसे कुछ भी याद रखना) को अलगdynamic_cast<componnet*>(entity)->update()
अलग करना चाहिए

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

जबकि मैं मानता हूं कि इसे इस तरह लागू करना संभव है, मुझे नहीं लगता कि यह एक अच्छा विचार है। जब तक आप एक über वर्ग जो सभी संभव घटक विरासत में मिला है आप डिजाइनरों खुद वस्तुओं की रचना नहीं कर सकते। और जब आप सिर्फ एक घटक पर अपडेट कॉल कर सकते हैं, तो इसमें अच्छा इन-मेमोरी लेआउट नहीं होगा, एक तैयार किए गए मॉडल में एक ही प्रकार के सभी घटक उदाहरणों को मेमोरी में बंद रखा जा सकता है और बिना किसी कैश मिस के इसे पुनरावृत्त किया जा सकता है। आप आरटीटीआई पर भी भरोसा कर रहे हैं, जो कुछ ऐसा है जो आमतौर पर प्रदर्शन के कारणों से खेल में बंद हो जाता है। एक अच्छा सॉर्ट किया गया ऑब्जेक्ट लेआउट ज्यादातर ठीक करता है।
शून्य
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.