व्यावहारिक रूप से घटक आधारित इकाई प्रणाली का उपयोग करना


59

कल, मैंने अटार्नी / व्यवहार इकाई प्रणाली के बारे में जीडीसी कनाडा से एक प्रस्तुति पढ़ी है और मुझे लगता है कि यह बहुत अच्छा है। हालांकि, मुझे यकीन नहीं है कि इसे केवल व्यावहारिक रूप से कैसे उपयोग किया जाए। सबसे पहले, मैं आपको जल्दी से समझाऊंगा कि यह प्रणाली कैसे काम करती है।


प्रत्येक खेल इकाई (गेम ऑब्जेक्ट) विशेषताओं (= डेटा, जो व्यवहारों द्वारा पहुँचा जा सकता है, लेकिन 'बाहरी कोड') और व्यवहारों (= तर्क, जिसमें सम्‍मिलित है OnUpdate()और OnMessage()) से बना है। तो, उदाहरण के लिए, एक ब्रेकआउट क्लोन में, प्रत्येक ईंट से बना होता जा (उदाहरण!): PositionAttribute , ColorAttribute , HealthAttribute , RenderableBehaviour , HitBehaviour । अंतिम व्यक्ति इस तरह दिख सकता है (यह C # में लिखा गया एक गैर-काम करने वाला उदाहरण है):

void OnMessage(Message m)
{
    if (m is CollisionMessage) // CollisionMessage is inherited from Message
    {
        Entity otherEntity = m.CollidedWith; // Entity CollisionMessage.CollidedWith
        if (otherEntity.Type = EntityType.Ball) // Collided with ball
        {
            int brickHealth = GetAttribute<int>(Attribute.Health); // owner's attribute
            brickHealth -= otherEntity.GetAttribute<int>(Attribute.DamageImpact);
            SetAttribute<int>(Attribute.Health, brickHealth); // owner's attribute

            // If health is <= 0, "destroy" the brick
            if (brickHealth <= 0)
                SetAttribute<bool>(Attribute.Alive, false);
        }
    }
    else if (m is AttributeChangedMessage) // Some attribute has been changed 'externally'
    {
        if (m.Attribute == Attribute.Health)
        {
            // If health is <= 0, "destroy" the brick
            if (brickHealth <= 0)
                SetAttribute<bool>(Attribute.Alive, false);
        }
    }
}

यदि आप इस प्रणाली में रुचि रखते हैं, तो आप यहाँ (.ppt) अधिक पढ़ सकते हैं ।


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

तो, मैं क्या पूछना चाहता हूँ? व्यवहार (घटकों) को कैसे डिज़ाइन करें। मैंने यहाँ GameDev SE पर पढ़ा है, कि सबसे आम गलती कई घटक बनाना है और बस, "सब कुछ एक घटक बनाना है"। मैंने पढ़ा है कि यह एक घटक में रेंडरिंग नहीं करने का सुझाव दिया गया है, लेकिन इसे इसके बाहर करो (इसलिए RenderableBehaviour के बजाय , यह शायद RenderableAttribute हो सकता है , और यदि किसी इकाई ने RenderableAttribute को सही पर सेट किया है, तो Renderer(वर्ग से संबंधित नहीं) घटकों, लेकिन इंजन को ही) इसे स्क्रीन पर खींचना चाहिए?)।

लेकिन, व्यवहार / घटकों के बारे में कैसे? मान लीजिए कि मुझे एक स्तर मिला है, और स्तर में, ए Entity button, Entity doorsऔर है Entity player। जब खिलाड़ी बटन से टकराता है (यह एक फ्लोर बटन है, जिसे दबाव से टॉगल किया जाता है), इसे दबाया जाता है। जब बटन दबाया जाता है, तो यह दरवाजे खोलता है। खैर, अब यह कैसे करना है?

मैं कुछ इस तरह से आया हूं: खिलाड़ी को CollisionBehaviour मिला है , जो यह जांचता है कि खिलाड़ी किसी चीज से टकराता है या नहीं। यदि वह एक बटन से टकराता है, तो यह इकाई CollisionMessageको भेजता है button। संदेश में सभी आवश्यक informations होंगे: जो बटन से टकरा गया। बटन को ToggleableBehaviour मिला है , जो प्राप्त करेगा CollisionMessage। यह जाँच करेगा कि यह किसके साथ टकराया था और यदि उस इकाई का वजन बटन को टॉगल करने के लिए काफी बड़ा है, तो बटन टॉगल हो जाता है। अब, यह बटन के ToggledAttribute को सही पर सेट करता है। ठीक है, लेकिन अब क्या?

क्या बटन को अन्य सभी वस्तुओं के लिए एक और संदेश भेजना चाहिए ताकि उन्हें बताया जा सके कि यह टॉगल किया गया है? मुझे लगता है कि अगर मैंने ऐसा सब कुछ किया, तो मेरे पास हजारों संदेश आएंगे और यह बहुत ही गन्दा होगा। तो शायद यह बेहतर है: दरवाजे लगातार जांचते हैं कि क्या बटन जो उनसे जुड़ा हुआ है, दबाया गया है या नहीं, और उसके अनुसार अपने ओपनएडर्न्स को बदलें । लेकिन फिर इसका मतलब है कि दरवाजे की OnUpdate()विधि लगातार कुछ कर रही होगी (क्या यह वास्तव में एक समस्या है?)।

और दूसरी समस्या: क्या होगा अगर मेरे पास अधिक प्रकार के बटन हैं। एक को दबाव के द्वारा दबाया जाता है, दूसरे को गोली मारकर उतारा जाता है, तीसरे को टॉगल किया जाता है अगर उस पर पानी डाला जाता है, आदि इसका मतलब है कि मुझे अलग-अलग व्यवहार करना होगा, कुछ इस तरह से:

Behaviour -> ToggleableBehaviour -> ToggleOnPressureBehaviour
                                 -> ToggleOnShotBehaviour
                                 -> ToggleOnWaterBehaviour

क्या यह असली खेल है या मैं सिर्फ बेवकूफ हूं? शायद मैं सिर्फ एक हो सकता था ToggleableBehaviour और यह के अनुसार व्यवहार करेंगे ButtonTypeAttribute । तो अगर यह एक है ButtonType.Pressure, यह यह करता है, अगर यह एक है ButtonType.Shot, यह कुछ और करता है ...

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

जवाबों:


46

घटक महान हैं, लेकिन ऐसा समाधान खोजने में कुछ समय लग सकता है जो आपको अच्छा लगता है। चिंता न करें, आप वहां पहुंचेंगे। :)

घटकों का आयोजन

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

यदि आपके पास उनके बीच संस्थाओं को जोड़ने के लिए एक तंत्र है, तो मेरे पास स्विच है सीधे उस दरवाजे को सूचित करें जिसे यह दबाया गया है, फिर दरवाजा तय कर सकता है कि क्या करना है।

यदि आप संस्थाओं को कनेक्ट नहीं कर सकते हैं, तो आपका समाधान मैं क्या करूँगा के करीब है। मैं दरवाजा एक सामान्य घटना ( SwitchActivatedEventशायद,) के लिए सुनना होगा । जब स्विच सक्रिय हो जाते हैं, तो वे इस घटना को पोस्ट करते हैं।

यदि आपके पास एक से अधिक प्रकार के स्विच हैं, तो मेरे पास होगा PressureToggle, WaterToggleऔर एक ShotToggleव्यवहार भी होगा, लेकिन मुझे यकीन नहीं है कि आधार ToggleableBehaviourकोई भी अच्छा है, इसलिए मैं इसके साथ दूर करूँगा (जब तक, निश्चित रूप से, आपके पास एक अच्छा है इसे रखने का कारण)।

Behaviour -> ToggleOnPressureBehaviour
          -> ToggleOnShotBehaviour
          -> ToggleOnWaterBehaviour

कुशल घटना से निपटने

चिंता के रूप में वहाँ बहुत सारी घटनाओं के आसपास उड़ रहा है, वहाँ एक बात आप कर सकते है। हर घटक को घटित होने वाली हर एक घटना के बारे में सूचित करने के बजाय, घटक की जाँच करें कि क्या यह सही प्रकार की घटना है, यहाँ एक अलग जानकारी है ...

आपके पास एक EventDispatcherऐसी subscribeविधि हो सकती है जो कुछ इस तरह दिखती है (स्यूडोकोड):

EventDispatcher.subscribe(event_type, function)

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

इस तरह, सिस्टम काफी अधिक कुशल है: प्रति घटना के लिए बहुत कम फ़ंक्शन कॉल है, और घटकों को यह सुनिश्चित किया जा सकता है कि उन्हें सही प्रकार की घटना प्राप्त हुई है और दोहरी जांच नहीं करनी है।

मैंने कुछ समय पहले StackOverflow पर इसका एक सरल कार्यान्वयन पोस्ट किया है। यह पायथन में लिखा गया है, लेकिन शायद यह अभी भी आपकी मदद कर सकता है:
ack https://stackoverflow.com/a/7294148/627005

यह कार्यान्वयन काफी सामान्य है: यह किसी भी प्रकार के फ़ंक्शन के साथ काम करता है, न कि केवल घटकों से कार्य करता है। यदि आपको इसकी आवश्यकता नहीं है, तो इसके बजाय function, behaviorआपके subscribeपद्धति में एक पैरामीटर हो सकता है - व्यवहार उदाहरण जिसे अधिसूचित करने की आवश्यकता है।

गुण और व्यवहार

मैं सादे पुराने घटकों के बजाय विशेषताओं और व्यवहारों का उपयोग करने के लिए आया हूं । हालाँकि, आपके विवरण से आप ब्रेकआउट गेम में सिस्टम का उपयोग कैसे करेंगे, मुझे लगता है कि आप इसे ओवरडोज़ कर रहे हैं।

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

  • गुण किसी अन्य घटक (न तो अन्य विशेषताओं, और न ही व्यवहार) का उपयोग नहीं करते हैं, वे स्वयं पर्याप्त हैं।

  • व्यवहार अन्य व्यवहारों का उपयोग या पता नहीं करता है। वे केवल कुछ विशेषताओं के बारे में जानते हैं (वे जिन्हें उन्हें सख्त आवश्यकता है)।

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


@ हीशे की टिप्पणी

क्या यह समस्या सामान्य घटकों के साथ भी नहीं होगी?

वैसे भी, मुझे ईवेंट प्रकारों की जांच करने की आवश्यकता नहीं है क्योंकि हर फ़ंक्शन को सही प्रकार का ईवेंट प्राप्त करना निश्चित है, हमेशा

साथ ही, व्यवहार की निर्भरता (अर्थात, जिन विशेषताओं की उन्हें आवश्यकता होती है) निर्माण पर हल हो जाती हैं, इसलिए आपको हर अपडेट पर विशेषताओं की तलाश करने की आवश्यकता नहीं है।

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

class SomeBehavior
{
  public:
    SomeBehavior(std::map<std::string, Attribute*> attribs, EventDispatcher* events)
        // For the purposes of this example, I'll assume that the attributes I
        // receive are the right ones. 
        : health_(static_cast<HealthAttribute*>(attribs["health"])),
          armor_(static_cast<ArmorAttribute*>(attribs["armor"]))
    {
        // Boost's polymorphic_downcast would probably be more secure than
        // a static_cast here, but nonetheless...
        // Also, I'd probably use some smart pointers instead of plain
        // old C pointers for the attributes.

        // This is how I'd subscribe a function to a certain type of event.
        // The dispatcher returns a `Subscription` object; the subscription 
        // is alive for as long this object is alive.
        subscription_ = events->subscribe(event::type<DamageEvent>(),
            std::bind(&SomeBehavior::onDamageEvent, this, _1));
    }

    void onDamageEvent(std::shared_ptr<Event> e)
    {
        DamageEvent* damage = boost::polymorphic_downcast<DamageEvent*>(e.get());
        // Simplistic and incorrect formula: health = health - damage + armor
        health_->value(health_->value() - damage->amount() + armor_->protection());
    }

    void update(boost::chrono::duration timePassed)
    {
        // Behaviors also have an `update` function, just like
        // traditional components.
    }

  private:
    HealthAttribute* health_;
    ArmorAttribute* armor_;
    EventDispatcher::Subscription subscription_;
};

व्यवहार के विपरीत, विशेषताओं का कोई updateफ़ंक्शन नहीं है - उन्हें ज़रूरत नहीं है, उनका उद्देश्य डेटा पकड़ना है, न कि जटिल गेम लॉजिक प्रदर्शन करना।

आप अभी भी अपनी विशेषताओं को कुछ सरल तर्क दे सकते हैं। इस उदाहरण में, HealthAttributeयह सुनिश्चित कर सकता है कि 0 <= value <= max_healthयह हमेशा सच हो। HealthCriticalEvent25 प्रतिशत होने पर यह उसी इकाई के अन्य घटकों को भी भेज सकता है , जब यह नीचे गिरता है, कहते हैं, लेकिन यह तर्क को अधिक जटिल नहीं बना सकता है।


एक विशेषता वर्ग का उदाहरण:

class HealthAttribute : public EntityAttribute
{
  public:
    HealthAttribute(Entity* entity, double max, double critical)
        : max_(max), critical_(critical), current_(max)
    { }

    double value() const {
        return current_;
    }    

    void value(double val)
    {
        // Ensure that 0 <= current <= max 
        if (0 <= val && val <= max_)
            current_ = val;

        // Notify other components belonging to this entity that
        // health is too low.
        if (current_ <= critical_) {
            auto ev = std::shared_ptr<Event>(new HealthCriticalEvent())
            entity_->events().post(ev)
        }
    }

  private:
    double current_, max_, critical_;
};

धन्यवाद! यह ठीक वैसा ही है जैसा मैं चाहता था। मुझे ईवेंटडिस्पैचर का आपका विचार भी पसंद है, जो सभी संस्थाओं के लिए सरल संदेश से बेहतर है। अब, आखिरी बात जो आपने मुझे बताई थी: आप मूल रूप से कहते हैं कि इस उदाहरण में हेल्थ और डैमेजइम्पैक्ट को विशेषता नहीं होना चाहिए। इसलिए, विशेषताओं के बजाय, वे व्यवहार के सिर्फ निजी चर होंगे? इसका मतलब है, कि "डैमेजइम्पैक्ट" इवेंट के माध्यम से पास हो जाएगा? उदाहरण के लिए EventArgs.DamageImpact? यह अच्छा लगता है ... लेकिन अगर मैं चाहता था कि ईंट उसके स्वास्थ्य के अनुसार रंग बदलती है, तो स्वास्थ्य को एक विशेषता होना चाहिए, है ना? धन्यवाद!
टॉमनटन

2
@TomsonTom हाँ, यह बात है। घटनाओं को रखने के लिए श्रोताओं को जो भी डेटा जानना आवश्यक है वह एक बहुत अच्छा समाधान है।
पॉल मंटा

3
यह एक महान जवाब है! (जैसा कि आपका पीडीएफ है) - जब आपके पास मौका होता है, तो क्या आप इस प्रणाली के साथ प्रतिपादन को संभालने के बारे में थोड़ा विस्तार से बता सकते हैं ? यह विशेषता / व्यवहार मॉडल मेरे लिए पूरी तरह से नया है, लेकिन बहुत पेचीदा है।
माइकल

1
@TomsonTom रेंडरिंग के बारे में, मैंने माइकल को दिया जवाब देखें। टकराव के लिए, मैंने व्यक्तिगत रूप से एक शॉर्टकट लिया। मैंने Box2D नामक एक पुस्तकालय का उपयोग किया जो कि उपयोग करने के लिए बहुत आसान है और टक्करों को जितना मैं कर सकता था उससे बेहतर संभालता हूं। लेकिन मैं अपने गेम लॉजिक कोड में सीधे लाइब्रेरी का उपयोग नहीं करता। प्रत्येक Entityमें एक है EntityBody, जो सभी बदसूरत बिट्स को दूर करता है। व्यवहार तब से स्थिति को पढ़ सकता है EntityBody, उस पर बल लागू कर सकता है, जोड़ों का उपयोग कर सकता है और शरीर को प्रभावित कर सकता है, इत्यादि जैसे उच्च निष्ठा भौतिकी सिमुलेशन में बॉक्स 2 डी जैसी चीजें निश्चित रूप से नई चुनौतियां लाती हैं, लेकिन वे काफी मजेदार हैं, इमो।
पॉल मंटा

1
@tilinuxlich तो आप आर्टेमिस के डेवलपर हैं! : D मैंने बोर्ड पर कुछ समय के लिए संदर्भित Component/ Systemस्कीम देखी है । हमारे कार्यान्वयन वास्तव में काफी समानताएं हैं।
पॉल मंटा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.