एकता प्रोफाइलर का उपयोग करके अपने गेम को अनुकूलित करें

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

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

गेम के धीमी गति से चलने के कई कारण हैं:

  • रेंडरिंग (बहुत अधिक हाई-पॉली मेश, जटिल शेडर या छवि प्रभाव)
  • ऑडियो (अधिकतर गलत ऑडियो आयात सेटिंग्स के कारण होता है)
  • अअनुकूलित कोड (स्क्रिप्ट जिनमें गलत स्थानों पर प्रदर्शन-मांग वाले कार्य शामिल हैं)

इस ट्यूटोरियल में, मैं दिखाऊंगा कि Unity प्रोफाइलर की मदद से अपने कोड को कैसे अनुकूलित किया जाए।

प्रोफाइलर

ऐतिहासिक रूप से, Unity में डिबगिंग प्रदर्शन एक कठिन काम था, लेकिन तब से, एक नई सुविधा जोड़ी गई है, जिसे प्रोफाइलर कहा जाता है।

प्रोफाइलर Unity में एक उपकरण है जो आपको मेमोरी खपत की निगरानी करके आपके गेम में बाधाओं को तुरंत इंगित करने देता है, जो अनुकूलन प्रक्रिया को बहुत सरल बनाता है।

यूनिटी प्रोफाइलर विंडो

बुरा प्रदर्शन

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

नीचे दिए गए उदाहरण की जाँच करें:

दृश्य में, मेरे पास एक क्यूब है जिसके साथ एक स्क्रिप्ट जुड़ी हुई है, जो क्यूब को एक तरफ से दूसरी तरफ ले जाती है और ऑब्जेक्ट का नाम प्रदर्शित करती है:

SC_ShowName.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SC_ShowName : MonoBehaviour
{
    bool moveLeft = true;
    float movedDistance = 0;

    // Start is called before the first frame update
    void Start()
    {
        moveLeft = Random.Range(0, 10) > 5;
    }

    // Update is called once per frame
    void Update()
    {
        //Move left and right in ping-pong fashion
        if (moveLeft)
        {
            if(movedDistance > -2)
            {
                movedDistance -= Time.deltaTime;
                Vector3 currentPosition = transform.position;
                currentPosition.x -= Time.deltaTime;
                transform.position = currentPosition;
            }
            else
            {
                moveLeft = false;
            }
        }
        else
        {
            if (movedDistance < 2)
            {
                movedDistance += Time.deltaTime;
                Vector3 currentPosition = transform.position;
                currentPosition.x += Time.deltaTime;
                transform.position = currentPosition;
            }
            else
            {
                moveLeft = true;
            }
        }
    }

    void OnGUI()
    {
        //Show object name on screen
        Camera mainCamera = Camera.main;
        Vector2 screenPos = mainCamera.WorldToScreenPoint(transform.position + new Vector3(0, 1, 0));
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), gameObject.name);
    }
}

आँकड़ों को देखते हुए, हम देख सकते हैं कि गेम अच्छे 800+ एफपीएस पर चलता है, इसलिए इसका प्रदर्शन पर कोई प्रभाव नहीं पड़ता है।

लेकिन आइए देखें कि जब हम क्यूब की 100 बार नकल करेंगे तो क्या होगा:

एफपीएस में 700 अंक से अधिक की गिरावट!

ध्यान दें: सभी परीक्षण Vsync अक्षम के साथ किए गए थे

आम तौर पर, जब खेल में हकलाना, ठंड लगना या एफपीएस 120 से नीचे चला जाए तो अनुकूलन शुरू करना एक अच्छा विचार है।

प्रोफाइलर का उपयोग कैसे करें?

प्रोफाइलर का उपयोग शुरू करने के लिए आपको आवश्यकता होगी:

  • Play दबाकर अपना गेम प्रारंभ करें
  • विंडो -> विश्लेषण -> प्रोफाइलर पर जाकर प्रोफाइलर खोलें (या Ctrl + 7 दबाएँ)

  • नई विंडो दिखाई देगी जो कुछ इस तरह दिखेगी:

यूनिटी 3डी प्रोफाइलर विंडो

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

  • आपको 3 अनुभाग दिखाई देंगे (एडिटरलूप, प्लेयरलूप, और प्रोफाइलर.कलेक्टएडिटरस्टैट्स):

  • उन सभी हिस्सों को देखने के लिए प्लेयरलूप का विस्तार करें जहां गणना शक्ति खर्च की जा रही है (नोट: यदि प्लेयरलूप मान अपडेट नहीं हो रहे हैं, तो प्रोफाइलर विंडो के शीर्ष पर "Clear" बटन पर क्लिक करें)।

सर्वोत्तम परिणामों के लिए, अपने गेम चरित्र को उस स्थिति (या स्थान) पर निर्देशित करें जहां गेम सबसे अधिक पिछड़ता है और कुछ सेकंड तक प्रतीक्षा करें।

  • थोड़ा इंतजार करने के बाद, गेम रोकें और प्लेयरलूप सूची देखें

आपको GC Alloc मान को देखना होगा, जो कचरा संग्रहण आवंटन के लिए है। यह एक प्रकार की मेमोरी है जिसे component द्वारा आवंटित किया गया है लेकिन अब इसकी आवश्यकता नहीं है और यह कचरा संग्रहण द्वारा मुक्त होने की प्रतीक्षा कर रहा है। आदर्श रूप से, कोड को कोई कचरा उत्पन्न नहीं करना चाहिए (या यथासंभव 0 के करीब होना चाहिए)।

समय ms भी एक महत्वपूर्ण मान है, यह दर्शाता है कि कोड को मिलीसेकंड में चलने में कितना समय लगा, इसलिए आदर्श रूप से, आपको इस मान को भी कम करने का लक्ष्य रखना चाहिए (मानों को कैशिंग करके, प्रत्येक अद्यतन में प्रदर्शन-मांग वाले कार्यों को कॉल करने से बचना आदि).).

परेशानी वाले हिस्सों का तेजी से पता लगाने के लिए, मानों को उच्च से निम्न क्रम में क्रमबद्ध करने के लिए जीसी एलोक कॉलम पर क्लिक करें)

  • सीपीयू उपयोग चार्ट में उस फ्रेम पर जाने के लिए कहीं भी क्लिक करें। विशेष रूप से, हमें उन शिखरों को देखने की ज़रूरत है, जहां एफपीएस सबसे कम था:

यूनिटी सीपीयू उपयोग चार्ट

प्रोफाइलर ने जो खुलासा किया वह यहां दिया गया है:

GUI.Repaint 45.4KB आवंटित कर रहा है, जो काफी अधिक है, इसका विस्तार करने पर अधिक जानकारी सामने आई:

  • यह दर्शाता है कि अधिकांश आवंटन SC_ShowName स्क्रिप्ट में GUIUtility.BeginGUI() और OnGUI() विधि से आ रहे हैं, यह जानते हुए कि हम अनुकूलन शुरू कर सकते हैं।

GUIUtility.BeginGUI() एक खाली OnGUI() विधि का प्रतिनिधित्व करता है (हां, यहां तक ​​कि खाली OnGUI() विधि भी काफी मेमोरी आवंटित करती है)।

जिन नामों को आप नहीं पहचानते उन्हें ढूंढने के लिए Google (या अन्य खोज इंजन) का उपयोग करें।

यहां OnGUI() भाग है जिसे अनुकूलित करने की आवश्यकता है:

    void OnGUI()
    {
        //Show object name on screen
        Camera mainCamera = Camera.main;
        Vector2 screenPos = mainCamera.WorldToScreenPoint(transform.position + new Vector3(0, 1, 0));
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), gameObject.name);
    }

अनुकूलन

आइए अनुकूलन शुरू करें।

प्रत्येक SC_ShowName स्क्रिप्ट अपनी स्वयं की OnGUI() विधि को कॉल करती है, जो कि हमारे पास 100 उदाहरणों को देखते हुए अच्छा नहीं है। तो इसके बारे में क्या किया जा सकता है? उत्तर है: OnGUI() विधि के साथ एक एकल स्क्रिप्ट रखना जो प्रत्येक क्यूब के लिए GUI विधि को कॉल करती है।

  • सबसे पहले, मैंने SC_ShowName स्क्रिप्ट में डिफ़ॉल्ट OnGUI() को सार्वजनिक void GUIMethod() से बदल दिया, जिसे किसी अन्य स्क्रिप्ट से कॉल किया जाएगा:
    public void GUIMethod()
    {
        //Show object name on screen
        Camera mainCamera = Camera.main;
        Vector2 screenPos = mainCamera.WorldToScreenPoint(transform.position + new Vector3(0, 1, 0));
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), gameObject.name);
    }
  • फिर मैंने एक नई स्क्रिप्ट बनाई और इसे SC_GUIMethod नाम दिया:

SC_GUIMethod.cs

using UnityEngine;

public class SC_GUIMethod : MonoBehaviour
{
    SC_ShowName[] instances; //All instances where GUI method will be called

    void Start()
    {
        //Find all instances
        instances = FindObjectsOfType<SC_ShowName>();
    }

    void OnGUI()
    {
        for(int i = 0; i < instances.Length; i++)
        {
            instances[i].GUIMethod();
        }
    }
}

SC_GUIMethod को दृश्य में एक यादृच्छिक ऑब्जेक्ट से जोड़ा जाएगा और सभी GUI विधियों को कॉल किया जाएगा।

  • हम 100 अलग-अलग OnGUI() तरीकों से केवल एक तक पहुंच गए हैं, आइए प्ले दबाएं और परिणाम देखें:

  • GUIUtility.BeginGUI() अब 36.7KB के बजाय केवल 368B आवंटित कर रहा है, एक बड़ी कमी!

हालाँकि, OnGUI() विधि अभी भी मेमोरी आवंटित कर रही है, लेकिन चूंकि हम जानते हैं कि यह केवल SC_ShowName स्क्रिप्ट से GUIMethod() को कॉल कर रही है, हम सीधे उस विधि को डीबग करने जा रहे हैं।

लेकिन प्रोफाइलर केवल वैश्विक जानकारी दिखाता है, हम कैसे देखते हैं कि विधि के अंदर वास्तव में क्या हो रहा है?

विधि के अंदर डिबग करने के लिए, Unity में एक आसान एपीआई है जिसे Profiler.BeginSample कहा जाता है

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

  • कोड में प्रोफाइलर क्लास का उपयोग करने से पहले, हमें स्क्रिप्ट की शुरुआत में UnityEngine.Profiling नेमस्पेस को आयात करना होगा:
using UnityEngine.Profiling;
  • प्रोफ़ाइलर नमूना कैप्चर की शुरुआत में Profiler.BeginSample("SOME_NAME"); जोड़कर और कैप्चर के अंत में Profiler.EndSample(); जोड़कर कैप्चर किया जाता है, जैसे यह:
        Profiler.BeginSample("SOME_CODE");
        //...your code goes here
        Profiler.EndSample();

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

यहां प्रोफाइलर नमूनों के साथ अंतिम विधि लागू की गई है:

    public void GUIMethod()
    {
        //Show object name on screen
        Profiler.BeginSample("sc_show_name part 1");
        Camera mainCamera = Camera.main;
        Profiler.EndSample();

        Profiler.BeginSample("sc_show_name part 2");
        Vector2 screenPos = mainCamera.WorldToScreenPoint(transform.position + new Vector3(0, 1, 0));
        Profiler.EndSample();

        Profiler.BeginSample("sc_show_name part 3");
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), gameObject.name);
        Profiler.EndSample();
    }
  • अब मैं Play दबाता हूं और देखता हूं कि यह प्रोफाइलर में क्या दिखाता है:
  • सुविधा के लिए, मैंने प्रोफाइलर में "sc_show_" खोजा, क्योंकि सभी नमूने इसी नाम से शुरू होते हैं।

  • दिलचस्प... sc_show_names भाग 3 में बहुत सारी मेमोरी आवंटित की जा रही है, जो कोड के इस भाग से मेल खाती है:
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), gameObject.name);

कुछ गूगलिंग के बाद, मुझे पता चला कि ऑब्जेक्ट का नाम प्राप्त करने से काफी मेमोरी आवंटित हो जाती है। इसका समाधान शून्य स्टार्ट() में एक स्ट्रिंग वेरिएबल को ऑब्जेक्ट का नाम निर्दिष्ट करना है, इस तरह इसे केवल एक बार ही कॉल किया जाएगा।

यहाँ अनुकूलित कोड है:

SC_ShowName.cs

using UnityEngine;
using UnityEngine.Profiling;

public class SC_ShowName : MonoBehaviour
{
    bool moveLeft = true;
    float movedDistance = 0;

    string objectName = "";

    // Start is called before the first frame update
    void Start()
    {
        moveLeft = Random.Range(0, 10) > 5;
        objectName = gameObject.name; //Store Object name to a variable
    }

    // Update is called once per frame
    void Update()
    {
        //Move left and right in ping-pong fashion
        if (moveLeft)
        {
            if(movedDistance > -2)
            {
                movedDistance -= Time.deltaTime;
                Vector3 currentPosition = transform.position;
                currentPosition.x -= Time.deltaTime;
                transform.position = currentPosition;
            }
            else
            {
                moveLeft = false;
            }
        }
        else
        {
            if (movedDistance < 2)
            {
                movedDistance += Time.deltaTime;
                Vector3 currentPosition = transform.position;
                currentPosition.x += Time.deltaTime;
                transform.position = currentPosition;
            }
            else
            {
                moveLeft = true;
            }
        }
    }

    public void GUIMethod()
    {
        //Show object name on screen
        Profiler.BeginSample("sc_show_name part 1");
        Camera mainCamera = Camera.main;
        Profiler.EndSample();

        Profiler.BeginSample("sc_show_name part 2");
        Vector2 screenPos = mainCamera.WorldToScreenPoint(transform.position + new Vector3(0, 1, 0));
        Profiler.EndSample();

        Profiler.BeginSample("sc_show_name part 3");
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), objectName);
        Profiler.EndSample();
    }
}
  • आइए देखें कि प्रोफाइलर क्या दिखा रहा है:

सभी नमूने 0B आवंटित कर रहे हैं, इसलिए कोई और मेमोरी आवंटित नहीं की जा रही है।

सुझाए गए लेख
एकता के लिए अनुकूलन युक्तियाँ
एकता में अद्यतन का उपयोग कैसे करें
यूनिटी में मोबाइल गेम के प्रदर्शन में सुधार
सर्वश्रेष्ठ प्रदर्शन के लिए यूनिटी ऑडियो क्लिप आयात सेटिंग्स
एकता के लिए बिलबोर्ड जनरेटर
यूनिटी में एक बेहतर प्रोग्रामर कैसे बनें
गेम डिज़ाइन की मौलिक अवधारणाएँ