मैं एकता में टाइट स्क्रिप्ट कपलिंग से कैसे बच सकता हूं?


24

कुछ समय पहले मैंने एकता के साथ काम करना शुरू कर दिया था और मैं अभी भी कसकर युग्मित स्क्रिप्ट के मुद्दे के साथ संघर्ष करता हूं। मैं इस समस्या से बचने के लिए अपना कोड कैसे बना सकता हूं?

उदाहरण के लिए:

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

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

यह मुझे आसानी से खेल की वस्तुओं में आसानी से व्यवहार जोड़ने की अनुमति देगा और हार्ड-कोडेड निर्भरता के बारे में चिंता नहीं करेगा।

एकता में समस्या

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

एकता में इंटरफेस के लिए पसंदीदा विकल्प क्या है? मैं इस समस्या को कैसे हल करूं? मुझे यह मिल गया , लेकिन यह मुझे बताता है कि मैं पहले से ही क्या जानता हूं, और यह मुझे नहीं बताता कि एकता में कैसे करना है! यह भी चर्चा करता है कि क्या किया जाना चाहिए, कैसे नहीं, यह स्क्रिप्ट के बीच तंग युग्मन के मुद्दे को संबोधित नहीं करता है।

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


1
The problem is that in Unity I can't expose interfaces in the inspectorमुझे नहीं लगता कि मैं समझता हूं कि "इंस्पेक्टर में इंटरफ़ेस को उजागर करें" से आपका क्या मतलब है क्योंकि मेरा पहला विचार "क्यों नहीं?"
jhocking

@ झटके से एकता देव से पूछें। आप बस इसे निरीक्षक में नहीं देखते हैं। यदि आप इसे सार्वजनिक विशेषता के रूप में सेट करते हैं, और अन्य सभी विशेषताएँ करते हैं, तो यह केवल वहाँ प्रकट नहीं होता है।
केएल

1
ओह्ह का अर्थ है कि आप इंटरफ़ेस को चर के प्रकार के रूप में उपयोग करना चाहते हैं, न कि उस इंटरफ़ेस के साथ किसी ऑब्जेक्ट को संदर्भित करना।
फॉक्स

1
क्या आपने मूल ईसीएस लेख पढ़ा है? cowboyprogramming.com/2007/01/05/evolve-your-heirachy SOLID डिजाइन के बारे में क्या? en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29
jzx

1
@jzx मैं SOLID डिज़ाइन को जानता हूं और उसका उपयोग करता हूं और मैं रॉबर्ट सी। मार्टिंस की पुस्तकों का बहुत बड़ा प्रशंसक हूं, लेकिन यह सवाल एकता में कैसे करना है, इस बारे में है। और नहीं, मैंने उस लेख को नहीं पढ़ा, इसे अभी पढ़ रहा हूं, धन्यवाद :)
KL

जवाबों:


14

कुछ तरीके हैं जिनसे आप तंग स्क्रिप्ट युग्मन से बचने के लिए काम कर सकते हैं। आंतरिक एकता के लिए एक SendMessage फ़ंक्शन है जो एक Monobehaviour के GameObject पर लक्षित होने पर उस संदेश को गेम ऑब्जेक्ट पर सब कुछ भेजता है। तो आपके स्वास्थ्य वस्तु में ऐसा कुछ हो सकता है:

[SerializeField]
private int _currentHealth;
public int currentHealth
{
    get { return _currentHealth;}
    protected set
    {
        _currentHealth = value;
        gameObject.SendMessage("HealthChanged", value, SendMessageOptions.DontRequireReceiver);
    }
}

[SerializeField]
private int _maxHealth;
public int maxHealth
{
    get { return _maxHealth;}
    protected set
    {
        _maxHealth = value;
        if (currentHealth > _maxHealth)
        {
            currentHealth = _maxHealth;
        }
    }
}

public void ChangeHealth(int deltaChange)
{
    currentHealth += deltaChange;
}

यह वास्तव में सरलीकृत है, लेकिन मुझे जो भी मिल रहा है, उसे ठीक-ठीक दिखाना चाहिए। आपके जैसे संदेश को फेंकने वाली संपत्ति एक घटना होगी। आपकी मौत की स्क्रिप्ट तब इस संदेश को इंटरसेप्ट करेगी (और अगर इसे इंटरसेप्ट करने के लिए कुछ नहीं है, तो SendMessageOptions.DontRequireReceiver आपको एक अपवाद प्राप्त नहीं होगा) और इसके सेट होने के बाद नए स्वास्थ्य मूल्य के आधार पर कार्रवाई करने की गारंटी देगा। इस तरह:

void HealthChanged(int newHealth)
{
    if (newHealth <= 0)
    {
        Die();
    }
}

void Die ()
{
    Debug.Log ("I am undone!");
    DestroyObject (gameObject);
}

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

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

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


5
SendMessage () इस स्थिति को संबोधित करता है और मैं कई स्थितियों में उस आदेश का उपयोग करता हूं, लेकिन इस तरह का एक असंगत संदेश प्रणाली सीधे रिसीवर के संदर्भ में होने से कम कुशल है। वस्तुओं के बीच सभी नियमित संचार के लिए आपको इस आदेश पर भरोसा करने से सावधान रहना चाहिए।
फॉक्स

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

1
मेरा यह भी मानना ​​है कि यूनिटी एसेट स्टोर में कुछ प्लगइन्स उपलब्ध हैं जो इंटरसिटी इंस्पेक्टर द्वारा समर्थित इंटरफेस और अन्य प्रोग्रामिंग कंस्ट्रक्शंस को उजागर कर सकते हैं, यह एक त्वरित खोज के लायक हो सकता है।
मैथ्यू पिगराम

7

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

http://forum.unity3d.com/threads/free-vfw-full-set-of-drawers-savesystem-serialize-interfaces-generics-auto-props-delegates.266165/

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

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


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

मैं उस ढांचे का उपयोग कर रहा था और अंततः रुक गया क्योंकि मेरे पास बहुत ही कारण था @ मेरे दिमाग में पीछे हटने का उल्लेख करना (और यह मदद नहीं करता था कि एक अपडेट से दूसरे में, यह एक संपादक सहायक से एक सिस्टम में चला गया जिसमें सब कुछ शामिल था लेकिन पीछे की अनुकूलता को तोड़ते हुए किचन सिंक [और एक उपयोगी सुविधा या दो को दूर करते हुए])
सेलाली एडोबोर

6

एकता-विशिष्ट दृष्टिकोण जो मेरे लिए होता है, शुरू GetComponents<MonoBehaviour>()में स्क्रिप्ट की एक सूची प्राप्त करने के लिए होगा , और फिर विशिष्ट इंटरफेस के लिए उन स्क्रिप्ट को निजी चर में डाल दिया जाएगा। आप प्रारंभ () पर निर्भरता इंजेक्टर स्क्रिप्ट में ऐसा करना चाहते हैं। कुछ इस तरह:

MonoBehaviour[] list = GetComponents<MonoBehaviour>();
for (int i = 0; i < list.Length; i++) {
    if (list[i] is IHealth) {
        Debug.Log("Found it!");
    }
}

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


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


मैं कई वर्गों से विरासत में नहीं मिल सकता, जबकि मैं कई इंटरफेस लागू कर सकता हूं। यह एक समस्या हो सकती है, लेकिन मुझे लगता है कि यह कुछ भी नहीं की तुलना में बहुत बेहतर है :) आपके उत्तर के लिए धन्यवाद!
केएल

जैसा कि संपादन के लिए है - एक बार जब मेरे पास स्क्रिप्ट की सूची होगी, तो मेरे कोड को कैसे पता चलेगा कि किस स्क्रिप्ट को किस इंटरफ़ेस में रखा गया है?
केएल

1
यह भी समझाने के लिए संपादित किया
झॉकिंग

1
एकता 5 में, आप उपयोग कर सकते हैं GetComponent<IMyInterface>()और GetComponents<IMyInterface>()सीधे, व्हाट उस घटक (ओं) को वापस करता है जो इसे लागू करता है।
एस। तारिक inetin

5

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

मैं आपकी चिंता को पूरी तरह से समझता हूं, खासकर क्योंकि हाल के उपकरणों के साथ प्रदर्शन चिंता कम महत्वपूर्ण है, लेकिन मेरी भावना यह है कि आपको अपने स्वयं के समाधान विकसित करने होंगे यदि आप एकता गेम के विकास के लिए औद्योगिक ओओपी सर्वोत्तम प्रथाओं को लागू करना चाहते हैं (जैसे निर्भरता इंजेक्शन, आदि...)।


2

IUnified ( http://u3d.as/content/wounded-wolf/iunified/5H1 ) पर एक नज़र डालें , जो आपको अपने उल्लेख के अनुसार, संपादक में इंटरफ़ेस को उजागर करने देता है। सामान्य चर घोषणा की तुलना में थोड़ा अधिक वायरिंग करना होता है, बस।

public interface IPinballValue{
   void Add(int value);
}

[System.Serializable]
public class IPinballValueContainer : IUnifiedContainer<IPinballValue> {}

public class MyClassThatExposesAnInterfaceProperty : MonoBehaviour {
   [SerializeField]
   IPinballValueContainer value;
}

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

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


1

स्रोत: http://www.udellgames.com/posts/ultra-useful-unity-snippet-developers-use-interfaces/

[SerializeField]
private GameObject privateGameObjectName;

public GameObject PublicGameObjectName
{
    get { return privateGameObjectName; }
    set
    {
        //Make sure changes to privateGameObjectName ripple to privateInterfaceName too
        if (privateGameObjectName != value)
        {
            privateInterfaceName = null;
            privateGameObjectName = value;
        }
    }
}

private InterfaceType privateInterfaceName;
public InterfaceType PublicInterfaceName
{
    get
    {
        if (privateInterfaceName == null)
        {
            privateInterfaceName = PublicGameObjectName.GetComponent(typeof(InterfaceType)) as InterfaceType;
        }
        return privateInterfaceName;
    }
    set
    {
        //Make sure changes to PublicInterfaceName ripple to PublicGameObjectName too
        if (privateInterfaceName != value)
        {
            privateGameObjectName = (privateInterfaceName as Component).gameObject;
            privateInterfaceName = value;
        }

    }
}

0

आपके द्वारा उल्लिखित सीमाओं के आसपास पाने के लिए मैं कुछ अलग रणनीतियों का उपयोग करता हूं।

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

public interface IRaycastStrategy 
{
    bool Cast(Ray ray, out RaycastHit raycastHit, float distance, LayerMask layerMask);
}

मैं तो विभिन्न वर्गों में इसे लागू वर्गों की तरह साथ LinecastRaycastStrategyऔर SphereRaycastStrategyऔर एक MonoBehavior लागू RaycastStrategySelectorकि प्रत्येक प्रकार का एक उदाहरण उजागर करता है। कुछ ऐसा है RaycastStrategySelectionBehavior, जिसे कुछ इस तरह से लागू किया जाता है:

public class RaycastStrategySelectionBehavior : MonoBehavior
{

    private readonly Dictionary<RaycastStrategyType, IRaycastStrategy> _raycastStrategies
        = new Dictionary<RaycastStrategyType, IRaycastStrategy>
        {
            { RaycastStrategyType.Line, new LineRaycastStrategy() },
            { RaycastStrategyType.Sphere, new SphereRaycastStrategy() }
        };

    public RaycastStrategyType StrategyType;


    public IRaycastStrategy RaycastStrategy
    {
        get
        {
            IRaycastStrategy raycastStrategy;
            if (!_raycastStrategies.TryGetValue(StrategyType, out raycastStrategy))
            {
                throw new InvalidOperationException("There is no registered implementation for the selected strategy");
            }
            return raycastStrategy;
        }
    }
}

यदि कार्यान्वयन से उनके निर्माणों में एकता कार्यों को संदर्भित करने की संभावना होती है (या बस सुरक्षित पक्ष पर होना चाहिए, तो मैं इसे भूल गया जब इस उदाहरण को टाइप करते हुए) Awakeकॉलिंग के साथ समस्याओं से बचने के लिए उपयोग करें या संपादक में या मुख्य के बाहर एकता कार्यों का संदर्भ दें। धागा

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

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

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