Unity Optimaliser spillet ditt ved å bruke Profiler

Ytelse er et nøkkelaspekt i ethvert spill og ingen overraskelse, uansett hvor bra spillet er, hvis det kjører dårlig på brukerens maskin, vil det ikke føles like hyggelig.

Siden ikke alle har en avansert PC eller enhet (hvis du retter deg mot mobil), er det viktig å ha ytelse i tankene under hele utviklingsforløpet.

Det er flere grunner til at spillet kan kjøre sakte:

  • Gjengivelse (for mange høypolymasker, komplekse skyggelegginger eller bildeeffekter)
  • Lyd (for det meste forårsaket av feil lydimportinnstillinger)
  • Uoptimalisert kode (Skript som inneholder ytelseskrevende funksjoner på feil steder)

I denne opplæringen vil jeg vise hvordan du kan optimalisere koden din ved hjelp av Unity Profiler.

Profiler

Historisk sett var feilsøking av ytelse i Unity en kjedelig oppgave, men siden den gang har en ny funksjon blitt lagt til, kalt Profiler.

Profiler er et verktøy i Unity som lar deg raskt finne flaskehalsene i spillet ditt ved å overvåke minneforbruket, noe som i stor grad forenkler optimaliseringsprosessen.

Unity Profiler-vinduet

Dårlig ytelse

Dårlig ytelse kan skje når som helst: La oss si at du jobber med fiendens forekomst og når du plasserer den i scenen, fungerer den fint uten problemer, men etter hvert som du skaper flere fiender, kan du legge merke til fps (frames-per-second) ) begynner å falle.

Sjekk eksempelet nedenfor:

I scenen har jeg en kube med et skript festet til den, som flytter kuben fra side til side og viser objektnavnet:

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);
    }
}

Når vi ser på statistikken, kan vi se at spillet kjører med gode 800+ fps, så det har knapt noen innvirkning på ytelsen.

Men la oss se hva som vil skje når vi dupliserer kuben 100 ganger:

Fps falt med mer enn 700 poeng!

MERK: Alle tester ble utført med Vsync deaktivert

Generelt er det en god idé å begynne å optimalisere når spillet begynner å ha stamming, fryse eller fps faller under 120.

Hvordan bruke Profiler?

For å begynne å bruke Profiler trenger du:

  • Start spillet ved å trykke Play
  • Åpne Profiler ved å gå til Vindu -> Analyse -> Profiler (eller trykk Ctrl + 7)

  • Nytt vindu vil dukke opp som ser omtrent slik ut:

Unity 3D-profilvindu

  • Det kan se skremmende ut til å begynne med (spesielt med alle disse listene osv.), men det er ikke den delen vi skal se på.
  • Klikk på Tidslinje-fanen og endre den til Hierarki:

  • Du vil legge merke til 3 seksjoner (EditorLoop, PlayerLoop og Profiler.CollectEditorStats):

  • Utvid PlayerLoop for å se alle delene der beregningskraften blir brukt (MERK: Hvis PlayerLoop-verdiene ikke oppdateres, klikk på "Clear"-knappen øverst i Profiler-vinduet).

For de beste resultatene, rett spillkarakteren din til situasjonen (eller stedet) der spillet henger mest og vent i et par sekunder.

  • Etter å ha ventet litt, stopp spillet og observer PlayerLoop-listen

Du må se på GC Alloc-verdien, som står for Garbage Collection Allocation. Dette er en type minne som har blitt tildelt av komponenten, men som ikke lenger er nødvendig og som venter på å bli frigjort av Garbage Collection. Ideelt sett bør koden ikke generere noe søppel (eller være så nær 0 som mulig).

Tid ms er også en viktig verdi, den viser hvor lang tid det tok å kjøre koden i millisekunder, så ideelt sett bør du sikte på å redusere denne verdien også (ved å bufre verdier, unngå å kalle ytelseskrevende funksjoner hver oppdatering, etc.).

For å finne de plagsomme delene raskere, klikk på GC Alloc-kolonnen for å sortere verdiene fra høyere til lavere)

  • Klikk hvor som helst i diagrammet for CPU-bruk for å hoppe til den rammen. Spesifikt må vi se på topper, der fps var den laveste:

Unity CPU-bruksdiagram

Her er hva profileren avslørte:

GUI.Repaint tildeler 45,4KB, som er ganske mye, og utvider det avslørte mer info:

  • Det viser at de fleste tildelingene kommer fra GUIUtility.BeginGUI() og OnGUI()-metoden i SC_ShowName-skriptet, vel vitende om at vi kan begynne å optimalisere.

GUIUtility.BeginGUI() representerer en tom OnGUI()-metode (Ja, selv den tomme OnGUI()-metoden tildeler ganske mye minne).

Bruk Google (eller annen søkemotor) for å finne navnene du ikke kjenner igjen.

Her er OnGUI()-delen som må optimaliseres:

    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);
    }

Optimalisering

La oss begynne å optimalisere.

Hvert SC_ShowName-skript kaller sin egen OnGUI()-metode, noe som ikke er bra med tanke på at vi har 100 forekomster. Så hva kan gjøres med det? Svaret er: Å ha et enkelt skript med OnGUI()-metoden som kaller GUI-metoden for hver kube.

  • Først erstattet jeg standard OnGUI() i SC_ShowName-skriptet med public void GUImethod() som vil bli kalt fra et annet skript:
    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);
    }
  • Så opprettet jeg et nytt skript og kalte det SC_GUIMetod:

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_GUIMetod vil bli knyttet til et tilfeldig objekt i scenen og kalle alle GUI-metodene.

  • Vi gikk fra å ha 100 individuelle OnGUI()-metoder til å bare ha én, la oss trykke play og se resultatet:

  • GUIUtility.BeginGUI() allokerer nå bare 368B i stedet for 36,7KB, en stor reduksjon!

OnGUI()-metoden allokerer imidlertid fortsatt minne, men siden vi vet at den bare kaller GUImethod() fra SC_ShowName-skriptet, går vi rett til å feilsøke den metoden.

Men Profileren viser bare global informasjon, hvordan ser vi hva som skjer i metoden?

For å feilsøke inne i metoden har Unity en hendig API kalt Profiler.BeginSample

Profiler.BeginSample lar deg fange en spesifikk del av skriptet, som viser hvor lang tid det tok å fullføre og hvor mye minne som ble tildelt.

  • Før vi bruker Profiler-klassen i kode, må vi importere UnityEngine.Profiling-navneområdet i begynnelsen av skriptet:
using UnityEngine.Profiling;
  • Profiler-eksemplet fanges ved å legge til Profiler.BeginSample("SOME_NAME"); ved starten av fangsten og legge til Profiler.EndSample(); på slutten av fangsten, slik:
        Profiler.BeginSample("SOME_CODE");
        //...your code goes here
        Profiler.EndSample();

Siden jeg ikke vet hvilken del av GUIMethod() som forårsaker minnetildelinger, vedlagte jeg hver linje i Profiler.BeginSample og Profiler.EndSample (Men hvis metoden din har mange linjer, trenger du definitivt ikke å omslutte hver linje, bare del den i jevne biter og jobb derfra).

Her er en siste metode med Profiler Samples implementert:

    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();
    }
  • Nå trykker jeg på Play og ser hva det viser i Profiler:
  • For enkelhets skyld søkte jeg etter "sc_show_" i Profiler, siden alle prøver starter med det navnet.

  • Interessant... Mye minne blir tildelt i sc_show_names del 3, som tilsvarer denne delen av koden:
        GUI.color = Color.green;
        GUI.Label(new Rect(screenPos.x - 150/2, Screen.height - screenPos.y, 150, 25), gameObject.name);

Etter litt googling oppdaget jeg at å få Objects navn tildeler ganske mye minne. Løsningen er å tilordne et objekts navn til en strengvariabel i void Start(), på den måten vil den bare kalles én gang.

Her er den optimaliserte koden:

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();
    }
}
  • La oss se hva profilen viser:

Alle prøver allokerer 0B, så det blir ikke tildelt mer minne.

Foreslåtte artikler
Optimaliseringstips for Unity
Slik bruker du oppdatering i Unity
Forbedre ytelsen til et mobilspill i Unity
Unity lydklipp importinnstillinger for best ytelse
Billboard-generatoren for Unity
Hvordan bli en bedre programmerer i Unity
Hvordan lage et FNAF-inspirert spill i Unity