Szukaj…


Uwagi

  1. Jeśli to możliwe, wyłącz skrypty na obiektach, gdy nie są one potrzebne. Na przykład, jeśli masz skrypt na obiekcie wroga, który szuka i strzela do gracza, rozważ wyłączenie tego skryptu, gdy wróg jest zbyt daleko, na przykład od gracza.

Szybkie i skuteczne kontrole

Unikaj niepotrzebnych operacji i wywołań metod, gdziekolwiek możesz, szczególnie w metodzie, która jest wywoływana wiele razy na sekundę, np. Update .

Kontrola odległości / zasięgu

Podczas porównywania odległości używaj sqrMagnitude zamiast magnitude . Pozwala to uniknąć niepotrzebnych operacji sqrt . Zauważ, że podczas korzystania z sqrMagnitude , prawa strona musi być również sqrMagnitude do kwadratu.

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

Kontrole graniczne

Przecięcia obiekt może być grubsza sprawdzana poprzez sprawdzenie, czy ich Collider / Renderer ograniczającą przecinają. Struktura Bounds ma również przydatną metodę Intersects , która pomaga ustalić, czy przecinają się dwie granice.

Bounds pomagają nam również obliczyć przybliżone przybliżenie rzeczywistej (powierzchni do powierzchni) odległości między obiektami (patrz Bounds.SqrDistance ).

Ostrzeżenia

Sprawdzanie granic działa naprawdę dobrze dla obiektów wypukłych, ale kontrole granic obiektów wklęsłych mogą prowadzić do znacznie większych niedokładności w zależności od kształtu obiektu.

Korzystanie z Mesh.bounds nie jest zalecane, ponieważ zwraca lokalne ograniczenia przestrzeni. MeshRenderer.bounds tego użyj MeshRenderer.bounds .

Moc Coroutine

Stosowanie

Jeśli masz długotrwałą operację, która opiera się na nie-bezpiecznym wątku Unity API, użyj Coroutines, aby podzielić go na wiele ramek i zachować reakcję aplikacji.

Korpusy pomagają również wykonywać drogie akcje co n-tą ramkę zamiast uruchamiać tę akcję dla każdej ramki.

Dzielenie długotrwałych procedur na wiele ramek

Korpusy pomagają w rozprowadzaniu długotrwałych operacji w wielu ramkach, aby pomóc utrzymać szybkość klatek aplikacji.

Procedury, które malują lub generują teren proceduralnie lub generują hałas, są przykładami, które mogą wymagać leczenia 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
    }
}

Powyższy kod jest łatwym do zrozumienia przykładem. W kodzie produkcyjnym lepiej jest unikać czek per-pixel, że kontrole kiedy yield return (może to zrobić co 2-3 rzędy) i wstępnie obliczyć for długości pętli z góry.

Wykonywanie kosztownych działań rzadziej

Korytarze pomagają rzadziej wykonywać drogie czynności, dzięki czemu nie są tak duże, jak w przypadku każdej klatki.

Biorąc następujący przykład bezpośrednio z instrukcji :

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

Testy zbliżeniowe można jeszcze bardziej zoptymalizować za pomocą interfejsu API CullingGroup .

Typowe pułapki

Częstym błędem popełnianym przez programistów jest uzyskiwanie dostępu do wyników lub skutków ubocznych koroutyn poza koroutyną. Korpusy zwracają kontrolę osobie dzwoniącej, gdy tylko pojawi się instrukcja yield return i wynik lub efekt uboczny mogą jeszcze nie zostać wykonane. Problemów obejść, gdzie trzeba użyć efektu wynik / boczną linią współprogram, sprawdź tę odpowiedź .

Smyczki

Można argumentować, że w Unity jest więcej wieprzy zasobów niż skromny sznurek, ale jest to jeden z łatwiejszych aspektów do wczesnego naprawienia.

Operacje na ciągach budują śmieci

Większość operacji na łańcuchach tworzy niewielkie ilości śmieci, ale jeśli te operacje są wywoływane kilka razy w ciągu jednej aktualizacji, stosy się kumulują. Z czasem spowoduje to automatyczne wyrzucanie elementów bezużytecznych, co może spowodować widoczny skok CPU.

Buforuj swoje operacje na łańcuchach

Rozważ następujący przykład.

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

Może to wyglądać głupio i niepotrzebnie, ale jeśli pracujesz z Shadersami, możesz napotkać takie sytuacje. Buforowanie kluczy zrobi różnicę.

Należy pamiętać, że literały łańcuchowe i stałe nie generują śmieci, ponieważ są one wstrzykiwane statycznie do obszaru stosu programu. Jeśli generujesz ciągi w czasie wykonywania i gwarantujesz, że generujesz te same ciągi za każdym razem, jak w powyższym przykładzie, buforowanie z pewnością pomoże.

W innych przypadkach, gdy generowany ciąg nie jest za każdym razem taki sam, nie ma innej alternatywy niż generowanie tych ciągów. Jako taki, skok pamięci z ręcznym generowaniem ciągów za każdym razem jest zwykle nieistotny, chyba że generuje się dziesiątki tysięcy ciągów naraz.

Większość operacji na łańcuchach to komunikaty debugowania

Wykonywanie operacji łańcuchowych dla komunikatów debugowania, tj. Debug.Log("Object Name: " + obj.name) jest w porządku i nie można go uniknąć podczas programowania. Ważne jest jednak, aby upewnić się, że nieistotne komunikaty debugowania nie znajdą się w wydanym produkcie.

Jednym ze sposobów jest użycie atrybutu warunkowego w wywołaniach debugowania. To nie tylko usuwa wywołania metod, ale także wszystkie operacje na łańcuchach wchodzące w jego skład.

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

To jest uproszczony przykład. Możesz poświęcić trochę czasu na zaprojektowanie pełniejszej procedury rejestrowania.

Porównanie ciągów

Jest to niewielka optymalizacja, ale warto o tym wspomnieć. Porównywanie łańcuchów jest nieco bardziej zaangażowane, niż mogłoby się wydawać. System domyślnie spróbuje uwzględnić różnice kulturowe. Możesz zamiast tego użyć prostego porównania binarnego, które działa szybciej.

// 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>();

Odwołania do pamięci podręcznej

Odwołaj się do pamięci podręcznej, aby uniknąć kosztownych połączeń, szczególnie w funkcji aktualizacji. Można to zrobić, buforując te referencje na początku, jeśli są dostępne lub gdy są dostępne, i sprawdzając, czy null / bool jest płaski, aby uniknąć ponownego uzyskania referencji.

Przykłady:

Odwołania do komponentów pamięci podręcznej

zmiana

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

do

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

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

Odwołania do obiektów w pamięci podręcznej

zmiana

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

do

private Transform enemy;

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

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

Dodatkowo buforuj drogie połączenia, takie jak połączenia do Mathf, jeśli to możliwe.

Unikaj wywoływania metod przy użyciu ciągów

Unikaj wywoływania metod przy użyciu ciągów, które mogą akceptować metody. Podejście to wykorzysta refleksję, która może spowolnić grę, szczególnie gdy jest używana w funkcji aktualizacji.

Przykłady:

    //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();

Unikaj pustych metod jedności

Unikaj pustych metod jedności. Poza złym stylem programowania, skrypty uruchomieniowe są bardzo obciążone. W wielu przypadkach może to narastać i wpływać na wydajność.

void Update
{
}

void FixedUpdate
{
}


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow