सभी को नमस्कार, मेरा नाम दमित्री इवाशचेंको है, और मैं MY.GAMES में एक सॉफ्टवेयर इंजीनियर हूं। इस लेख में, हम राज्यों और तत्वों के मार्कअप के आधार पर एकता में एक यूजर इंटरफेस विकसित करने के बारे में बात करेंगे।
सबसे पहले, यह ध्यान दिया जाना चाहिए कि हम यूनिटी यूआई (यूजीयूआई) तकनीक के संदर्भ में बात करेंगे, जो अभी भी प्रलेखन के अनुसार रनटाइम के लिए अनुशंसित है। वर्णित दृष्टिकोण UI टूलकिट , IMGUI , या अन्य UI बिल्डिंग सिस्टम पर लागू नहीं है।
अक्सर एकता परियोजनाओं में, आप MonoBehaviour
से विरासत में मिली व्यू क्लासेस पर बने यूआई कार्यान्वयन में आएंगे और बड़ी संख्या में SerializeField
फ़ील्ड्स के साथ आच्छादित होंगे। यह दृष्टिकोण UI के व्यवहार पर पूर्ण नियंत्रण प्रदान करता है, लेकिन यह दृश्य और प्रस्तुतकर्ता स्तरों पर बड़ी मात्रा में कोड लिखना भी आवश्यक बनाता है (उपयोग की गई वास्तुकला के आधार पर)।
अक्सर, जैसे-जैसे परियोजना का विकास जारी रहता है, ये वर्ग अविश्वसनीय आकार में बढ़ जाते हैं, और GameObject के घटक स्वयं आंतरिक वस्तुओं के लिंक की एक बड़ी संख्या से आच्छादित होते हैं:
इस तरह के घटकों को संशोधित करना भी आनंददायक नहीं है: कक्षा में किसी नए तत्व का संदर्भ प्राप्त करने के लिए, आपको SerializeField
जोड़ना होगा, कोड को दोबारा संकलित करना होगा, प्रीफैब घटक में नया फ़ील्ड ढूंढना होगा, और आवश्यक वस्तु को इसमें खींचना होगा। जैसे-जैसे परियोजना बढ़ती है, संकलन समय, फ़ील्ड की संख्या और प्रीफ़ैब के आयोजन की जटिलता भी बारी-बारी से बढ़ती जाती है।
नतीजतन, हम MonoBehaviour
(या आपकी वरीयता के आधार पर बड़ी संख्या में छोटे) के भारी और अतिभारित उपवर्गों के साथ समाप्त होते हैं।
यह भी विचार करने योग्य है कि ऐसे यूआई के व्यवहार में कोई भी बदलाव प्रोग्रामर के लिए एक कार्य है, और यह कार्य सभी संबद्ध लागतों के साथ आता है: कोड समीक्षा, मर्ज संघर्षों को हल करना, परीक्षणों के साथ कोड कवरेज, और इसी तरह।
SerializeField
की आवश्यकता होती है, और फिर इसे आवश्यकताओं के अनुसार काम करने के लिए बड़ी मात्रा में कोड लिखा जाता है . स्वाभाविक रूप से, केवल एक प्रोग्रामर ही इसे संभाल सकता है, और कार्यान्वयन लंबा, महंगा और सुपर-कुशल हो जाता है (अक्सर किसी की तुलना में कहीं अधिक कुशल)।
अब जब हमने यूजीयूआई के साथ काम करने की कुछ कठिनाइयों पर प्रकाश डाला है, तो मैं इस समस्या को हल करने के लिए एक अलग दृष्टिकोण के बारे में बात करना चाहूंगा।
अपने एक पालतू-परियोजना पर काम के दौरान, मैंने एकता में संरचित यूआई विकास के लिए एक पुस्तकालय विकसित किया। बाद में, मैंने और मेरी टीम ने उत्पादन पर इसका परीक्षण किया और हम परिणामों से प्रसन्न थे।
पुस्तकालय के लिए स्रोत कोड GitHub पर डाउनलोड के लिए उपलब्ध है ।
लाइब्रेरी का मुख्य तत्व StatefulComponent
घटक है। यह घटक प्रत्येक स्क्रीन के रूट GameObject पर रखा गया है और इसमें आंतरिक तत्वों के सभी आवश्यक संदर्भ शामिल हैं, जो टैब में वितरित हैं:
प्रत्येक लिंक को उसकी भूमिका के आधार पर नामित किया गया है। एक कोड के नजरिए से, भूमिकाओं का सेट एक नियमित enum
है। प्रत्येक प्रकार के UI तत्व (बटन, चित्र, टेक्स्ट, आदि) के लिए अलग-अलग भूमिकाएँ तैयार की जाती हैं:
public enum ButtonRole { ... } public enum ImageRole { ... } public enum TextRole { ... } ...
भूमिकाएँ सीधे घटक से उत्पन्न होती हैं, और मैन्युअल रूप से enum
संपादित करने की आवश्यकता नहीं होती है। भूमिका बनाते समय पुनर्संकलन की प्रतीक्षा करना भी आवश्यक नहीं है, क्योंकि इन enum
तत्वों का उपयोग निर्माण के तुरंत बाद किया जा सकता है।
मर्ज विरोधों को सरल बनाने के लिए, तत्वों के नामों के आधार पर गणना मूल्यों की गणना की जाती है:
[StatefulUI.Runtime.RoleAttributes.ButtonRoleAttribute] public enum ButtonRole { Unknown = 0, Start = -1436209294, // -1436209294 == "Start".GetHashCode() Settings = 681682073, Close = -1261564850, Quests = 1375430261, }
यह आपको प्रीफ़ैब में क्रमबद्ध मूल्यों को तोड़ने से बचने की अनुमति देता है यदि आप और आपके सहयोगी एक साथ विभिन्न शाखाओं में बटनों के लिए नई भूमिकाएँ बनाते हैं।
प्रत्येक प्रकार का UI तत्व (बटन, टेक्स्ट, चित्र) अपने स्वयं के टैब पर स्थित होता है:
भूमिकाओं का उपयोग करके, प्रीफ़ैब के अंदर सभी तत्वों का पूर्ण मार्कअप प्राप्त किया जाता है। छवियों और ग्रंथों तक पहुँचने के लिए SerializeField
के सेट की आवश्यकता नहीं है, और StatefulComponent
के लिए एक संदर्भ होना और वांछित छवि की भूमिका जानना पर्याप्त है, उदाहरण के लिए, इसके स्प्राइट को बदलने के लिए।
UnityEngine.UI.Text
और TextMeshProUGUI
सहित टेक्स्टUnityEngine.UI.InputField
और TMP_InputField
सहित टेक्स्ट इनपुट
एनोटेटेड ऑब्जेक्ट्स के साथ काम करने के लिए संबंधित तरीके हैं। कोड में, आप StatefulComponent
के संदर्भ का उपयोग कर सकते हैं या StatefulView
से वर्ग को इनहेरिट कर सकते हैं:
public class ExamplePresenter { private StatefulComponent _view; public void OnOpen() { _view.GetButton(ButtonRole.Settings).onClick.AddListener(OnSettingsClicked); _view.GetButton(ButtonRole.Close).onClick.AddListener(OnCloseClicked); _view.GetSlider(SliderRole.Volume).onValueChanged.AddListener(OnVolumeChanged); } } public class ExampleScreen : StatefulView { private void Start() { SetText(TextRole.Title, "Hello World"); SetTextValues(TextRole.Timer, hours, minutes, seconds); SetImage(ImageRole.UserAvatar, avatarSprite); } }
टेक्स्ट वाले टैब में, ऑब्जेक्ट की भूमिका और लिंक के अलावा, निम्नलिखित कॉलम होते हैं:
लाइब्रेरी में अनुवाद के साथ काम करने के लिए बिल्ट-इन सबसिस्टम शामिल नहीं है। अपनी स्थानीयकरण प्रणाली को जोड़ने के लिए, आपको ILocalizationProvider
इंटरफ़ेस का कार्यान्वयन बनाना होगा। इसका निर्माण, उदाहरण के लिए, आपके बैक-एंड, स्क्रिप्टेबलऑब्जेक्ट्स या Google शीट्स के आधार पर किया जा सकता है।
public class HardcodeLocalizationProvider : ILocalizationProvider { private Dictionary<string, string> _dictionary = new Dictionary<string, string> { { "timer" , "{0}h {1}m {2}s" }, { "title" , "Título do Jogo" }, { "description" , "Descrição longa do jogo" }, }; public string GetPhrase(string key, string defaultValue) { return _dictionary.TryGetValue(key, out var value) ? value : defaultValue; } }
कॉपी स्थानीयकरण बटन पर क्लिक करके, कोड और वैल्यू कॉलम की सामग्री को क्लिपबोर्ड पर Google पत्रक में पेस्ट करने के लिए उपयुक्त प्रारूप में कॉपी किया जाएगा।
अक्सर, पुन: उपयोग की सुविधा के लिए, UI के अलग-अलग हिस्सों को अलग-अलग प्रीफ़ैब में निकाला जाता है। StatefulComponent
हमें घटकों का एक पदानुक्रम बनाने की भी अनुमति देता है, जहां प्रत्येक घटक केवल अपने स्वयं के बाल इंटरफ़ेस तत्वों के साथ काम करता है।
आंतरिक कम्पास टैब पर, आप आंतरिक घटकों को भूमिकाएँ सौंप सकते हैं:
अन्य प्रकार के तत्वों के समान कोड में कॉन्फ़िगर की गई भूमिकाओं का उपयोग किया जा सकता है:
var header = GetInnerComponent(InnerComponentRole.Header); header.GetButton(ButtonRole.Close).onClick.AddListener(OnCloseClicked); header.SetText(TextRole.Title, "Header Title"); var footer = GetInnerComponent(InnerComponentRole.Footer); footer.GetButton(ButtonRole.Continue).onClick.AddListener(OnContinueClicked); footer.SetText(TextRole.Message, "Footer Message");
समान तत्वों की सूची बनाने के लिए, आप ContainerView
घटक का उपयोग कर सकते हैं। आपको तात्कालिकता और रूट ऑब्जेक्ट (वैकल्पिक) के लिए प्रीफ़ैब निर्दिष्ट करने की आवश्यकता है। संपादन-मोड में, आप StatefulComponent
का उपयोग करके तत्वों को जोड़ या हटा सकते हैं:
तात्कालिक प्रीफ़ैब्स की सामग्री को चिह्नित करने के लिए StatefulComponent
का उपयोग करना सुविधाजनक है। रनटाइम में, आप कंटेनर को पॉप्युलेट करने के लिए AddInstance<T>
, AddStatefulComponent
, या FillWithItems
विधियों का उपयोग कर सकते हैं:
var container = GetContainer(ContainerRole.Players); container.Clear(); container.FillWithItems(_player, (StatefulComponent view, PlayerData data) => { view.SetText(TextRole.Name, data.Name); view.SetText(TextRole.Level, data.Level); view.SetImage(ImageRole.Avatar, data.Avatar); });
यदि ऑब्जेक्ट बनाने के लिए मानक Object.Instantiate()
आपको सूट नहीं करता है, तो आप इस व्यवहार को ओवरराइड कर सकते हैं, उदाहरण के लिए, Zenject का उपयोग करके इन्स्टेन्शियशन के लिए:
StatefulUiManager.Instance.CustomInstantiateMethod = prefab => { return _diContainer.InstantiatePrefab(prefab); };
आंतरिक घटक और कंटेनर StatefulComponent
के लिए क्रमशः स्थिर और गतिशील नेस्टिंग प्रदान करते हैं।
हमने प्रीफ़ैब, स्थानीयकरण और तात्कालिकता के मार्कअप पर विचार किया है। अब यह सबसे दिलचस्प भाग - राज्यों के आधार पर यूआई विकसित करने के लिए आगे बढ़ने का समय है।
हम राज्य की अवधारणा को प्रीफ़ैब में परिवर्तनों के नामित सेट के रूप में मानेंगे। इस मामले में नाम StateRole
एनम से एक भूमिका है, और प्रीफ़ैब में परिवर्तन के उदाहरण हो सकते हैं:
परिवर्तन का एक सेट (राज्य विवरण) राज्य टैब पर कॉन्फ़िगर किया जा सकता है। एक कॉन्फ़िगर स्थिति सीधे निरीक्षक से लागू की जा सकती है:
SetState
विधि का उपयोग कर कोड से एक कॉन्फ़िगर स्थिति लागू की जा सकती है:
switch (colorScheme) { case ColorScheme.Orange: SetState(StateRole.Orange); break; case ColorScheme.Red: SetState(StateRole.Red); break; case ColorScheme.Purple: SetState(StateRole.Purple); break; }
टूल टैब पर, जब इनिशियल स्टेट ऑन इनेबल पैरामीटर सक्षम होता है, तो आप उस स्थिति को कॉन्फ़िगर कर सकते हैं जो ऑब्जेक्ट इंस्टेंटेशन पर तुरंत लागू हो जाएगी।
राज्यों का उपयोग दृश्य वर्ग स्तर पर आवश्यक कोड की मात्रा में महत्वपूर्ण कमी की अनुमति देता है। StatefulComponent
में परिवर्तनों के एक सेट के रूप में बस अपनी स्क्रीन की प्रत्येक स्थिति का वर्णन करें और खेल की स्थिति के आधार पर कोड से आवश्यक स्थिति लागू करें।
दरअसल, राज्यों के आधार पर यूआई विकसित करना अविश्वसनीय रूप से सुविधाजनक है। इतना अधिक कि, समय के साथ, यह एक और समस्या की ओर ले जाता है - जैसे-जैसे परियोजना विकसित होती है, एकल खिड़की के लिए राज्यों की सूची एक अनियंत्रित लंबाई तक बढ़ सकती है, और इस प्रकार नेविगेट करना मुश्किल हो जाता है। इसके अलावा, ऐसे राज्य भी हैं जो केवल कुछ अन्य राज्यों के संदर्भ में अर्थपूर्ण हैं। इस समस्या को हल करने के लिए, स्टेटफुल यूआई के पास एक और टूल है: स्टेट ट्री। आप इसे स्टेट्स टैब में स्टेट ट्री एडिटर बटन पर क्लिक करके एक्सेस कर सकते हैं।
मूल राज्य (इस उदाहरण में Reward ) हर बार बाल राज्यों को कहा जाता है:
एक कॉन्फ़िगर किए गए StatefulComponent
प्रबंधित करने से सरल और समझने योग्य कोड की न्यूनतम मात्रा कम हो जाती है जो आवश्यक डेटा के साथ घटकों को पॉप्युलेट करता है और सही समय पर स्थिति को स्विच करता है:
public void ShowIntro() { SetState(StateRole.Intro); } public void ShowReward(IReward reward) { // Update the inner view with the reward reward.UpdateView(GetInnerComponent(InnerComponentRole.Reward)); // Switch on the type of reward switch (reward) { case ICardsReward cardsReward: SetState(StateRole.Cards); break; case IMoneyReward moneyReward: SetState(StateRole.Money); break; case IEmojiReward emojiReward: SetState(StateRole.Emoji); break; } } public void ShowResults(IEnumerable<IReward> rewards) { SetState(StateRole.Results); // Fill the container with the rewards GetContainer(ContainerRole.TotalReward) .FillWithItems(rewards, (view, reward) => reward.UpdateView(view)); }
भूमिकाओं का उद्देश्य कोड में बाद में उपयोग के लिए लिंक और राज्यों को नाम देने का एक सुविधाजनक और स्पष्ट तरीका प्रदान करना है। हालाँकि, ऐसी स्थितियाँ हैं जहाँ किसी राज्य का वर्णन करने के लिए एक ऐसे नाम की आवश्यकता होती है जो बहुत लंबा हो, और यह लिंक क्या इंगित करता है, या राज्य किस व्यवहार को दर्शाता है, इस बारे में एक छोटी सी टिप्पणी छोड़ना अधिक सुविधाजनक होगा। ऐसे मामलों के लिए, StatefulComponent
में प्रत्येक लिंक और स्थिति आपको विवरण जोड़ने की अनुमति देती है:
आपने पहले ही प्रत्येक टैब पर कॉपी एपीआई और कॉपी डॉक्स बटन पर ध्यान दिया होगा - ये चयनित अनुभाग के लिए जानकारी की प्रतिलिपि बनाते हैं। उनके अलावा, टूल टैब में समान बटन हैं - ये एक ही बार में सभी अनुभागों के लिए जानकारी की प्रतिलिपि बनाते हैं। जब आप कॉपी एपीआई बटन पर क्लिक करते हैं, तो इस StatfulComponent
ऑब्जेक्ट को प्रबंधित करने के लिए उत्पन्न कोड को क्लिपबोर्ड पर कॉपी किया जाएगा। यहां हमारी पुरस्कार विंडो के लिए एक उदाहरण दिया गया है:
// Insert the name of the chest here SetText(TextRole.Title, "Lootbox"); // Button to proceed to the reward issuance phase GetButton(ButtonRole.Overlay); // Button to display information about the card GetButton(ButtonRole.Info); // Container for displaying the complete list of awarded rewards GetContainer(ContainerRole.TotalReward); // Insert the card image here SetImage(ImageRole.Avatar, null); // Animated appearance of a chest SetState(StateRole.Intro);
जब आप कॉपी डॉक्स बटन पर क्लिक करते हैं, तो इस प्रीफ़ैब के दस्तावेज़ मार्कडाउन प्रारूप में क्लिपबोर्ड पर कॉपी हो जाएंगे:
### RewardScreen Buttons: - Overlay - Button to proceed to the reward issuance phase - Info - Button to display information about the card Texts: - Title - Insert the name of the chest here Containers: - TotalReward - Container for displaying the complete list of awarded rewards Images: - Avatar - Insert the card image here States: - Intro - Animated appearance of a chest - Cards - Displaying rewards in the form of a card - Money - Displaying rewards in the form of game currency - Emoji - Displaying rewards in the form of an emoji - Results - Displaying a complete list of issued rewards
यह स्पष्ट है कि इस स्क्रीन को इतने विस्तृत निर्देशों के साथ लागू करते समय गलती करना काफी कठिन है। आप प्रोजेक्ट के नॉलेज बेस में अपने यूआई संगठन के बारे में आसानी से अप-टू-डेट जानकारी रख सकते हैं।
उसी समय, स्टेटफुल यूआई यूआई प्रीफैब के निर्माण को सौंपने की अनुमति देता है। वास्तव में, राज्य-आधारित मार्कअप प्रोग्रामर्स को पास करने से पहले प्रीफ़ैब के व्यवहार का पूरी तरह से परीक्षण करने की अनुमति देता है। इसका अर्थ है कि गेम डिज़ाइनर , तकनीकी डिज़ाइनर, या यहाँ तक कि अलग-अलग UI डेवलपर भी प्रीफ़ैब तैयार कर सकते हैं। इसके अलावा, चूंकि एक एपीआई कोड और प्रीफैब के बीच बनाया गया है, प्रीफैब को प्रोग्रामिंग और कॉन्फ़िगर करना समानांतर में किया जा सकता है! एपीआई को पहले से तैयार करना आवश्यक है। लेकिन, भले ही प्रीफ़ैब को कॉन्फ़िगर करने का कार्य प्रोग्रामर के पास रहता है, स्टेटफुल यूआई का उपयोग करने से इस काम में काफी तेजी आती है।
जैसा कि हमने देखा है, स्टेटफुल यूआई यूआई तत्व राज्यों के साथ काम करना काफी सरल करता है। SerializeFields बनाने, कोड को फिर से संकलित करने और बड़ी संख्या में व्यू क्लास फ़ील्ड्स के बीच संदर्भों की खोज करने के लिए लंबे चक्रों की आवश्यकता नहीं है। स्वयं दृश्य कक्षाओं में, वस्तुओं को चालू और बंद करने या पाठ का रंग बदलने जैसे दोहराव वाले संचालन के लिए बड़ी मात्रा में कोड लिखना अब आवश्यक नहीं है।
पुस्तकालय एक परियोजना में लेआउट को व्यवस्थित करने, प्रीफ़ैब के भीतर वस्तुओं को चिह्नित करने, राज्यों को बनाने, उन्हें यूआई तत्वों से जोड़ने और यूआई प्रबंधन के लिए एक एपीआई और प्रलेखन प्रदान करने के लिए एक सुसंगत दृष्टिकोण की अनुमति देता है। यह यूआई प्रीफैब के निर्माण को सौंपने और उनके साथ काम को गति देने की भी अनुमति देता है।
राज्यों की क्षमताओं का विस्तार करना, विवरण में नए प्रकार के UI परिवर्तनों का समर्थन करना, जैसे नए प्रकार के एनिमेशन, राज्यों में ध्वनि बजाना, और इसी तरह
पाठ और छवियों को रंगने के लिए रंग पट्टियों के लिए समर्थन जोड़ना
GameObjects पुन: उपयोग के साथ आइटम की सूची के लिए समर्थन जोड़ना
बड़ी संख्या में एकता UI तत्वों का समर्थन करना
स्थानीयकरण के लिए जोड़े गए टेक्स्ट को अनलोड करना स्वचालित करना
टेस्ट फ्रेमवर्क को लागू करना। चूंकि हमारे पास हमारे प्रीफ़ैब का संपूर्ण मार्कअप है, इसलिए हम निम्न प्रारूप में आसानी से सेट-अप स्क्रिप्ट योग्य ऑब्जेक्ट-आधारित परिदृश्य बना सकते हैं:
ButtonRole.Settings
बटन पर क्लिक करें
TextRole.SomeText
में टेक्स्ट की जांच करें। "कुछ मूल्य" के बराबर होने के लिए कुछ टेक्स्ट
यह सुनिश्चित करने के लिए ImageRole.SomeImage
में छवि की जांच करें कि यह एक निश्चित स्प्राइट के बराबर है
एक ट्यूटोरियल प्रणाली। परीक्षण के समान, चिह्नित लेआउट ScriptableObject-आधारित ट्यूटोरियल परिदृश्यों को " ButtonRole.UpgradeHero
बटन पर पॉइंटर दिखाएँ" जैसे निर्देशों के रूप में बनाने की अनुमति देता है।
प्रोजेक्ट सोर्स कोड GitHub पर उपलब्ध है । मुद्दों को बनाने या पुस्तकालय में योगदान करने के लिए आपका स्वागत है!