unity3d
Optimierung
Suche…
Bemerkungen
- Deaktivieren Sie nach Möglichkeit Skripts für Objekte, wenn diese nicht benötigt werden. Wenn Sie beispielsweise ein Skript für ein feindliches Objekt haben, das der Spieler sucht und auf den Spieler schießt, sollten Sie dieses Skript deaktivieren, wenn der Feind beispielsweise zu weit vom Spieler entfernt ist.
Schnelle und effiziente Prüfungen
Vermeiden Sie unnötige Operationen und Methodenaufrufe, wo immer Sie können, insbesondere bei einer Methode, die oftmals pro Sekunde aufgerufen wird, z. B. Update
.
Entfernungs- / Entfernungsprüfungen
Verwenden Sie sqrMagnitude
anstelle des magnitude
wenn Sie Entfernungen vergleichen. Dies vermeidet unnötige sqrt
. Beachten Sie, dass bei der Verwendung von sqrMagnitude
auch die rechte Seite ein Quadrat sein muss.
if ((target.position - transform.position).sqrMagnitude < minDistance * minDistance))
Bounds Checks
Objektschnittstellen können grob geprüft werden, indem geprüft wird, ob sich deren Grenzen zwischen Collider
/ Renderer
schneiden. Die Bounds
Struktur verfügt auch über eine praktische Intersects
Methode, mit deren Hilfe festgestellt werden kann, ob sich zwei Grenzen schneiden.
Bounds
helfen uns auch, eine Bounds.SqrDistance
Annäherung an den tatsächlichen Abstand zwischen Objekten zu erhalten (siehe Bounds.SqrDistance
).
Vorsichtsmaßnahmen
Die Begrenzungsprüfung funktioniert für konvexe Objekte sehr gut, aber Begrenzungsprüfungen bei konkaven Objekten können abhängig von der Form des Objekts zu viel höheren Ungenauigkeiten führen.
Die Verwendung von Mesh.bounds
wird nicht empfohlen, da sie lokale Mesh.bounds
zurückgibt. Verwenden MeshRenderer.bounds
stattdessen MeshRenderer.bounds
.
Coroutine Power
Verwendungszweck
Wenn Sie einen lang andauernden Vorgang ausführen, der auf der nicht-Thread-sicheren Unity-API beruht , können Sie Coroutines verwenden , um ihn auf mehrere Frames aufzuteilen und Ihre Anwendung reaktionsfähig zu halten.
Coroutines helfen auch, teure Aktionen in jedem n-ten Frame auszuführen , anstatt diese Aktion in jedem Frame auszuführen .
Aufteilen langlaufender Routinen auf mehrere Frames
Coroutines helfen dabei, lange laufende Vorgänge auf mehrere Frames zu verteilen, um die Framerate Ihrer Anwendung zu erhalten.
Routinen, die prozedural malen oder Terrain erzeugen oder Lärm erzeugen, sind Beispiele, für die eine Coroutine-Behandlung erforderlich ist.
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
}
}
Der obige Code ist ein leicht verständliches Beispiel. In Produktionscode ist es besser , die pro Pixel Prüfung zu vermeiden , die überprüft , wenn sie Nutzen
yield return
wird (vielleicht tun es alle 2-3 Zeilen) und vorab berechnenfor
Schleifenlänge im Voraus.
Kostspielige Aktionen seltener durchführen
Mit Coroutinen können Sie seltener teure Aktionen ausführen, sodass die Leistung nicht so groß ist, wie dies bei jedem Frame der Fall wäre.
Nehmen Sie das folgende Beispiel direkt aus dem Handbuch :
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);
}
}
Durch die Verwendung der CullingGroup-API können Annäherungstests noch weiter optimiert werden.
Häufige Fehler
Ein häufiger Fehler, den Entwickler begehen, ist der Zugriff auf Ergebnisse oder Nebenwirkungen von Coroutinen außerhalb der Coroutine. Coroutines geben die Kontrolle an den Aufrufer zurück, sobald eine yield return
Anweisung gefunden wird und das Ergebnis oder der Nebeneffekt noch nicht ausgeführt wird. Zur Umgehung Probleme , bei denen Sie das Ergebnis / Nebenwirkung außerhalb des Koroutine verwenden müssen, überprüfen Sie diese Antwort .
Zeichenketten
Man könnte argumentieren, dass es in Unity größere Ressourcenfresser gibt als die demütige Saite, aber dies ist einer der einfacheren Aspekte, die frühzeitig behoben werden können.
String-Operationen bauen Müll auf
Die meisten String-Vorgänge erzeugen winzige Mengen an Müll, aber wenn diese Vorgänge im Verlauf einer einzelnen Aktualisierung mehrmals aufgerufen werden, stapeln sie sich. Im Laufe der Zeit wird die automatische Speicherbereinigung ausgelöst, was zu sichtbaren CPU-Spitzen führen kann.
Zwischenspeichern Sie Ihre Stringoperationen
Betrachten Sie das folgende Beispiel.
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");
}
Es kann dumm und überflüssig wirken, aber wenn Sie mit Shadern arbeiten, können Sie auf Situationen wie diese stoßen. Das Zwischenspeichern der Schlüssel macht einen Unterschied.
Bitte beachten Sie, dass Zeichenketten und Konstanten erzeugen keinen Müll, da sie statisch in den Programmstapel Raum injiziert werden. Wenn Sie zur Laufzeit Strings generieren und garantiert jedes Mal dieselben Strings wie im obigen Beispiel generieren, ist das Zwischenspeichern definitiv hilfreich.
Für andere Fälle, in denen der erzeugte String nicht jedes Mal gleich ist, gibt es keine andere Alternative zum Generieren dieser Strings. Daher ist die Gedächtnisspitze, bei der jedes Mal manuell Strings erzeugt werden, normalerweise vernachlässigbar, sofern nicht Zehntausende von Strings gleichzeitig erzeugt werden.
Die meisten Stringoperationen sind Debug-Nachrichten
Stringoperationen für Debug-Nachrichten ausführen, z. Debug.Log("Object Name: " + obj.name)
ist in Ordnung und kann während der Entwicklung nicht vermieden werden. Es ist jedoch wichtig sicherzustellen, dass irrelevante Debug-Nachrichten nicht im freigegebenen Produkt landen.
Eine Möglichkeit besteht darin, das Conditional-Attribut in Ihren Debug-Aufrufen zu verwenden. Dadurch werden nicht nur die Methodenaufrufe entfernt, sondern auch alle String-Vorgänge, die darin ausgeführt werden.
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);
}
}
Dies ist ein vereinfachtes Beispiel. Vielleicht möchten Sie etwas Zeit investieren, um eine vollständigere Protokollierungsroutine zu entwerfen.
Stringvergleich
Dies ist eine geringfügige Optimierung, die jedoch erwähnenswert ist. Der Vergleich von Strings ist etwas komplizierter als man denkt. Das System versucht standardmäßig, kulturelle Unterschiede zu berücksichtigen. Sie können stattdessen einen einfachen binären Vergleich verwenden, der schneller abschneidet.
// 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>();
Referenzen zwischenspeichern
Zwischenspeichern von Referenzen, um die teuren Aufrufe insbesondere in der Update-Funktion zu vermeiden. Dies kann durch Cachen dieser Referenzen beim Start (falls verfügbar oder falls verfügbar) und Überprüfen auf null / bool flat erfolgen, damit die Referenz nicht erneut abgerufen wird.
Beispiele:
Komponentenverweise zwischenspeichern
Veränderung
void Update()
{
var renderer = GetComponent<Renderer>();
renderer.material.SetColor("_Color", Color.green);
}
zu
private Renderer myRenderer;
void Start()
{
myRenderer = GetComponent<Renderer>();
}
void Update()
{
myRenderer.material.SetColor("_Color", Color.green);
}
Objektreferenzen zwischenspeichern
Veränderung
void Update()
{
var enemy = GameObject.Find("enemy");
enemy.transform.LookAt(new Vector3(0,0,0));
}
zu
private Transform enemy;
void Start()
{
this.enemy = GameObject.Find("enemy").transform;
}
void Update()
{
enemy.LookAt(new Vector3(0, 0, 0));
}
Zusätzlich können Sie kostspielige Anrufe wie Mathfax-Anrufe zwischenspeichern.
Vermeiden Sie den Aufruf von Methoden, die Strings verwenden
Vermeiden Sie den Aufruf von Methoden, die Strings verwenden, die Methoden akzeptieren. Bei diesem Ansatz werden Reflexionen verwendet, die das Spiel verlangsamen können, insbesondere wenn sie in der Aktualisierungsfunktion verwendet werden.
Beispiele:
//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();
Vermeiden Sie leere Einheitsmethoden
Vermeiden Sie leere Einheitsmethoden. Abgesehen von einem schlechten Programmierstil ist das Runtime-Scripting mit einem geringen Aufwand verbunden. In vielen Fällen kann sich dies auf die Leistung auswirken.
void Update
{
}
void FixedUpdate
{
}