Diese Frage ist nur, um zu erfahren, wie ein Spiel mit so vielen Charakteren gleichzeitig umgehen kann. Ich bin neu im Spiel, deshalb bitte ich Sie im Voraus um Verzeihung.
Beispiel
Ich erstelle ein Tower Defense-Spiel, in dem es 15 Tower Slots gibt, in denen Türme gebaut werden und jeder Tower Geschosse mit einer bestimmten Geschwindigkeit auswirft. Nehmen wir an, dass jede Sekunde 2 Projektile von jedem der Türme erzeugt werden und es Feinde gibt, die auf dem Schlachtfeld marschieren, sagen wir 70 (jede mit 10 Arten von Attributen wie HP, Mana usw.), die sich ändern, wenn sie sich auf dem Schlachtfeld bewegen Schlachtfeld).
Zusammenfassung
Turmzahl = 15
Geschosse pro Turm pro Sekunde = 2
Gesamtzahl der Geschosse pro Sekunde = 30
Einheiten in der Schlachtfeldzahl = 70
Bewältigt das Spiel diese 30 Projektile und 70 Einheiten, indem es sie auf 100 verschiedenen Threads (was für einen PC zu viel ist) oder 1 Thread, der sie alle bewegt, ihren Wert verringert usw. (was etwas langsam sein wird) handhabt , Ich glaube)?
Ich habe keine Ahnung davon. Kann mich jemand anleiten, wie das funktionieren wird?
quelle
Antworten:
Nein, mach das nie. Erstellen Sie niemals einen neuen Thread pro Ressource. Dies skaliert weder im Netzwerk noch bei der Aktualisierung von Entitäten. (Erinnert sich jemand an die Zeiten, als Sie einen Thread zum Lesen pro Socket in Java hatten?)
Ja, für den Anfang ist dies der richtige Weg. Die "großen Motoren" teilen einige Aufgaben zwischen den Threads auf, aber dies ist nicht erforderlich, um ein einfaches Spiel wie ein Tower-Defense-Spiel zu starten. Wahrscheinlich gibt es für jeden Tick noch mehr Arbeit, die Sie auch in diesem einen Thread erledigen werden. Oh ja, und das Rendering natürlich.
Nun ... Was ist Ihre Definition von langsam ? Für 100 Entities sollte es nicht länger als eine halbe Millisekunde dauern, wahrscheinlich sogar weniger, abhängig von Ihrer Codequalität und der Sprache, mit der Sie arbeiten. Und selbst wenn es zwei volle Millisekunden dauert, ist es immer noch gut genug, um die 60 tps zu erreichen (Ticks pro Sekunde, in diesem Fall handelt es sich nicht um Frames).
quelle
Die erste Regel für Multithreading lautet: Verwenden Sie sie nur, wenn Sie für die Leistung oder Reaktionsfähigkeit mehrere CPU-Kerne parallelisieren müssen . Die Anforderung "x und y sollten aus Benutzersicht gleichzeitig auftreten" ist noch kein ausreichender Grund für die Verwendung von Multithreading.
Warum?
Multithreading ist schwierig. Sie haben keine Kontrolle darüber, wann jeder Thread ausgeführt wird, was dazu führen kann, dass Probleme jeglicher Art nicht reproduzierbar sind ("Race Conditions"). Es gibt Methoden, um dies zu vermeiden (Synchronisationssperren, kritische Abschnitte), aber diese haben ihre eigenen Probleme ("Deadlocks").
Gewöhnlich verarbeiten Spiele, die mit einer so geringen Anzahl von Objekten wie nur ein paar Hundert (ja, das ist in der Spieleentwicklung nicht so viel) zu tun haben , diese normalerweise seriell, wobei jeder Logik-Tick eine gemeinsame
for
Schleife verwendet.Selbst die relativ schwächeren Smartphone-CPUs können Milliarden von Anweisungen pro Sekunde ausführen . Das heißt, auch wenn die Aktualisierungslogik Ihrer Objekte komplex ist und etwa 1000 Anweisungen pro Objekt und Tick benötigt und Sie großzügige 100 Ticks pro Sekunde anstreben, haben Sie genügend CPU-Kapazität für Zehntausende von Objekten. Ja, dies ist eine stark vereinfachte Back-of-the-Envelope-Berechnung, aber sie gibt Ihnen eine Idee.
Es ist auch allgemein bekannt, dass die Spielelogik sehr selten der Engpass eines Spiels ist. Der leistungskritische Teil ist fast immer die Grafik. Ja, auch für 2D-Spiele.
quelle
Die anderen Antworten haben sich mit dem Threading und der Leistung moderner Computer befasst. Um die größere Frage zu beantworten, vermeiden Sie hier "n squared" Situationen.
Wenn Sie zum Beispiel 1000 Geschosse und 1000 Feinde haben, besteht die naive Lösung darin, sie alle gegeneinander zu prüfen.
Dies bedeutet, dass Sie am Ende p * e = 1.000 * 1.000 = 1.000.000 verschiedene Schecks haben! Das ist O (n ^ 2).
Auf der anderen Seite können Sie vieles vermeiden, wenn Sie Ihre Daten besser organisieren.
Wenn Sie beispielsweise auf jedem Feld des Gitters angeben, welche Feinde sich auf diesem Feld befinden, können Sie Ihre 1000 Projektile durchlaufen und das Feld auf dem Gitter überprüfen. Jetzt müssen Sie nur noch jedes Projektil gegen das Quadrat prüfen, dies ist O (n). Anstelle von einer Million Schecks pro Frame benötigen Sie nur tausend.
Die größte Einzeloptimierung, die Sie jemals vornehmen können, besteht darin, Ihre Daten zu organisieren und sie aufgrund dieser Organisation effizient zu verarbeiten.
quelle
Erstellen Sie keine Threads pro Ressource / Objekt, sondern pro Abschnitt Ihrer Programmlogik. Beispielsweise:
Dies hat den Vorteil, dass Ihre GUI (z. B. Schaltflächen) nicht unbedingt hängen bleibt, wenn Ihre Logik langsam ist. Der Benutzer kann das Spiel trotzdem anhalten und speichern. Es ist auch gut, um Ihr Spiel für den Mehrspielermodus vorzubereiten, da Sie jetzt die Grafik von der Logik trennen.
quelle
Sogar Space Invaders verwaltete Dutzende interagierender Objekte. Während das Dekodieren eines HD-H264-Videoframes Hunderte von Millionen arithmetischer Operationen umfasst. Sie haben viel Rechenleistung zur Verfügung.
Das heißt, Sie können es immer noch langsam machen, wenn Sie es verschwenden. Das Problem ist weniger die Anzahl der Objekte als vielmehr die Anzahl der durchgeführten Kollisionstests. Die einfache Methode, jedes Objekt mit dem anderen zu vergleichen, quadriert die Anzahl der erforderlichen Berechnungen. Das Testen von 1001 Objekten auf Kollisionen auf diese Weise würde eine Million Vergleiche erfordern. Oft wird dies dadurch behoben, dass zB keine Projektile auf Kollision miteinander geprüft werden.
quelle
Ich werde mit einigen der anderen Antworten hier nicht einverstanden sein. Separate Logikthreads sind nicht nur eine gute Idee, sondern auch von großem Vorteil für die Verarbeitungsgeschwindigkeit - wenn Ihre Logik leicht trennbar ist .
Ihre Frage ist ein gutes Beispiel für eine Logik, die sich wahrscheinlich trennen lässt, wenn Sie eine zusätzliche Logik hinzufügen können. Sie können beispielsweise mehrere Threads zur Treffererkennung ausführen, indem Sie die Threads auf bestimmte Bereiche des Speicherplatzes beschränken oder die betroffenen Objekte mutexen.
Sie möchten wahrscheinlich NICHT einen Thread für jede mögliche Kollision, nur weil dies wahrscheinlich den Scheduler blockiert. Das Erstellen und Zerstören von Threads ist ebenfalls mit Kosten verbunden. Besser ist es, eine bestimmte Anzahl von Threads um die Systemkerne zu erstellen (oder eine Metrik wie die alte zu verwenden
#cores * 2 + 4
), und sie dann wieder zu verwenden, wenn der Prozess abgeschlossen ist.Allerdings ist nicht jede Logik leicht zu trennen. Manchmal können Ihre Vorgänge alle Spieldaten auf einmal erfassen, was das Threading unbrauchbar macht (tatsächlich schädlich, da Sie Prüfungen hinzufügen müssten, um Threading-Probleme zu vermeiden). Wenn mehrere Stufen der Logik in bestimmten Reihenfolgen stark voneinander abhängig sind, müssen Sie die Ausführung von Threads so steuern, dass sichergestellt ist, dass keine von der Reihenfolge abhängigen Ergebnisse erzielt werden. Dieses Problem wird jedoch nicht behoben, indem keine Threads verwendet werden. Threads verschlimmern es lediglich.
Die meisten Spiele tun dies nicht einfach, weil es komplexer ist, als der durchschnittliche Spieleentwickler bereit / in der Lage ist, etwas zu tun, was normalerweise gar nicht der Engpass ist. Die überwiegende Mehrheit der Spiele ist nicht auf die CPU beschränkt, sondern auf die GPU. Eine Verbesserung der CPU-Geschwindigkeit kann zwar insgesamt hilfreich sein, ist jedoch normalerweise nicht der Schwerpunkt.
Das heißt, Physik-Engines verwenden oft mehrere Threads, und ich kann mehrere Spiele nennen, von denen ich glaube, dass sie von mehreren logischen Threads profitiert hätten (zum Beispiel die Paradox RTS-Spiele wie HOI3 und solche).
Ich stimme anderen Posts zu, dass Sie in diesem speziellen Beispiel wahrscheinlich keine Threads verwenden müssten, auch wenn dies von Vorteil sein könnte. Das Threading sollte für Fälle reserviert werden, in denen Sie eine übermäßige CPU-Auslastung haben, die mit anderen Methoden nicht optimiert werden kann. Es ist ein riesiges Unterfangen und wird die Grundstruktur eines Motors beeinflussen; Es ist nichts, woran man nachträglich festhalten kann.
quelle
Ich denke, dass die anderen Antworten einen wichtigen Teil der Frage verfehlen, indem sie sich zu sehr auf den Threading-Teil der Frage konzentrieren.
Ein Computer verarbeitet nicht alle Objekte in einem Spiel auf einmal. Es behandelt sie nacheinander.
Ein Computerspiel schreitet in diskreten Zeitschritten voran. Abhängig vom Spiel und der Geschwindigkeit des PCs betragen diese Schritte normalerweise entweder 30 oder 60 Schritte pro Sekunde oder so viele / wenige Schritte, wie der PC berechnen kann.
In einem solchen Schritt berechnet ein Computer, was jedes der Spielobjekte während dieses Schritts tun wird, und aktualisiert sie nacheinander entsprechend. Dies könnte sogar parallel geschehen, indem Threads verwendet werden, um schneller zu sein, aber wie wir gleich sehen werden, ist Geschwindigkeit überhaupt kein Problem.
Eine durchschnittliche CPU sollte 2 GHz oder schneller sein, dh 10 9 Taktzyklen pro Sekunde. Wenn wir berechnen 60 Zeitschritte pro Sekunde, die Blätter 10 9 /60 Taktzyklen = 16.666.666 Taktzyklen pro Zeitschritt. Mit 70 Einheiten haben wir noch etwa 2.400.000 Taktzyklen pro Einheit übrig. Wenn wir optimieren müssten, könnten wir in der Lage sein, jede Einheit in nur 240 Zyklen zu aktualisieren, abhängig von der Komplexität der Spiellogik. Wie Sie sehen, ist unser Computer etwa 10.000-mal schneller als für diese Aufgabe erforderlich.
quelle
Haftungsausschluss: Mein Lieblingsspiel aller Zeiten ist textbasiert und ich schreibe dies als langjähriger Programmierer eines alten MUD.
Ich denke, eine wichtige Frage, die Sie sich stellen müssen, ist folgende: Brauchen Sie überhaupt Fäden? Ich verstehe, dass ein Grafikspiel wahrscheinlich mehr MTs verwendet, aber ich denke, es hängt auch von der Spielmechanik ab. (Es ist auch zu bedenken, dass GPUs, CPUs und alle anderen Ressourcen, die wir heute haben, weitaus leistungsstärker sind, was Ihre Bedenken in Bezug auf Ressourcen so problematisch macht, wie es Ihnen erscheinen mag. Tatsächlich sind 100 Objekte praktisch null.) Es hängt auch davon ab, wie Sie "alle Zeichen auf einmal" definieren. Meinen Sie genau zur gleichen Zeit? Sie werden das nicht haben, wie Peter zu Recht betont, also ist alles auf einmal im wörtlichen Sinne irrelevant; es erscheint nur so.
Vorausgesetzt, Sie gehen mit Threads: Sie sollten auf keinen Fall 100 Threads in Betracht ziehen (und ich werde nicht einmal darauf eingehen, ob es zu viel für Ihre CPU ist oder nicht; ich beziehe mich nur auf die Komplikationen und die Praktikabilität davon).
Aber denken Sie daran: Multithreading ist nicht einfach (wie Philipp betont) und hat viele Probleme. Andere haben viel mehr Erfahrung (viel mehr) als ich mit MT, aber ich würde sagen, dass auch sie dasselbe vorschlagen würden (obwohl sie fähiger wären als ich - besonders ohne Übung von meiner Seite).
Einige argumentieren, dass sie nicht einverstanden sind, dass Threads nicht vorteilhaft sind, und andere argumentieren, dass jedes Objekt einen Thread haben sollte. Aber (und wieder ist dies alles Text, aber selbst wenn Sie mehr als einen Thread betrachten, müssen und sollten Sie ihn nicht für jedes Objekt berücksichtigen), wie Philipp darauf hinweist, dass Spiele dazu neigen, durch die Listen zu iterieren. Aber es ist nicht nur (wie er andeutet, obwohl mir klar ist, dass er nur auf Ihre Parameter von so wenigen Objekten reagiert) für so wenige Objekte. Im MUD bin ich ein Programmierer, denn wir haben Folgendes (und dies ist nicht alles, was in Echtzeit geschieht, denken Sie also auch daran):
(Die Anzahl der Instanzen variiert natürlich - höher und niedriger)
Mobiles (NPC, dh Nicht-Spieler-Charakter): 2614; Prototypen: 1360 Objekte: 4457; prototypen: 2281 zimmer: 7983; Prototypen: 7983. Jeder Raum hat normalerweise seine eigene Instanz, aber wir haben auch dynamische Räume, dh Räume innerhalb eines Raumes; oder Räume in einem Handy, zB der Magen eines Drachen; oder Räume in Objekten (zB wenn Sie ein magisches Objekt betreten). Beachten Sie, dass diese dynamischen Räume pro Objekt / Raum / Mobiltelefon vorhanden sind, für das sie tatsächlich definiert wurden. Ja, dies ähnelt sehr der Idee von World of Warcraft (ich spiele es nicht, aber ein Freund ließ mich es spielen, als ich eine Weile einen Windows-Computer hatte), mit der Ausnahme, dass wir es hatten, lange bevor es World of Warcraft überhaupt gab.
Skripte: 868 (derzeit) (seltsamerweise zeigt unser Statistik-Befehl nicht an, wie viele Prototypen wir haben, also werde ich das hinzufügen). Alle diese Veranstaltungen finden in Gebieten / Zonen statt, von denen wir 103 haben. Wir haben auch spezielle Verfahren, die zu unterschiedlichen Zeiten ablaufen. Wir haben auch andere Veranstaltungen. Dann haben wir auch Steckdosen angeschlossen. Handys bewegen sich, üben verschiedene Aktivitäten aus (außer Kämpfen), haben Interaktionen mit Spielern und so weiter. (So auch andere Arten von Entitäten).
Wie gehen wir ohne Verzögerung damit um?
Sockets: select (), Warteschlangen (Eingabe, Ausgabe, Ereignisse usw.), Puffer (Eingabe, Ausgabe usw.) usw. Diese werden 10-mal pro Sekunde abgefragt.
Charaktere, Objekte, Räume, Kämpfe, alles: alles in einer zentralen Schleife mit verschiedenen Impulsen.
Wir haben auch (meine Implementierung basiert auf einer Diskussion zwischen dem Gründer / anderen Programmierer und mir) umfangreiche Nachverfolgungs- und Zeigergültigkeitstests für verknüpfte Listen und wir haben mehr als genug freie Ressourcen, falls wir tatsächlich einen Bedarf dafür haben. All dies (mit der Ausnahme, dass wir die Welt erweitert haben) gab es vor Jahren, als es weniger RAM, CPU-Leistung, Festplattenspeicher usw. gab. Und selbst dann hatten wir keine Probleme. In den beschriebenen Loops (Skripte verursachen dies, ebenso wie das Zurücksetzen / Wiederauffüllen von Bereichen und andere Dinge) werden Monster, Objekte (Gegenstände) und andere Dinge erstellt, befreit und so weiter. Verbindungen werden ebenfalls akzeptiert, abgefragt und alles andere, was Sie erwarten würden.
quelle