यूनिटी डॉट्स डेवलपर्स को आधुनिक प्रोसेसर की पूरी क्षमता का उपयोग करने और अत्यधिक अनुकूलित, कुशल गेम वितरित करने की अनुमति देता है - और हमें लगता है कि इस पर ध्यान देना उचित है।
यूनिटी द्वारा पहली बार अपने डेटा-ओरिएंटेड टेक्नोलॉजी स्टैक (डॉट्स) के विकास की घोषणा किए हुए पांच साल से अधिक समय हो गया है। अब, दीर्घकालिक समर्थन (एलटीएस) संस्करण, यूनिटी 2023.3.0f1 की रिलीज के साथ, हमने आखिरकार एक आधिकारिक रिलीज देखी है। लेकिन गेम डेवलपमेंट उद्योग के लिए यूनिटी डॉट्स इतना महत्वपूर्ण क्यों है, और यह तकनीक क्या लाभ प्रदान करती है?
सभी को नमस्कार! मेरा नाम डेनिस कोंड्रेटेव है, और मैं MY.GAMES में यूनिटी डेवलपर हूं। यदि आप यह समझने के लिए उत्सुक हैं कि यूनिटी डॉट्स क्या है और क्या यह खोज के लायक है, तो इस आकर्षक विषय पर गहराई से विचार करने का यह सही मौका है, और इस लेख में - हम बस यही करेंगे।
इसके मूल में, डॉट्स एंटिटी कंपोनेंट सिस्टम (ईसीएस) आर्किटेक्चरल पैटर्न को लागू करता है। इस अवधारणा को सरल बनाने के लिए, आइए इसे इस तरह वर्णित करें: ईसीएस तीन मूलभूत तत्वों पर बनाया गया है: इकाइयां, घटक और सिस्टम।
संस्थाओं में , अपने आप में, किसी अंतर्निहित कार्यक्षमता या विवरण का अभाव होता है। इसके बजाय, वे विभिन्न घटकों के लिए कंटेनर के रूप में काम करते हैं, जो उन्हें गेम लॉजिक, ऑब्जेक्ट रेंडरिंग, ध्वनि प्रभाव और बहुत कुछ के लिए विशिष्ट विशेषताएं प्रदान करते हैं।
घटक , बदले में, विभिन्न प्रकारों में आते हैं और अपनी स्वयं की स्वतंत्र प्रसंस्करण क्षमताओं के बिना केवल डेटा संग्रहीत करते हैं।
ईसीएस ढांचे को पूरा करने वाले सिस्टम हैं, जो घटकों को संसाधित करते हैं, इकाई निर्माण और विनाश को संभालते हैं, और घटकों को जोड़ने या हटाने का प्रबंधन करते हैं।
उदाहरण के लिए, "स्पेस शूटर" गेम बनाते समय, खेल के मैदान में कई वस्तुएं होंगी: खिलाड़ी का अंतरिक्ष यान, दुश्मन, क्षुद्रग्रह, लूट, आप इसे नाम दें।
इन सभी वस्तुओं को किसी विशिष्ट विशेषता से रहित, अपने आप में इकाई माना जाता है। हालाँकि, उन्हें अलग-अलग घटक निर्दिष्ट करके, हम उन्हें अद्वितीय विशेषताओं से भर सकते हैं।
प्रदर्शित करने के लिए, यह मानते हुए कि ये सभी ऑब्जेक्ट खेल के मैदान पर स्थिति रखते हैं, हम एक स्थिति घटक बना सकते हैं जो ऑब्जेक्ट के निर्देशांक रखता है। इसके अलावा, खिलाड़ी के अंतरिक्ष यान, दुश्मनों और क्षुद्रग्रहों के लिए, हम स्वास्थ्य घटकों को शामिल कर सकते हैं; वस्तु टकराव से निपटने के लिए जिम्मेदार प्रणाली इन संस्थाओं के स्वास्थ्य को नियंत्रित करेगी। इसके अतिरिक्त, हम शत्रुओं के साथ एक शत्रु प्रकार का घटक जोड़ सकते हैं, जिससे शत्रु नियंत्रण प्रणाली उनके निर्दिष्ट प्रकार के आधार पर उनके व्यवहार को नियंत्रित करने में सक्षम हो जाती है।
हालाँकि यह स्पष्टीकरण एक सरल, प्रारंभिक अवलोकन प्रदान करता है, वास्तविकता कुछ अधिक जटिल है। बहरहाल, मुझे विश्वास है कि ईसीएस की मौलिक अवधारणा स्पष्ट है। इन बातों से हटकर, आइए इस दृष्टिकोण के फायदों पर गौर करें।
एंटिटी कंपोनेंट सिस्टम (ईसीएस) दृष्टिकोण के मुख्य लाभों में से एक वास्तुशिल्प डिजाइन है जिसे यह बढ़ावा देता है। ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग (ओओपी) इनहेरिटेंस और इनकैप्सुलेशन जैसे पैटर्न के साथ एक महत्वपूर्ण विरासत रखती है, और यहां तक कि अनुभवी प्रोग्रामर भी विकास की गर्मी में वास्तुशिल्प गलतियां कर सकते हैं, जिससे दीर्घकालिक परियोजनाओं में रिफैक्टरिंग या पेचीदा तर्क हो सकता है।
इसके विपरीत, ईसीएस एक सरल और सहज वास्तुकला प्रदान करता है। हर चीज़ स्वाभाविक रूप से अलग-अलग घटकों और प्रणालियों में गिरती है, जिससे इस दृष्टिकोण का उपयोग करके समझना और विकसित करना आसान हो जाता है; यहां तक कि नौसिखिए डेवलपर्स भी न्यूनतम त्रुटियों के साथ इस दृष्टिकोण को तुरंत समझ लेते हैं।
ईसीएस एक समग्र दृष्टिकोण का अनुसरण करता है, जहां जटिल वंशानुक्रम पदानुक्रमों के बजाय पृथक घटकों और व्यवहार प्रणालियों का निर्माण किया जाता है। इन घटकों और प्रणालियों को आसानी से जोड़ा या हटाया जा सकता है, जिससे इकाई विशेषताओं और व्यवहार में लचीले परिवर्तन की अनुमति मिलती है - यह दृष्टिकोण कोड पुन: प्रयोज्य को काफी बढ़ाता है।
ईसीएस का एक अन्य प्रमुख लाभ प्रदर्शन अनुकूलन है। ईसीएस में, डेटा को मेमोरी में सन्निहित और अनुकूलित तरीके से संग्रहीत किया जाता है, जिसमें समान डेटा प्रकार एक-दूसरे के करीब रखे जाते हैं। यह डेटा एक्सेस को अनुकूलित करता है, कैश मिस को कम करता है, और मेमोरी एक्सेस पैटर्न में सुधार करता है। इसके अलावा, अलग-अलग डेटा ब्लॉक से बने सिस्टम को विभिन्न प्रक्रियाओं में समानांतर करना आसान होता है, जिससे पारंपरिक दृष्टिकोण की तुलना में असाधारण प्रदर्शन लाभ होता है।
यूनिटी डॉट्स में यूनिटी टेक्नोलॉजीज द्वारा प्रदान की गई तकनीकों का एक सेट शामिल है जो यूनिटी में ईसीएस अवधारणा को लागू करता है। इसमें खेल विकास के विभिन्न पहलुओं को बढ़ाने के लिए डिज़ाइन किए गए कई पैकेज शामिल हैं; आइए अब उनमें से कुछ को कवर करें।
डॉट्स का मूल एंटिटीज़ पैकेज है, जो परिचित मोनोबिहेवियर और गेमऑब्जेक्ट्स से एंटिटी कंपोनेंट सिस्टम दृष्टिकोण में संक्रमण की सुविधा प्रदान करता है। यह पैकेज डॉट्स-आधारित विकास की नींव बनाता है।
यूनिटी फिजिक्स पैकेज खेलों में भौतिकी को संभालने के लिए एक नया दृष्टिकोण पेश करता है, जो समानांतर गणनाओं के माध्यम से उल्लेखनीय गति प्राप्त करता है।
इसके अतिरिक्त, हॉक फिजिक्स फॉर यूनिटी पैकेज आधुनिक हॉक फिजिक्स इंजन के साथ एकीकरण की अनुमति देता है। यह इंजन उच्च-प्रदर्शन टकराव का पता लगाने और भौतिक सिमुलेशन प्रदान करता है, जो द लीजेंड ऑफ ज़ेल्डा: ब्रेथ ऑफ द वाइल्ड, डूम इटरनल, डेथ स्ट्रैंडिंग, मॉर्टल कोम्बैट 11 और अधिक जैसे लोकप्रिय गेम को शक्ति प्रदान करता है।
एंटिटीज़ ग्राफ़िक्स पैकेज डॉट्स में रेंडरिंग पर केंद्रित है। यह रेंडरिंग डेटा को कुशल तरीके से एकत्र करने में सक्षम बनाता है और यूनिवर्सल रेंडर पाइपलाइन (यूआरपी) या हाई डेफिनिशन रेंडर पाइपलाइन (एचडीआरपी) जैसी मौजूदा रेंडर पाइपलाइनों के साथ निर्बाध रूप से काम करता है।
एक और बात, यूनिटी नेटकोड नामक एक नेटवर्किंग तकनीक भी सक्रिय रूप से विकसित कर रही है। इसमें निम्न-स्तरीय मल्टीप्लेयर गेम विकास के लिए यूनिटी ट्रांसपोर्ट, पारंपरिक दृष्टिकोण के लिए गेमऑब्जेक्ट्स के लिए नेटकोड और एंटिटीज़ पैकेज के लिए उल्लेखनीय यूनिटी नेटकोड जैसे पैकेज शामिल हैं, जो डॉट्स सिद्धांतों के साथ संरेखित हैं। ये पैकेज अपेक्षाकृत नए हैं और भविष्य में भी विकसित होते रहेंगे।
डॉट्स से निकटता से संबंधित कई तकनीकों का उपयोग डॉट्स ढांचे के भीतर और उससे आगे भी किया जा सकता है। जॉब सिस्टम पैकेज समानांतर गणनाओं के साथ कोड लिखने का एक सुविधाजनक तरीका प्रदान करता है। यह काम को छोटे-छोटे हिस्सों में विभाजित करने के इर्द-गिर्द घूमता है, जिन्हें नौकरियां कहा जाता है, जो अपने डेटा पर गणना करते हैं। जॉब सिस्टम कुशल निष्पादन के लिए इन नौकरियों को थ्रेड्स में समान रूप से वितरित करता है।
कोड सुरक्षा सुनिश्चित करने के लिए, जॉब सिस्टम ब्लिटेबल डेटा प्रकारों के प्रसंस्करण का समर्थन करता है। ब्लिटेबल डेटा प्रकारों का प्रबंधित और अप्रबंधित मेमोरी में समान प्रतिनिधित्व होता है और प्रबंधित और अप्रबंधित कोड के बीच पारित होने पर किसी रूपांतरण की आवश्यकता नहीं होती है। ब्लिटेबल प्रकारों के उदाहरणों में बाइट, sbyte, शॉर्ट, यूशॉर्ट, इंट, uint, लॉन्ग, उलॉन्ग, फ्लोट, डबल, IntPtr और UIntPtr शामिल हैं। ब्लिटेबल आदिम प्रकारों और विशेष रूप से ब्लिटेबल प्रकारों वाली संरचनाओं के एक-आयामी सरणियों को भी ब्लिटेबल माना जाता है।
हालाँकि, ब्लिटेबल प्रकारों की एक चर सरणी वाले प्रकारों को स्वयं ब्लिटेबल नहीं माना जाता है। इस सीमा को संबोधित करने के लिए, यूनिटी ने कलेक्शंस पैकेज विकसित किया है, जो नौकरियों में उपयोग के लिए अप्रबंधित डेटा संरचनाओं का एक सेट प्रदान करता है। ये संग्रह संरचित हैं और एकता तंत्र का उपयोग करके अप्रबंधित मेमोरी में डेटा संग्रहीत करते हैं। डिस्पोजल() पद्धति का उपयोग करके इन संग्रहों को पुनः आवंटित करना डेवलपर की जिम्मेदारी है।
एक अन्य महत्वपूर्ण पैकेज बर्स्ट कंपाइलर है, जिसका उपयोग अत्यधिक अनुकूलित कोड उत्पन्न करने के लिए जॉब सिस्टम के साथ किया जा सकता है। हालाँकि यह कुछ कोड उपयोग सीमाओं के साथ आता है, बर्स्ट कंपाइलर एक महत्वपूर्ण प्रदर्शन को बढ़ावा देता है।
जैसा कि उल्लेख किया गया है, जॉब सिस्टम और बर्स्ट कंपाइलर डॉट्स के प्रत्यक्ष घटक नहीं हैं, लेकिन कुशल और तेज़ समानांतर गणनाओं की प्रोग्रामिंग में बहुमूल्य सहायता प्रदान करते हैं। आइए एक व्यावहारिक उदाहरण का उपयोग करके उनकी क्षमताओं का परीक्षण करें: कार्यान्वयन
private void SimulateStep() { Profiler.BeginSample(nameof(SimulateStep)); for (var i = 0; i < width; i++) { for (var j = 0; j < height; j++) { var aliveNeighbours = CountAliveNeighbours(i, j); var index = i * height + j; var isAlive = aliveNeighbours switch { 2 => _cellStates[index], 3 => true, _ => false }; _tempResults[index] = isAlive; } } _tempResults.CopyTo(_cellStates); Profiler.EndSample(); } private int CountAliveNeighbours(int x, int y) { var count = 0; for (var i = x - 1; i <= x + 1; i++) { if (i < 0 || i >= width) continue; for (var j = y - 1; j <= y + 1; j++) { if (j < 0 || j >= height) continue; if (_cellStates[i * width + j]) { count++; } } } return count; }
मैंने गणना में लगने वाले समय को मापने के लिए प्रोफाइलर में मार्कर जोड़े हैं। कोशिकाओं की स्थिति को एक-आयामी सरणी में संग्रहीत किया जाता है जिसे _सेलस्टेट्स कहा जाता है। हम प्रारंभ में अस्थायी परिणाम _tempResults पर लिखते हैं और फिर गणना पूरी होने पर उन्हें वापस _सेलस्टेट्स में कॉपी कर देते हैं। यह दृष्टिकोण आवश्यक है क्योंकि अंतिम परिणाम सीधे _सेलस्टेट्स पर लिखने से बाद की गणनाएं प्रभावित होंगी।
मैंने 1000x1000 सेल का एक फ़ील्ड बनाया और प्रदर्शन को मापने के लिए प्रोग्राम चलाया। यहाँ परिणाम हैं:
जैसा कि परिणामों से देखा गया, गणना में 380 एमएस लगे।
आइए अब प्रदर्शन को बेहतर बनाने के लिए जॉब सिस्टम और बर्स्ट कंपाइलर लागू करें। सबसे पहले, हम कॉनवे के गेम ऑफ लाइफ एल्गोरिदम को क्रियान्वित करने के लिए जिम्मेदार जॉब बनाएंगे।
public struct SimulationJob : IJobParallelFor { public int Width; public int Height; [ReadOnly] public NativeArray<bool> CellStates; [WriteOnly] public NativeArray<bool> TempResults; public void Execute(int index) { var i = index / Height; var j = index % Height; var aliveNeighbours = CountAliveNeighbours(i, j); var isAlive = aliveNeighbours switch { 2 => CellStates[index], 3 => true, _ => false }; TempResults[index] = isAlive; } private int CountAliveNeighbours(int x, int y) { var count = 0; for (var i = x - 1; i <= x + 1; i++) { if (i < 0 || i >= Width) continue; for (var j = y - 1; j <= y + 1; j++) { if (j < 0 || j >= Height) continue; if (CellStates[i * Width + j]) { count++; } } } return count; } }
मैंने सेलस्टेट्स फ़ील्ड में [केवल पढ़ने के लिए] विशेषता निर्दिष्ट की है, जिससे किसी भी थ्रेड से सरणी के सभी मानों तक अप्रतिबंधित पहुंच की अनुमति मिलती है। हालाँकि, TempResults फ़ील्ड के लिए, जिसमें [WriteOnly] विशेषता है, लेखन केवल Execute(int Index) विधि में निर्दिष्ट सूचकांक के माध्यम से किया जा सकता है। किसी भिन्न सूचकांक में मान लिखने का प्रयास करने से एक चेतावनी उत्पन्न होगी। मल्टी-थ्रेडेड मोड में काम करते समय यह डेटा सुरक्षा सुनिश्चित करता है।
अब, नियमित कोड से, आइए अपना कार्य प्रारंभ करें:
private void SimulateStepWithJob() { Profiler.BeginSample(nameof(SimulateStepWithJob)); var job = new SimulationJob { Width = width, Height = height, CellStates = _cellStates, TempResults = _tempResults }; var jobHandler = job.Schedule(width * height, 4); jobHandler.Complete(); job.TempResults.CopyTo(_cellStates); Profiler.EndSample(); }
सभी आवश्यक डेटा की प्रतिलिपि बनाने के बाद, हम शेड्यूल() विधि का उपयोग करके कार्य के निष्पादन को शेड्यूल करते हैं। यह ध्यान रखना महत्वपूर्ण है कि यह शेड्यूलिंग गणनाओं को तुरंत निष्पादित नहीं करती है: ये क्रियाएं मुख्य थ्रेड से शुरू की जाती हैं, और निष्पादन विभिन्न थ्रेड्स के बीच वितरित श्रमिकों के माध्यम से होता है। कार्य पूरा होने की प्रतीक्षा करने के लिए, हम jobHandler.Complete() का उपयोग करते हैं। केवल तभी हम प्राप्त परिणाम को वापस _सेलस्टेट्स पर कॉपी कर सकते हैं।
आइए गति मापें:
निष्पादन गति लगभग दस गुना बढ़ गई है, और निष्पादन समय अब लगभग 42 एमएस है। प्रोफाइलर विंडो में, हम देख सकते हैं कि कार्यभार 17 श्रमिकों के बीच वितरित किया गया था। यह संख्या परीक्षण वातावरण में प्रोसेसर थ्रेड्स की संख्या से थोड़ी कम है, जो 10 कोर और 20 थ्रेड्स वाला Intel® Core™ i9-10900 है। हालांकि कम कोर वाले प्रोसेसर पर परिणाम भिन्न हो सकते हैं, हम प्रोसेसर की शक्ति का पूर्ण उपयोग सुनिश्चित कर सकते हैं।
लेकिन इतना ही नहीं - अब बर्स्ट कंपाइलर का उपयोग करने का समय है, जो महत्वपूर्ण कोड अनुकूलन प्रदान करता है लेकिन कुछ प्रतिबंधों के साथ आता है। बर्स्ट कंपाइलर को सक्षम करने के लिए, बस SimulationJob में [BurstCompile] विशेषता जोड़ें।
[BurstCompile] public struct SimulationJob : IJobParallelFor { ... }
आइए फिर से मापें:
परिणाम सबसे आशावादी अपेक्षाओं से भी अधिक हैं: प्रारंभिक परिणाम की तुलना में गति लगभग 200 गुना बढ़ गई है। अब, 1 मिलियन कोशिकाओं के लिए गणना समय 2 एमएस से अधिक नहीं है। प्रोफाइलर में, बर्स्ट कंपाइलर के साथ संकलित कोड द्वारा निष्पादित भागों को हरे रंग में हाइलाइट किया गया है।
हालांकि मल्टीथ्रेडेड गणनाओं का उपयोग हमेशा आवश्यक नहीं हो सकता है, और बर्स्ट कंपाइलर का उपयोग हमेशा संभव नहीं हो सकता है, हम मल्टी-कोर आर्किटेक्चर की ओर प्रोसेसर विकास में एक सामान्य प्रवृत्ति देख सकते हैं। इसका मतलब यह है कि हमें उनकी पूरी शक्ति का उपयोग करने के लिए तैयार रहना चाहिए। ईसीएस, और विशेष रूप से यूनिटी डॉट्स, इस प्रतिमान के साथ पूरी तरह से संरेखित हैं।
मेरा मानना है कि कम से कम यूनिटी डॉट्स ध्यान देने योग्य है। हालांकि यह हर मामले के लिए सबसे अच्छा समाधान नहीं हो सकता है, ईसीएस कई खेलों में अपनी उपयोगिता साबित कर सकता है।
यूनिटी डॉट्स फ्रेमवर्क, अपने डेटा-उन्मुख और मल्टीथ्रेडेड दृष्टिकोण के साथ, यूनिटी गेम्स में प्रदर्शन को अनुकूलित करने की जबरदस्त क्षमता प्रदान करता है। एंटिटी कंपोनेंट सिस्टम आर्किटेक्चर को अपनाकर और जॉब सिस्टम और बर्स्ट कंपाइलर जैसी प्रौद्योगिकियों का लाभ उठाकर, डेवलपर्स प्रदर्शन और स्केलेबिलिटी के नए स्तरों को अनलॉक कर सकते हैं।
जैसे-जैसे खेल का विकास जारी है और हार्डवेयर आगे बढ़ रहा है, यूनिटी डॉट्स को अपनाना तेजी से मूल्यवान होता जा रहा है। यह डेवलपर्स को आधुनिक प्रोसेसर की पूरी क्षमता का उपयोग करने और अत्यधिक अनुकूलित और कुशल गेम वितरित करने का अधिकार देता है। हालांकि यूनिटी डॉट्स हर परियोजना के लिए आदर्श समाधान नहीं हो सकता है, लेकिन यह निस्संदेह प्रदर्शन-संचालित विकास और स्केलेबिलिटी चाहने वालों के लिए बहुत बड़ा वादा रखता है।
यूनिटी डॉट्स एक शक्तिशाली ढांचा है जो प्रदर्शन को बढ़ाकर, समानांतर गणनाओं को सक्षम करके और मल्टी-कोर प्रोसेसिंग के भविष्य को अपनाकर गेम डेवलपर्स को महत्वपूर्ण रूप से लाभान्वित कर सकता है। आधुनिक हार्डवेयर का पूरी तरह से लाभ उठाने और यूनिटी गेम्स के प्रदर्शन को अनुकूलित करने के लिए इसे अपनाना तलाशने और विचार करने लायक है।