Wie wir unser Themenkrankenhaus für verschiedene Plattformen optimiert haben

Bild

Project Hospital ist ein Spiel über die Verwaltung eines Krankenhausgebäudes mit allen Standardaspekten des Genres: vom Spieler erstellte dynamische Szenen, viele aktive Charaktere und Objekte, die vom UI-System bereitgestellt werden. Damit das Spiel auf verschiedenen Geräten funktioniert, mussten wir viele Anstrengungen unternehmen. Dies war ein großartiges Beispiel für den berüchtigten „Tod durch tausend Schnitte“ - viele kleine Schritte, die eine Reihe sehr spezifischer Probleme lösen und viel Zeit für die Profilerstellung aufwenden.

Leistungsniveau: Was wir erreichen wollten


In einem frühen Entwicklungsstadium haben wir uns für die Hauptparameter entschieden: die maximale Größe der Szenen, das Leistungsniveau und die Systemanforderungen.

Wir haben uns die Aufgabe gestellt, mindestens hundert aktive und vollständig animierte Charaktere auf einem Bildschirm, insgesamt dreihundert aktive Charaktere, Kachelkarten mit einer Größe von ca. 100 x 100 und bis zu vier Stockwerken im Gebäude zu unterstützen.

Wir waren fest davon überzeugt, dass das Spiel auch auf integrierten Grafikkarten in 1080p mit einer anständigen Bildrate funktionieren sollte, und an sich war dieses Ziel nicht so schwer zu erreichen: Der Hauptbeschränkungsfaktor ist die CPU, insbesondere bei einer Zunahme des Krankenhausvolumens. Moderne integrierte Grafikkarten treten erst bei Auflösungen von ca. 2560 x 1440 auf.

Um die Unterstützung von Mods zu vereinfachen, wurden die meisten Daten geöffnet, dh wir mussten auf die Leistung verzichten, die durch das Packen der Dateien erzielt wurde. Dies hatte jedoch bis auf eine etwas längere Downloadzeit keine besonders starken Auswirkungen.

Grafik


Project Hospital ist ein „klassisches“ isometrisches 2D-Spiel, sodass Sie verstehen können, dass alles von hinten nach vorne gezeichnet wird. In Unity werden dazu die entsprechenden Werte entlang der Z-Achse (oder des Abstands zur Kamera) für einzelne Grafikobjekte festgelegt. Wenn möglich, sind Objekte, die nicht miteinander interagieren, in Schichten angeordnet. Beispielsweise sind die Stockwerke unabhängig von Objekten und Charakteren.


Die gesamte Geometrie in einer isometrisch gerenderten Szene wird dynamisch in C # erstellt. Einer der beiden wichtigsten Aspekte für die Grafikleistung ist daher die Häufigkeit der Neuerstellung der Geometrie. Der zweite Aspekt ist die Anzahl der Draw Calls.

Anrufe zeichnen


Die Anzahl der einzelnen Objekte, die unabhängig von ihrer Einfachheit in einem Frame gezeichnet werden, ist die Hauptbeschränkung, insbesondere bei schlechten Geräten (außerdem erhöht die Unity-Engine selbst den übermäßigen Ressourcenverbrauch). Die naheliegende Lösung besteht darin, so viele Grafikobjekte wie möglich in einem Zeichnungsaufruf zu gruppieren (zu stapeln). So können Sie einige interessante Ergebnisse erzielen, z. B. Objekte gruppieren, die sich im gleichen Abstand von der Kamera befinden, sodass der Rest der Grafiken korrekt hinter oder vor ihnen gerendert wird.


Hier einige Zahlen: Auf einer 96 x 96-Kachel können Sie theoretisch 9216 Objekte platzieren, für die 9216 Zeichenaufrufe erforderlich wären. Nach dem Stapeln sinkt diese Zahl auf 192.

Im wirklichen Leben ist jedoch alles etwas komplizierter, da Sie nur Objekte mit derselben Textur gruppieren können, was die Ergebnisse etwas weniger optimal macht, aber das System funktioniert immer noch recht gut.


Die meisten Chargen werden manuell durchgeführt, um die Kontrolle über die Ergebnisse zu haben. Darüber hinaus verwenden wir als letzten Ausweg auch das dynamische Batching von Unity. Dies ist jedoch ein zweischneidiges Schwert. Es hilft zwar, die Anzahl der Draw-Aufrufe zu verringern, führt jedoch zu unnötigen Ressourcen in jedem Frame und kann in einigen Fällen unvorhersehbar sein. Beispielsweise können zwei überlagerte Sprites im gleichen Abstand von der Kamera in unterschiedlichen Frames in einer unterschiedlichen Reihenfolge gerendert werden, was zu Flackern führt, das beim manuellen Stapeln nicht auftritt.

Mehrstöckig


Spieler können Gebäude mit mehreren Stockwerken bauen, was die Komplexität erhöht, aber überraschenderweise die Leistung verbessert. Nur Charaktere auf der aktiven Etage und auf der Straße müssen gerendert und animiert werden, und alles auf den anderen Etagen des Krankenhauses kann ausgeblendet werden.

Shader


Das Projektkrankenhaus verwendet relativ einfache selbstgeschriebene Shader mit kleinen Tricks wie Farbwechsel. Angenommen, ein Zeichen-Shader kann bis zu fünf Farben ersetzen (abhängig von den Bedingungen im Shader-Code) und ist daher recht teuer. Dies scheint jedoch keine Probleme zu verursachen, da Zeichen selten viel Platz auf dem Bildschirm einnehmen. Der Shader begründete den Aufwand, da die Möglichkeit, unendlich viele Kleidungsfarben zu verwenden, die Variabilität der Charaktere und der Umgebung erheblich erhöhen kann.

Außerdem haben wir schnell genug gelernt, die Angabe von Shader-Parametern zu vermeiden, und stattdessen, wann immer möglich, Scheitelpunktfarben verwendet.

Texturqualität


Eine interessante Tatsache - in Project Hospital verwenden wir keine Texturkomprimierung: Die Grafiken werden in einem Vektorstil erstellt, und bei einigen Texturen sieht die Komprimierung sehr schlecht aus.

Um CPU-Speicher in Systemen mit weniger als 1 GB zu sparen, reduzieren wir die Größe von Texturen im Spiel automatisch auf die halbe Auflösung (mit Ausnahme von Texturen auf der Benutzeroberfläche). Dies lässt sich anhand des Parameters „Texturqualität: niedrig“ in den Optionen verstehen. UI-Texturen behalten ihre ursprüngliche Auflösung bei.

Optimieren Sie die CPU-Leistung - Multithreading


Obwohl die Unity-Skriptlogik im Wesentlichen Single-Threaded ist, können wir immer mehrere Threads direkt in C # ausführen. Möglicherweise ist dieser Ansatz nicht für die Spielelogik geeignet, aber häufig gibt es zeitkritische Aufgaben, die in separaten Threads ausgeführt werden können, indem ein Aufgabensystem organisiert wird. In unserem Fall wurden Threads für zwei Funktionen verwendet:

1. Die Suche nach einem Pfad, insbesondere auf großen Karten mit einer verwirrenden Anordnung, kann bis zu Hunderten von Millisekunden dauern. Dies war also der Hauptkandidat für die Übertragung vom Hauptstrom. Parallele Aufgaben berücksichtigen die Anzahl der Hardware-Threads einer Maschine.

2. Beleuchtungskarten können auch in einem separaten Stream aktualisiert werden, jedoch jeweils nur in einer Etage - dies ist kein kritisches System, und automatische Lampen in den Räumen gehen mit einer solchen Geschwindigkeit aus, dass eine seltene Aktualisierung ausreicht.

Animationen


Fast zu Beginn der Entwicklung haben wir uns für ein zweidimensionales Skelettanimationssystem entschieden. Nachdem wir verschiedene moderne Animationsprogramme studiert hatten, beschlossen wir schließlich, ein einfaches System, das ich vor einigen Jahren erstellt hatte (im Wesentlichen als Hobbyprojekt), zu modifizieren und es an die Bedürfnisse des Projektkrankenhauses anzupassen - es ähnelt einer vereinfachten Wirbelsäule mit direkter Unterstützung für die Erstellung von Charaktervariationen. Wie bei Spine wird die C # -Laufzeit verwendet, die offensichtlich teurer als der native Code ist. Daher haben wir während des Entwicklungsprozesses einige Optimierungszyklen durchgeführt. Glücklicherweise sind unsere Rigs recht einfach, nur etwa 20 Knochen pro Charakter.

Eine merkwürdige Tatsache: Die nützlichste Verbesserung bei der Optimierung des Zugriffs auf die Transformation einzelner Knochen war der Übergang von der Kartensuche zur einfachen Indizierung von Arrays.


Neben der Tatsache, dass die Zeichen nicht außerhalb der Kamera animiert werden, gibt es noch einen weiteren Trick: Die hinter den Fenstern der Hauptbenutzeroberfläche versteckten Zeichen müssen auch nicht animiert werden. Leider haben wir in der endgültigen Version des Spiels auf eine durchscheinende Benutzeroberfläche umgestellt, sodass wir sie nicht verwenden konnten.

Caching


Wenn möglich, versuchen wir, die teuersten Berechnungen nur mit Änderungen durchzuführen, die sich auf deren Werte auswirken. Das beste Beispiel hierfür sind Räume und Aufzüge: Wenn ein Spieler einen Aufzug platziert oder Wände baut, führen wir einen Füllalgorithmus aus, der Kacheln markiert, von denen Aufzüge und Räume verfügbar sind. Dies beschleunigt die nachfolgende Suche nach Pfaden und kann verwendet werden, um dem Spieler anzuzeigen, welche Räume noch nicht verfügbar sind.

Verstreute und verzögerte Updates


In einigen Fällen ist es logisch, bestimmte Aktualisierungen nur teilweise durchzuführen. Hier einige Beispiele:

Einige Aktualisierungen können in jedem Frame nur für einen Teil der Zeichen durchgeführt werden. Beispielsweise werden die Verhaltensskripte der Hälfte der Patienten nur in ungeraden Frames und in der zweiten Hälfte in geraden Frames aktualisiert (obwohl Animationen und Bewegungen reibungslos ausgeführt werden).

Unter bestimmten Bedingungen, insbesondere wenn sich Zeichen im Standby-Modus befinden, aber teure Teile des Codes aufrufen (z. B. Mitarbeiter, die prüfen, was gefüllt werden muss, und nach nicht besetzten Geräten suchen), werden Vorgänge nur in bestimmten Intervallen ausgeführt, z. B. einmal pro Sekunde.

Eine der teuersten und gleichzeitig häufigsten Herausforderungen besteht darin, zu überprüfen, welche Tests für jeden Patienten verfügbar sind. Gleichzeitig müssen viele Faktoren bewertet werden - zum Beispiel, welches Personal der Abteilung derzeit beschäftigt ist und welche Ausrüstung reserviert ist. Darüber hinaus sind diese Informationen nicht allen Patienten gemeinsam, da sie beispielsweise von dem ihnen zugewiesenen Arzt und ihrer Sprechfähigkeit beeinflusst werden. Es ist notwendig, Dutzende verfügbarer Analysetypen zu überprüfen. Daher wird die Aktualisierung in einem Frame nur für einige durchgeführt und im nächsten fortgesetzt.


Fazit


Die Optimierung eines Spielmanagers mit vielen interagierenden Teilen hat sich als langwieriger Prozess erwiesen. Ich musste regelmäßig mit dem Unity-Profiler arbeiten und die offensichtlichsten Probleme beheben. Dies ist ein wesentlicher Bestandteil des Entwicklungsprozesses geworden.

Natürlich gibt es immer Raum für Verbesserungen, aber wir sind sehr zufrieden mit den Ergebnissen. Das Spiel bewältigt unsere Aufgaben und die Spieler erstellen ständig Mods dafür, die das ursprüngliche Limit für die Anzahl der Charaktere deutlich überschreiten.

Es ist auch erwähnenswert, dass ich selbst im Vergleich zu einigen AAA-Spielen, an denen ich gearbeitet habe, im Project Hospital die komplexeste Spielelogik in meiner Praxis getroffen habe, weshalb viele der Probleme spezifisch für dieses Projekt waren. Trotzdem empfehle ich, in jedem Projekt genügend Zeit für die Optimierung entsprechend der Komplexität des Spiels zu lassen.

Source: https://habr.com/ru/post/de459256/


All Articles