paint-brush
यूनिटी में UI को कैसे अनुकूलित करें: धीमी प्रदर्शन के कारण और समाधानद्वारा@sergeybegichev
688 रीडिंग
688 रीडिंग

यूनिटी में UI को कैसे अनुकूलित करें: धीमी प्रदर्शन के कारण और समाधान

द्वारा Sergei Begichev9m2024/08/25
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

इस विस्तृत गाइड का उपयोग करके यूनिटी में यूआई प्रदर्शन को अनुकूलित करने का तरीका देखें, जिसमें कई प्रयोग, व्यावहारिक सलाह और प्रदर्शन परीक्षण शामिल हैं!
featured image - यूनिटी में UI को कैसे अनुकूलित करें: धीमी प्रदर्शन के कारण और समाधान
Sergei Begichev HackerNoon profile picture

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


नमस्ते! मैं सर्गेई बेगिचेव हूँ, पिक्सोनिक (MY.GAMES) में क्लाइंट डेवलपर। इस पोस्ट में, मैं यूनिटी3डी में यूआई ऑप्टिमाइज़ेशन पर चर्चा करूँगा। बनावट के एक सेट को रेंडर करना सरल लग सकता है, लेकिन इससे महत्वपूर्ण प्रदर्शन संबंधी समस्याएँ हो सकती हैं। उदाहरण के लिए, हमारे वॉर रोबोट्स प्रोजेक्ट में, बिना अनुकूलित यूआई संस्करणों ने कुल CPU लोड का 30% तक हिस्सा लिया - एक आश्चर्यजनक आंकड़ा!


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

यूनिटी की सिफ़ारिशें

सबसे पहले, आइए समीक्षा करें यूनिटी की सिफारिशें यूआई अनुकूलन के लिए, जिसे मैंने छह प्रमुख बिंदुओं में संक्षेपित किया है:


  1. अपने कैनवस को उप-कैनवस में विभाजित करें
  2. अनावश्यक रेकास्ट लक्ष्य हटाएँ
  3. महंगे तत्वों (बड़ी सूची, ग्रिड दृश्य, आदि) का उपयोग करने से बचें।
  4. लेआउट समूहों से बचें
  5. गेम ऑब्जेक्ट के बजाय कैनवास छिपाएँ (GO)
  6. एनिमेटरों का इष्टतम उपयोग करें


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


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


यूनिटी की सिफारिशों में यह अस्पष्टता एक मुख्य मुद्दा है - अक्सर यह स्पष्ट नहीं होता कि हमें इन सुझावों पर कौन से सिद्धांत लागू करने चाहिए।

यूआई निर्माण सिद्धांत

UI प्रदर्शन को अनुकूलित करने के लिए, यह समझना आवश्यक है कि Unity UI का निर्माण कैसे करता है। Unity में प्रभावी UI अनुकूलन के लिए इन चरणों को समझना महत्वपूर्ण है। हम इस प्रक्रिया में मोटे तौर पर तीन प्रमुख चरणों की पहचान कर सकते हैं:


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


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


  3. रेंडरिंग . अंत में, यूनिटी एकत्रित बैचों को खींचती है। जितने कम बैच होंगे, रेंडरिंग प्रक्रिया उतनी ही तेज़ होगी।


यद्यपि इस प्रक्रिया में अन्य तत्व भी शामिल हैं, लेकिन अधिकांश समस्याएं इन तीन चरणों में ही आती हैं, इसलिए फिलहाल हम इन्हीं पर ध्यान केंद्रित करेंगे।


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


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


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


एक व्यावहारिक उदाहरण के रूप में, लेआउट समूहों का उपयोग करते समय यह समस्या विशेष रूप से स्पष्ट होती है। हर बार जब कोई लेआउट फिर से बनाया जाता है, तो हर LayoutElement एक GetComponent ऑपरेशन करता है, जो काफी संसाधन-गहन हो सकता है।

एकाधिक परीक्षण

आइए प्रदर्शन परिणामों की तुलना करने के लिए उदाहरणों की एक श्रृंखला की जाँच करें। (सभी परीक्षण Google Pixel 1 डिवाइस पर Unity संस्करण 2022.3.24f1 का उपयोग करके किए गए थे।)


इस परीक्षण में, हम एक एकल तत्व वाला लेआउट समूह बनाएंगे, और हम दो परिदृश्यों का विश्लेषण करेंगे: एक जहां हम तत्व का आकार बदलते हैं, और दूसरा जहां हम FillAmount गुण का उपयोग कर रहे हैं।


RectTransform में परिवर्तन:


राशि में परिवर्तन:


दूसरे उदाहरण में, हम वही काम करने की कोशिश करेंगे, लेकिन 8 तत्वों वाले लेआउट समूह में। इस मामले में, हम अभी भी केवल एक तत्व ही बदलेंगे।


RectTransform में परिवर्तन:


राशि में परिवर्तन:


यदि पिछले उदाहरण में RectTransform में परिवर्तन के परिणामस्वरूप लेआउट पर 0.2 ms का लोड हुआ, तो इस बार लोड बढ़कर 0.7 ms हो जाता है। इसी तरह, बैचिंग अपडेट से लोड 0.65 ms से बढ़कर 1.10 ms हो जाता है।


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


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


स्पष्ट रूप से, इस परिदृश्य में FillAmount का उपयोग करना अधिक कुशल विकल्प है। हालाँकि, जब हम किसी तत्व के पैमाने या स्थिति को बदलते हैं तो स्थिति अधिक जटिल हो जाती है। इन मामलों में, यूनिटी के अंतर्निहित तंत्रों को बदलना चुनौतीपूर्ण है जो लेआउट पुनर्निर्माण को ट्रिगर नहीं करते हैं।


यहीं पर SubCanvases की भूमिका आती है। आइए परिणामों की जांच करें जब हम SubCanvas के भीतर एक परिवर्तनीय तत्व को समाहित करते हैं।


हम 8 तत्वों वाला एक लेआउट समूह बनाएंगे, जिनमें से एक को SubCanvas के भीतर रखा जाएगा, और फिर उसके रूपांतरण को संशोधित करेंगे।


SubCanvas में RectTransform परिवर्तन:


जैसा कि परिणाम दर्शाते हैं, SubCanvas के भीतर एक एकल तत्व को समाहित करने से लेआउट पर लोड लगभग समाप्त हो जाता है; ऐसा इसलिए है क्योंकि SubCanvas सभी परिवर्तनों को पृथक कर देता है, जिससे पदानुक्रम के उच्च स्तरों में पुनर्निर्माण को रोका जा सकता है।


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


आइए 8 लेआउट तत्वों को SubCanvas में लपेटकर आगे बढ़ें:


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


अब, चलिए एक और प्रयोग करते हैं। सबसे पहले, हम 8 तत्वों वाला एक लेआउट समूह बनाएंगे और फिर एनिमेटर का उपयोग करके लेआउट तत्वों में से एक को संशोधित करेंगे।


एनिमेटर RectTransform को एक नए मान पर समायोजित करेगा:


यहाँ, हम दूसरे उदाहरण के समान ही परिणाम देखते हैं जहाँ हमने सब कुछ मैन्युअल रूप से बदला था। यह तर्कसंगत है क्योंकि इससे कोई फर्क नहीं पड़ता कि हम RectTransform को बदलने के लिए क्या उपयोग करते हैं।


एनिमेटर RectTransform को समान मान में परिवर्तित करता है:


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


अब, आइए देखें कि 8 तत्वों वाले लेआउट समूह में टेक्स्ट मान बदलने से क्या प्रभाव पड़ता है और क्या इससे लेआउट पुनर्निर्माण शुरू होता है:


हम देखते हैं कि पुनर्निर्माण भी शुरू हो गया है।


अब, हम 8 तत्वों के लेआउट समूह में TextMechPro का मान बदलेंगे:


TextMechPro लेआउट पुनर्निर्माण को भी सक्रिय करता है, और ऐसा भी लगता है कि यह नियमित टेक्स्ट की तुलना में बैचिंग और रेंडरिंग पर अधिक भार डालता है।


8 तत्वों के लेआउट समूह में SubCanvas में TextMechPro मान बदलना:


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


अब, आइए लेआउट समूह के भीतर गेमऑब्जेक्ट (GO) को चालू और बंद करते समय लगने वाले लोड का मूल्यांकन करें।


8 तत्वों के लेआउट समूह के अंदर गेमऑब्जेक्ट को चालू और बंद करना:


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


8 तत्वों के लेआउट समूह के साथ SubCanvas के अंदर GO चालू करना:


इस मामले में, सबकैनवास लोड को कम करने में भी मदद करता है।


अब, आइए देखें कि यदि हम लेआउट समूह के साथ संपूर्ण GO को चालू या बंद करते हैं तो लोड क्या होता है:


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


तो, अगर हमें अत्यधिक लोड पैदा किए बिना संपूर्ण UI तत्वों को सक्षम या अक्षम करने की आवश्यकता है, तो हम क्या कर सकते हैं? GO को सक्षम और अक्षम करने के बजाय, आप केवल कैनवास या कैनवास समूह घटक को अक्षम कर सकते हैं। इसके अतिरिक्त, कैनवास समूह के अल्फा चैनल को 0 पर सेट करने से प्रदर्शन संबंधी समस्याओं से बचते हुए समान प्रभाव प्राप्त किया जा सकता है।



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


आगे, आइए लेआउट समूह के भीतर SiblingIndex को बदलने के प्रभाव की जांच करें।


8 तत्वों के लेआउट समूह के अंदर SiblingIndex बदलना:


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


अब, आइए एक अलग दृष्टिकोण के साथ प्रयोग करें। SiblingIndex को बदलने के बजाय, हम लेआउट समूह के भीतर दो तत्वों की बनावट को बदल देंगे।


8 तत्वों के लेआउट समूह में दो तत्वों की बनावट की अदला-बदली:


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


अब, चलिए एक कस्टम लेआउट समूह बनाते हैं। हम 8 तत्व बनाएंगे और उनमें से दो की स्थिति को आसानी से बदल देंगे।


8 तत्वों वाला कस्टम लेआउट समूह:


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


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

निष्कर्ष

लेआउट को फिर से बनाना एक बड़ी चुनौती है। इस समस्या को हल करने के लिए, हमें इसके मूल कारणों की पहचान करनी होगी, जो अलग-अलग हो सकते हैं। लेआउट को फिर से बनाने के लिए प्राथमिक कारक यहां दिए गए हैं:


  1. तत्वों का एनीमेशन: गति, पैमाना, घूर्णन (रूपांतरण में कोई भी परिवर्तन)
  2. स्प्राइट्स को प्रतिस्थापित करना
  3. पाठ का पुनर्लेखन
  4. GO को चालू या बंद करना, GO को जोड़ना/हटाना
  5. भाई-बहन सूचकांक बदलना


कुछ पहलुओं को उजागर करना महत्वपूर्ण है जो अब यूनिटी के नए संस्करणों में समस्या उत्पन्न नहीं करते हैं, लेकिन जो पहले वाले संस्करणों में करते थे: एक ही पाठ को ओवरराइट करना और एनिमेटर के साथ एक ही मान को बार-बार सेट करना।


अब जबकि हमने उन कारकों की पहचान कर ली है जो लेआउट पुनर्निर्माण को ट्रिगर करते हैं, तो आइए हमारे समाधान विकल्पों को संक्षेप में प्रस्तुत करें:


  1. एक गेमऑब्जेक्ट (GO) लपेटें जो SubCanvas में पुनर्निर्माण को ट्रिगर करता है। यह दृष्टिकोण परिवर्तनों को अलग करता है, उन्हें पदानुक्रम में अन्य तत्वों को प्रभावित करने से रोकता है। हालाँकि, सावधान रहें - बहुत सारे SubCanvas बैचिंग पर लोड को काफी बढ़ा सकते हैं।


  2. GO के बजाय SubCanvas या Canvas Group को चालू और बंद करें। नए GO बनाने के बजाय ऑब्जेक्ट पूल का उपयोग करें। यह विधि मेमोरी में लेआउट को सुरक्षित रखती है, जिससे पुनर्निर्माण की आवश्यकता के बिना तत्वों को त्वरित रूप से सक्रिय किया जा सकता है।


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


  4. यूनिटी के लेआउट समूह को कस्टम लेआउट समूह से बदलें। यूनिटी के लेआउट समूहों के साथ एक मुख्य समस्या यह है कि प्रत्येक लेआउट एलीमेंट पुनर्निर्माण के दौरान GetComponent को कॉल करता है, जो संसाधन-गहन है। कस्टम लेआउट समूह बनाना इस समस्या को हल कर सकता है, लेकिन इसकी अपनी चुनौतियाँ हैं। कस्टम घटकों की विशिष्ट परिचालन आवश्यकताएँ हो सकती हैं जिन्हें प्रभावी उपयोग के लिए आपको समझना होगा। फिर भी, यह दृष्टिकोण अधिक कुशल हो सकता है, खासकर सरल लेआउट समूह परिदृश्यों के लिए।