Ich habe bereits nach Antworten gesucht, konnte jedoch nicht den besten Ansatz für die Handhabung teurer Funktionen / Berechnungen finden.
In meinem aktuellen Spiel (ein auf 2 Kacheln basierendes Stadtgebäude) kann der Benutzer Gebäude platzieren, Straßen bauen usw. Alle Gebäude benötigen eine Verbindung zu einer Kreuzung, die der Benutzer am Rand der Karte platzieren muss. Wenn ein Gebäude nicht mit dieser Kreuzung verbunden ist, wird über dem betroffenen Gebäude ein Schild mit der Aufschrift "Nicht mit der Straße verbunden" angezeigt (andernfalls muss es entfernt werden). Die meisten Gebäude haben einen Radius und können auch miteinander verwandt sein (z. B. kann eine Feuerwehr allen Häusern in einem Radius von 30 Kacheln helfen). Das muss ich auch aktualisieren / überprüfen, wenn sich die Straßenverbindung ändert.
Gestern bin ich auf ein großes Leistungsproblem gestoßen. Schauen wir uns das folgende Szenario an: Ein Benutzer kann natürlich auch Gebäude und Straßen löschen. Wenn ein Benutzer jetzt direkt nach der Kreuzung die Verbindung unterbricht, muss ich viele Gebäude gleichzeitig aktualisieren . Ich denke, einer der ersten Ratschläge wäre, verschachtelte Schleifen zu vermeiden (was in diesem Szenario definitiv ein wichtiger Grund ist), aber ich muss überprüfen ...
- Wenn ein Gebäude noch mit der Kreuzung verbunden ist, falls eine Straßenkachel entfernt wurde (das mache ich nur für betroffene Gebäude auf dieser Straße). (Könnte in diesem Szenario ein kleineres Problem sein)
die Liste der Radiuskacheln und erhalten Gebäude innerhalb des Radius (verschachtelte Schleifen - großes Problem!) .
// Go through all buildings affected by erasing this road tile. foreach(var affectedBuilding in affectedBuildings) { // Get buildings within radius. foreach(var radiusTile in affectedBuilding.RadiusTiles) { // Get all buildings on Map within this radius (which is technially another foreach). var buildingsInRadius = TileMap.Buildings.Where(b => b.TileIndex == radiusTile.TileIndex); // Do stuff. } }
Dies alles bricht meine FPS für eine Sekunde von 60 auf fast 10 herunter .
Könnte ich auch tun. Meine Ideen wären:
- Verwenden Sie nicht den Haupt-Thread (Update-Funktion) für diesen, sondern einen anderen Thread. Wenn ich Multithreading verwende, kann es zu Problemen beim Sperren kommen.
- Verwenden einer Warteschlange für viele Berechnungen (was wäre in diesem Fall der beste Ansatz?)
- Bewahren Sie mehr Informationen in meinen Objekten (Gebäuden) auf, um weitere Berechnungen zu vermeiden (z. B. Gebäude im Radius).
Mit dem letzten Ansatz könnte ich stattdessen eine Verschachtelung in dieser Form entfernen:
// Go through all buildings affected by erasing this road tile.
foreach(var affectedBuilding in affectedBuildings) {
// Go through buildings within radius.
foreach(var buildingInRadius in affectedBuilding.BuildingsInRadius) {
// Do stuff.
}
}
Aber ich weiß nicht, ob das reicht. Spiele wie Cities Skylines müssen viel mehr Gebäude bewältigen, wenn der Spieler eine große Karte hat. Wie gehen sie mit diesen Dingen um?! Möglicherweise gibt es eine Aktualisierungswarteschlange, da nicht alle Gebäude gleichzeitig aktualisiert werden.
Ich freue mich auf Ihre Ideen und Kommentare!
Vielen Dank!
quelle
Antworten:
Caching-Gebäudeabdeckung
Die Idee, die Informationen zwischenzuspeichern, welche Gebäude sich in Reichweite eines Effektorgebäudes befinden (die Sie entweder vom Effektor oder vom Betroffenen zwischenspeichern können), ist auf jeden Fall eine gute Idee. Gebäude bewegen sich (normalerweise) nicht, daher gibt es wenig Grund, diese teuren Berechnungen zu wiederholen. "Was wirkt sich dieses Gebäude aus?" Und "Was beeinflusst dieses Gebäude?" Müssen Sie nur überprüfen, wenn ein Gebäude erstellt oder entfernt wird.
Dies ist ein klassischer Austausch von CPU-Zyklen gegen Speicher.
Umgang mit Abdeckungsinformationen nach Region
Wenn sich herausstellt, dass Sie zu viel Speicher verwenden, um diese Informationen zu verfolgen, prüfen Sie, ob Sie diese Informationen nach Kartenregionen verarbeiten können. Teilen Sie Ihre Karte in quadratische Bereiche von
n
*n
Fliesen. Wenn eine Region vollständig von einer Feuerwehr abgedeckt ist, werden auch alle Gebäude in dieser Region abgedeckt. Sie müssen also nur Abdeckungsinformationen nach Region speichern, nicht nach einzelnen Gebäuden. Wenn eine Region nur teilweise abgedeckt ist, müssen Sie auf die Bearbeitung von Verbindungen in dieser Region zurückgreifen. Die Update-Funktion für Ihre Gebäude würde also zuerst prüfen, ob die Region, in der sich dieses Gebäude befindet, von einer Feuerwehr abgedeckt wird. und wenn nicht "Wird dieses Gebäude individuell von einer Feuerwehr abgedeckt?". Dies beschleunigt auch Aktualisierungen, da Sie beim Entfernen einer Feuerwehr die Abdeckungsstatus von 2000 Gebäuden nicht mehr aktualisieren müssen, sondern nur noch 100 Gebäude und 25 Regionen aktualisieren müssen.Verzögerte Aktualisierung
Eine andere Optimierung, die Sie durchführen können, besteht darin, nicht alles sofort und nicht alles gleichzeitig zu aktualisieren.
Ob ein Gebäude noch mit dem Straßennetz verbunden ist oder nicht, müssen Sie nicht bei jedem einzelnen Frame überprüfen (Übrigens finden Sie möglicherweise auch Möglichkeiten, dies speziell zu optimieren, indem Sie sich ein wenig mit der Graphentheorie befassen). Es wäre völlig ausreichend, wenn Gebäude dies nur alle paar Sekunden nach dem Bau des Gebäudes regelmäßig überprüfen würden (UND wenn sich das Straßennetz ändern würde). Gleiches gilt für Gebäudebereichseffekte. Es ist durchaus akzeptabel, wenn ein Gebäude nur alle paar hundert Frames überprüft. "Ist mindestens eine der mich betreffenden Feuerwehren noch aktiv?"
Sie können also Ihre Update-Schleife nur diese teuren Berechnungen für jeweils ein paar hundert Gebäude für jedes Update durchführen lassen. Möglicherweise möchten Sie Gebäuden, die derzeit auf dem Bildschirm angezeigt werden, Voreinstellungen geben, damit die Spieler sofort Feedback zu ihren Aktionen erhalten.
In Bezug auf Multithreading
Städtebauer sind in der Regel rechenintensiver, insbesondere wenn Sie den Spielern erlauben möchten, wirklich große Gebäude zu bauen, und wenn Sie eine hohe Simulationskomplexität wünschen. Auf lange Sicht ist es also möglicherweise nicht falsch, darüber nachzudenken, welche Berechnungen in Ihrem Spiel asynchron gehandhabt werden können.
quelle
1. Doppelte Arbeit .
Sie
affectedBuildings
sind vermutlich nahe beieinander, sodass sich die verschiedenen Radien überlappen. Markieren Sie die Gebäude, die aktualisiert werden müssen, und aktualisieren Sie sie dann.2. Ungeeignete Datenstrukturen.
sollte eindeutig sein
wobei Gebäude eine
IEnumerable
mit konstanter Iterationszeit ist (z. B. aList<Building>
)quelle