
Fortschrittliche Programmier- und Codearchitektur
Unity PlayerLoop verstehen
Die Unity PlayerLoop enthält Funktionen zur Interaktion mit dem Kern der Spiele-Engine. Diese Struktur umfasst eine Reihe von Systemen, die die Initialisierung und Aktualisierungen pro Frame handhaben. Alle Ihre Skripte werden sich auf diesen PlayerLoop verlassen, um Gameplay zu erstellen. Beim Profiling sehen Sie den Benutzercode Ihres Projekts unter der PlayerLoop – mit Editor-Komponenten unter der EditorLoop.
Es ist wichtig, die Ausführungsreihenfolge von Unity FrameLoop zu verstehen. Jedes Unity Skript führt mehrere Ereignisfunktionen in einer vorbestimmten Reihenfolge aus. Lernen Sie den Unterschied zwischen Awake, Start, Update und anderen Funktionen kennen, die den Lebenszyklus eines Skripts zur Verbesserung der Leistung erstellen.
Einige Beispiele sind die Verwendung von FixedUpdate anstelle von Update, wenn es sich um einen Starrkörper handelt, oder die Verwendung von Awake anstelle von Start zur Initialisierung von Variablen oder Spielzuständen, bevor das Spiel beginnt. Verwenden Sie diese, um den Code zu minimieren, der auf jedem Frame ausgeführt wird. Awake wird nur einmal während der Lebensdauer der Skriptinstanz und immer vor den Startfunktionen aufgerufen. Das bedeutet, dass Sie Start für den Umgang mit Objekten verwenden sollten, von denen Sie wissen, dass sie mit anderen Objekten sprechen können, oder sie abfragen sollten, wie sie initialisiert wurden.
Die spezifische Reihenfolge der Ausführung der Ereignisfunktionen finden Sie im Script-Lebenszyklusflussdiagramm.

Erstellung eines benutzerdefinierten Update-Managers
Wenn Ihr Projekt anspruchsvolle Leistungsanforderungen hat (z. B. ein Open-World-Spiel), sollten Sie einen benutzerdefinierten Update Manager mit Update, LateUpdate oder FixedUpdate erstellen.
Ein gebräuchliches Nutzungsmuster für Update oder LateUpdate ist, die Logik nur auszuführen, wenn eine Bedingung erfüllt ist. Dies kann zu einer Reihe von Callbacks pro Frame führen, die effektiv keinen Code ausführen, außer um diese Bedingung zu überprüfen.
Wann immer Unity eine Nachrichtenmethode wie Update oder LateUpdate aufruft, wird ein Interoperabilitätsaufruf durchgeführt – das bedeutet, ein Aufruf von der C/C++-Seite zur verwalteten C#-Seite. Für eine kleine Anzahl von Objekten ist dies kein Problem. Wenn Sie Tausende von Objekten haben, beginnt dieser Overhead erheblich zu werden.
Abonnieren Sie aktive Objekte für diesen Update Manager, wenn sie Rückrufe benötigen, und kündigen Sie sie, wenn sie dies nicht tun. Dieses Muster kann viele Interoperabilitätsaufrufe auf Ihre Monobehaviour Objekte reduzieren.
Beispiele für die Implementierung finden Sie in den spielenginespezifischen Optimierungstechniken.
Minimieren Sie Code, der jeden Frame ausführt
Überlegen Sie, ob Code für jeden Frame ausgeführt werden muss. Sie können unnötige Logik aus Update, LateUpdate und FixedUpdate entfernen. Diese Unity Ereignisfunktionen sind praktische Orte, um Code einzugeben, der jeden Frame aktualisieren muss, aber Sie können jede Logik extrahieren, die nicht mit dieser Frequenz aktualisiert werden muss.
Führen Sie Logik nur aus, wenn sich etwas ändert. Denken Sie daran, Techniken wie das Beobachtermuster in Form von Ereignissen zu nutzen, um eine bestimmte Funktionssignatur auszulösen.
Wenn Sie Update verwenden müssen, führen Sie den Code möglicherweise alle n Frames aus. Dies ist eine Möglichkeit, Time Slicing anzuwenden, eine gängige Technik, bei der eine hohe Arbeitslast über mehrere Frames verteilt wird.
In diesem Beispiel führen wir die ExampleExpensiveFunction einmal alle drei Frames aus.
Der Trick besteht darin, dies mit anderen Arbeiten zu verknüpfen, die auf den anderen Frames ausgeführt werden. In diesem Beispiel können Sie andere teure Funktionen „planen“, wenn Time.frameCount % Intervall == 1 oder Time.frameCount % Intervall == 2 ist.
Alternativ können Sie eine benutzerdefinierte Update Manager-Klasse verwenden, um die abonnierten Objekte alle n Frames zu aktualisieren.
Zwischenspeichern der Ergebnisse teurer Funktionen
In Unity Versionen vor 2020.2 können GameObject.Find, GameObject.GetComponent und Camera.main teuer sein, daher vermeiden Sie am besten den Aufruf in Update Methoden.
Vermeiden Sie außerdem, teure Methoden in OnEnable und OnDisable zu platzieren, wenn sie oft aufgerufen werden. Das häufige Aufrufen dieser Methoden kann zu CPU-Spitzen beitragen.
Führen Sie nach Möglichkeit teure Funktionen wie MonoBehaviour.Awake und MonoBehaviour.Starten Sie während der Initialisierungsphase. Cache die benötigten Referenzen und verwende sie später wieder. Sehen Sie sich unseren früheren Abschnitt auf der Unity PlayerLoop für die Ausführung von Skriptaufträgen genauer an.
Hier ist ein Beispiel, das die ineffiziente Verwendung eines wiederholten GetComponent-Aufrufs demonstriert:
void Update()
{
Renderer myRenderer = GetComponent();
ExampleFunction(myRenderer);
}
Rufen Sie stattdessen GetComponent nur einmal auf, da das Ergebnis der Funktion zwischengespeichert wird. Das zwischengespeicherte Ergebnis kann ohne weiteren Aufruf von GetComponent in Update wiederverwendet werden.
Lesen Sie mehr über die Ausführungsreihenfolge für Ereignisfunktionen.
Leere Unity Events vermeiden und Protokollanweisungen debuggen
Protokollanweisungen (insbesondere in Update, LateUpdate oder FixedUpdate) können die Leistung beeinträchtigen, deaktivieren Sie also Ihre Protokollanweisungen, bevor Sie einen Build erstellen. Um dies schnell zu tun, sollten Sie ein bedingtes Attribut zusammen mit einer Vorverarbeitungsrichtlinie erstellen.
Sie können beispielsweise eine benutzerdefinierte Klasse erstellen, wie unten gezeigt.
Generieren Sie Ihre Protokollnachricht mit Ihrer benutzerdefinierten Klasse. Wenn Sie den ENABLE_LOG-Preprocessor in den Player-Einstellungen > Scripting Define Symbols deaktivieren, verschwinden alle Ihre Log-Anweisungen auf einen Schlag.
Der Umgang mit Zeichenketten und Text ist eine häufige Ursache für Leistungsprobleme in Unity Projekten. Deshalb kann das Entfernen von Protokollanweisungen und deren teure String-Formatierung potenziell ein großer Leistungsgewinn sein.
Ebenso erfordern leere MonoBehaviours Ressourcen, daher sollten Sie leere Update- oder LateUpdate-Methoden entfernen. Verwenden Sie Preprocessor-Direktiven, wenn Sie diese Methoden zum Testen verwenden:
#if UNITY_EDITOR
void Update()
{
}
#endif
Hier können Sie den Update-Editor zum Testen verwenden, ohne unnötigen Overhead in Ihren Build zu schieben.
In diesem Blog-Eintrag zu 10 000 Update-Aufrufen wird erläutert, wie Unity das Monobehaviour.Update ausführt.
Stack Trace-Protokollierung deaktivieren
Verwenden Sie die Stack Trace-Optionen in den Player-Einstellungen, um zu steuern, welche Art von Protokollmeldungen angezeigt werden. Deaktivieren Sie Stack Traces, um die Leistung zu verbessern, wenn Ihre Anwendung Fehler oder Warnmeldungen in Ihrem Release Build protokolliert (z. B. um Absturzberichte in freier Wildbahn zu generieren).
Erfahren Sie mehr über die Stack Trace-Protokollierung.
Hashwerte statt String-Parameter verwenden
Unity verwendet keine Stringnamen, um die Eigenschaften von Animator, Material oder Shader intern zu adressieren. Zur Beschleunigung werden alle Eigenschaftsnamen in Eigenschafts-IDs gehasht und diese IDs werden zur Adressierung der Eigenschaften verwendet.
Wenn Sie eine Set- oder Get-Methode für einen Animator, Material oder Shader verwenden, verwenden Sie die Methode mit ganzzahligem Wert anstelle der Methoden mit String-Wert. Die Methoden mit String-Wert führen ein String-Hashing durch und leiten dann die Hash-ID an die Methoden mit ganzzahligem Wert weiter.
Verwenden Sie Animator.StringToHash für Animator-Eigenschaftennamen und Shader.PropertyToID für Material- und Shader-Eigenschaftennamen.
In Zusammenhang damit steht die Wahl der Datenstruktur, die sich auf die Leistung auswirkt, wenn Sie Tausende Male pro Frame iterieren. Folgen Sie dem MSDN-Leitfaden zu Datenstrukturen in C# als allgemeine Anleitung für die Auswahl der richtigen Struktur.

Poolen Sie Ihre Objekte
Instantiat und Zerstörung können Spitzen der Müllabfuhr (GC) erzeugen. Dies ist in der Regel ein langsamer Prozess, also anstatt GameObjects regelmäßig zu instanziieren und zu zerstören (z. B. Kugeln aus einer Waffe zu schießen), verwenden Sie Pools von vorab zugewiesenen Objekten, die wiederverwendet und recycelt werden können.
Erstellen Sie die wiederverwendbaren Instanzen an einem Punkt im Spiel, beispielsweise während eines Menübildschirms oder eines Ladebildschirms, wenn ein CPU-Spitzenwert weniger auffällig ist. Verfolgen Sie diesen Objektpool mit einer Sammlung. Aktivieren Sie während des Gameplays bei Bedarf einfach die nächste verfügbare Instanz und deaktivieren Sie Objekte, anstatt sie zu zerstören, bevor Sie sie zum Pool zurückgeben. Dies reduziert die Anzahl der verwalteten Zuordnungen in Ihrem Projekt und kann GC Probleme vermeiden.
Vermeiden Sie das Hinzufügen von Komponenten während der Laufzeit. Der Aufruf von AddComponent ist mit einigen Kosten verbunden. Unity muss bei jedem Hinzufügen von Komponenten zur Laufzeit auf Duplikate oder andere erforderliche Komponenten prüfen. Die Instanziierung eines Prefabs mit den gewünschten Komponenten ist leistungsfähiger, also verwenden Sie dies in Kombination mit Ihrem Objektpool.
Verwenden Sie beim Verschieben von Transformationen Transform.SetPositionAndRotation, um Position und Rotation gleichzeitig zu aktualisieren. Dadurch wird der Aufwand vermieden, eine Transformation zweimal zu ändern.
Wenn Sie ein GameObject zur Laufzeit instanziieren, parentieren und zur Optimierung neu positionieren müssen, siehe unten.
Weitere Informationen zu Object.Instantiate finden Sie in der Scripting API.
Hier erfahren Sie, wie Sie ein einfaches Objektpooling-System in Unity erstellen.

Nutzen Sie die Leistungsfähigkeit von ScriptableObjects
Speichern Sie unveränderte Werte oder Einstellungen in einem ScriptableObject anstelle von MonoBehaviour. Das ScriptableObject ist ein Asset, das sich innerhalb des Projekts befindet. Es muss nur einmal eingerichtet werden und kann nicht direkt an ein GameObject angehängt werden.
Erstellen Sie Felder im ScriptableObject, um Ihre Werte oder Einstellungen zu speichern, und referenzieren Sie dann das ScriptableObject in Ihren MonoBehaviours. Durch die Verwendung von Feldern aus dem ScriptableObject kann verhindert werden, dass jedes Mal, wenn Sie ein Objekt mit diesem MonoBehaviour instanziieren, unnötig viele Daten dupliziert werden.
Sehen Sie sich diese Einführung in ScriptableObjects an und finden Sie hier die entsprechende Dokumentation.

Einer unserer umfassendsten Leitfäden überhaupt enthält über 80 praktische Tipps zur Optimierung Ihrer Spiele für PC und Konsole. Diese ausführlichen Tipps wurden von unseren erfahrenen Erfolgs- und Accelerate Solutions-Technikern erstellt und helfen Ihnen, das Beste aus Unity herauszuholen und die Leistung Ihres Spiels zu steigern.