Ricerca…


Osservazioni

  1. Se possibile, disabilita gli script sugli oggetti quando non sono necessari. Ad esempio, se hai uno script su un oggetto nemico che cerca e spara al giocatore, considera di disabilitare questo script quando il nemico è troppo lontano ad esempio dal giocatore.

Controlli veloci ed efficienti

Evita le operazioni non necessarie e le chiamate ai metodi ovunque tu sia, specialmente in un metodo chiamato molte volte al secondo, come Update .

Controllo distanza / intervallo

Usa sqrMagnitude invece di magnitude quando confronti le distanze. Ciò evita operazioni sqrt non necessarie. Nota che quando usi sqrMagnitude , anche il lato destro deve essere quadrato.

if ((target.position - transform.position).sqrMagnitude < minDistance * minDistance))

Controlli limitati

Le intersezioni degli oggetti possono essere controllate in modo grossolano controllando se i loro limiti di Collider / Renderer intersecano. La struttura Bounds ha anche un pratico metodo Intersects che aiuta a determinare se due limiti si intersecano.

Bounds ci aiutano anche a calcolare approssimativamente la distanza effettiva (da superficie a superficie) tra gli oggetti (vedere Bounds.SqrDistance ).

Avvertenze

Il controllo dei limiti funziona molto bene per gli oggetti convessi, ma i controlli dei limiti sugli oggetti concavi possono portare a imprecisioni molto più elevate a seconda della forma dell'oggetto.

L'utilizzo di Mesh.bounds non è raccomandato in quanto restituisce limiti di spazio locali. Utilizzare invece MeshRenderer.bounds .

Coroutine Power

uso

Se hai una lunga esecuzione che si basa sull'API Unity non thread-safe, usa Coroutines per suddividerla su più frame e mantenere la tua applicazione reattiva.

Le Coroutine aiutano anche a eseguire costose azioni ogni ennesimo fotogramma invece di eseguire quell'azione ogni fotogramma.

Divisione di routine a esecuzione prolungata su più frame

Le Coroutine aiutano a distribuire operazioni a lungo termine su più fotogrammi per mantenere il framerate della tua applicazione.

Le routine che dipingono o generano terreno proceduralmente o generano rumore sono esempi che potrebbero richiedere il trattamento di Coroutine.

for (int y = 0; y < heightmap.Height; y++) 
{
    for (int x = 0; x < heightmap.Width; x++)
    {
        // Generate pixel at (x, y)
        // Assign pixel at (x, y)
        
        // Process only 32768 pixels each frame
        if ((y * heightmap.Height + x) % 32 * 1024) == 0)
            yield return null; // Wait for next frame
    }
}

Il codice sopra è un esempio facile da capire. Nel codice di produzione è meglio evitare il controllo per pixel che controlla quando yield return (forse farlo ogni 2-3 righe) e pre-calcolare for lunghezza loop in anticipo.

Esecuzione di azioni costose meno frequentemente

Le coroutine ti aiutano a eseguire azioni costose meno frequentemente, in modo che non sia il più grande successo di prestazioni che sarebbe se eseguissi ogni fotogramma.

Prendendo il seguente esempio direttamente dal Manuale :

private void ProximityCheck() 
{
    for (int i = 0; i < enemies.Length; i++) 
    {
        if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance)
                return true;
    }
    return false;
}

private IEnumerator ProximityCheckCoroutine() 
{
    while(true) 
    {
        ProximityCheck();
        yield return new WaitForSeconds(.1f);
    }
}

I test di prossimità possono essere ulteriormente ottimizzati utilizzando l' API CullingGroup .

Insidie ​​comuni

Un errore comune che gli sviluppatori creano è l'accesso ai risultati o agli effetti collaterali delle coroutine al di fuori della coroutine. Le coroutine restituiscono il controllo al chiamante non appena viene rilevata una dichiarazione di yield return e il risultato o l'effetto collaterale non possono ancora essere eseguiti. Per aggirare i problemi in cui è necessario utilizzare il risultato / l'effetto collaterale al di fuori della coroutine, controllare questa risposta .

stringhe

Si potrebbe sostenere che ci sono più risorse per le risorse in Unity rispetto alla stringa umile, ma è uno degli aspetti più facili da risolvere in anticipo.

Le operazioni con le stringhe costruiscono garbage

La maggior parte delle operazioni con le stringhe costruisce piccole quantità di spazzatura, ma se quelle operazioni vengono chiamate più volte nel corso di un singolo aggiornamento, si accumula. Col tempo, attiverà la Garbage Collection automatica, che potrebbe determinare un picco della CPU visibile.

Memorizza le tue operazioni sulle stringhe

Considera il seguente esempio.

string[] StringKeys = new string[] {
    "Key0",
    "Key1",
    "Key2"
};

void Update()
{
    for (var i = 0; i < 3; i++)
    {
        // Cached, no garbage generated
        Debug.Log(StringKeys[i]);
    }

    for (var i = 0; i < 3; i++)
    {
        // Not cached, garbage every cycle
        Debug.Log("Key" + i);
    }

    // The most memory-efficient way is to not create a cache at all and use literals or constants.
    // However, it is not necessarily the most readable or beautiful way.
    Debug.Log("Key0");
    Debug.Log("Key1");
    Debug.Log("Key2");
}

Può sembrare sciocco e ridondante, ma se stai lavorando con Shader, potresti imbatterti in situazioni come queste. La memorizzazione nella cache delle chiavi farà la differenza.

Si prega di notare che le stringhe e le costanti non generano rifiuti, in quanto vengono iniettati in modo statico nello spazio stack del programma. Se si generano stringhe in fase di esecuzione e si garantisce che generano sempre le stesse stringhe come nell'esempio precedente, il caching sarà sicuramente di aiuto.

Per altri casi in cui la stringa generata non è la stessa ogni volta, non c'è altra alternativa alla generazione di quelle stringhe. In quanto tale, il picco di memoria con stringhe di generazione manuale ogni volta è generalmente trascurabile, a meno che non vengano generate decine di migliaia di stringhe alla volta.

La maggior parte delle operazioni con le stringhe sono messaggi di debug

Fare operazioni con le stringhe per i messaggi di debug, es. Debug.Log("Object Name: " + obj.name) va bene e non può essere evitato durante lo sviluppo. Tuttavia, è importante assicurarsi che i messaggi di debug non pertinenti non finiscano nel prodotto rilasciato.

Un modo è utilizzare l' attributo Conditional nelle chiamate di debug. Questo non solo rimuove le chiamate al metodo, ma anche tutte le operazioni sulle stringhe che vi entrano.

using UnityEngine;
using System.Collections;

public class ConditionalDebugExample: MonoBehaviour
{
    IEnumerator Start()
    {
        while(true)
        {
            // This message will pop up in Editor but not in builds
            Log("Elapsed: " + Time.timeSinceLevelLoad);
            yield return new WaitForSeconds(1f);
        }
    }

    [System.Diagnostics.Conditional("UNITY_EDITOR")]
    void Log(string Message)
    {
        Debug.Log(Message);
    }
}

Questo è un esempio semplificato. Potresti voler investire un po 'di tempo nella progettazione di una routine di registrazione più completa.

Confronto di stringhe

Questa è una piccola ottimizzazione, ma vale la pena menzionarla. Il confronto delle stringhe è leggermente più complicato di quanto si possa pensare. Il sistema proverà a tenere conto delle differenze culturali per impostazione predefinita. Puoi scegliere di utilizzare un semplice confronto binario, che esegue più velocemente.

// Faster string comparison
if (strA.Equals(strB, System.StringComparison.Ordinal)) {...}
// Compared to
if (strA == strB) {...}

// Less overhead
if (!string.IsNullOrEmpty(strA)) {...}
// Compared to
if (strA == "") {...}

// Faster lookups
Dictionary<string, int> myDic = new Dictionary<string, int>(System.StringComparer.Ordinal);
// Compared to
Dictionary<string, int> myDictionary = new Dictionary<string, int>();

Riferimenti di cache

Riferimenti della cache per evitare le costose chiamate, specialmente nella funzione di aggiornamento. Questo può essere fatto inserendo nella cache questi riferimenti all'avvio se disponibili o quando disponibili e controllando null / bool flat per evitare di ottenere nuovamente il riferimento.

Esempi:

Riferimenti ai componenti della cache

modificare

void Update()
{
    var renderer = GetComponent<Renderer>();
    renderer.material.SetColor("_Color", Color.green);
}

a

private Renderer myRenderer;
void Start()
{
    myRenderer = GetComponent<Renderer>();
}

void Update()
{
    myRenderer.material.SetColor("_Color", Color.green);
}

Riferimenti all'oggetto cache

modificare

void Update()
{
    var enemy = GameObject.Find("enemy");
    enemy.transform.LookAt(new Vector3(0,0,0));
}

a

private Transform enemy;

void Start()
{
    this.enemy = GameObject.Find("enemy").transform;
}

void Update()
{
    enemy.LookAt(new Vector3(0, 0, 0));
}

Inoltre, memorizza nella cache chiamate costose come le chiamate a Mathf ove possibile.

Evita di chiamare metodi usando stringhe

Evita di chiamare metodi usando stringhe che possono accettare metodi. Questo approccio farà uso di riflessioni che possono rallentare il tuo gioco, specialmente se usato nella funzione di aggiornamento.

Esempi:

    //Avoid StartCoroutine with method name
    this.StartCoroutine("SampleCoroutine");

    //Instead use the method directly
    this.StartCoroutine(this.SampleCoroutine());

    //Avoid send message
    var enemy = GameObject.Find("enemy");
    enemy.SendMessage("Die");

    //Instead make direct call
    var enemy = GameObject.Find("enemy") as Enemy;
    enemy.Die();

Evita i metodi di unità vuoti

Evita i metodi di unità vuoti. Oltre ad essere un cattivo stile di programmazione, c'è un piccolo overhead coinvolto nello scripting di runtime. In molti casi, questo può accumularsi e influire sulle prestazioni.

void Update
{
}

void FixedUpdate
{
}


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow