Ich bin auf diese Frage gestoßen, als ich ein Videospiel in C # entwarf.
Wenn wir Spiele wie Battlefield oder Call of Duty in Betracht ziehen , fliegen Hunderte oder sogar Tausende von Kugeln gleichzeitig. Ereignisse werden ständig ausgelöst und, soweit ich weiß, verbraucht dies viel Rechenleistung ... oder doch? Ich möchte wissen, wie verschiedene Spieleentwickler Aufzählungszeichen (2D und 3D) verwalten und welche Methode jeweils am effizientesten ist.
Ich habe die Frage gelesen, wie Kugeln in Videospielen simuliert werden. Aber es geht nicht darum, wie Aufzählungszeichen aus der Sicht des Programmdesigns funktionieren.
Ich hatte ein paar Ideen, aber jede hat ihre Nachteile:
Die effizienteste Methode, die ich mir vorstellen kann (für 2D-Spiele):
Angenommen, ich würde eine Klasse namens Bullet erstellen, und so lange der Benutzer eine Schaltfläche gedrückt hält, würde alle 0,01 Sekunden ein Bullet-Objekt erstellt. Diese Kugel hat:
1 Geschwindigkeit
2 Startposition, von der aus aufgenommen wird
3 Sprite-Textur
4 Ein On-Hit-Effekt
Da die Kugel eine eigene Klasse sein würde, könnte sie das Zeichnen, Bewegen und Zuhören von Aktionen selbst verwalten.
Wäre es nicht schwierig für den Prozessor, Tausende dieser Objekte zu verarbeiten, die instanziiert und dann zerstört werden (wenn ein On-Hit-Effekt ausgelöst wird)? RAM-Speicher?
Effiziente Methode für 3D-Spiele - Ein weiterer Gedanke, den ich hatte, war:
Sagen wir, ich erstelle eine Waffenklasse. Diese Waffe hat verschiedene Eigenschaften, von denen einige:
1 Ermitteln Sie, wohin die Waffe zielt, und bestimmen Sie, ob sie auf ein Ziel gerichtet ist
2 Lösen Sie eine Animation des Waffenschießens aus
3 Verfügt über eine doDamage () -Methode, die angibt, auf was auch immer die Waffe gerichtet ist, um die Gesundheit zu subtrahieren
4 Benachrichtigt eine Aufzählungszeichen-Animationsklasse, wenn die Schaltfläche gedrückt wird
Ich könnte dann eine statische Klasse, z. B. BulletAnimation, erstellen, die eine Benachrichtigung darüber erhält, wo sich die Waffe befindet, auf die sie gerichtet ist (für das Ziel der Kugel), und Informationen zu einem geeigneten Sprite und einer Geschwindigkeit, die für die Kugel verwendet werden können . Diese Klasse zeichnet dann Sprites (auf einem neuen Thread, vielleicht idk) basierend auf beiden Positionen und dem gewünschten Sprite, um eine Kugel zu simulieren, die von einer Waffe abgefeuert wird.
Letzteres scheint viel schwieriger zu programmieren zu sein, und würde es nicht eine Menge Rechenleistung erfordern, die Statik ständig aufzurufen, um dies für Tausende von Aufzählungszeichen gleichzeitig zu tun? Ständige Aktualisierungen von Start- und Endpositionen wären ebenfalls schwierig.
Meine Frage ist, wie Spieleentwickler es am effizientesten machen. Wechselt diese Methode von 2D- zu 3D-Spielen?
pew-pew-pew
Technologie :)Antworten:
Ich kann mit Sicherheit nachvollziehen, warum Sie der Meinung sind, dass es schwierig ist, diese zu simulieren, aber es gibt genug Einschränkungen für Kugeln (eigentlich alle Projektile), um sie einfacher zu machen.
Sie werden im Allgemeinen als ein einzelner Punkt simuliert, anstatt als etwas mit Volumen. Dies erleichtert die Kollisionserkennung erheblich, da ich jetzt nur noch Kollisionen mit sehr einfachen Oberflächen durchführen muss, z. B. einer Linie gegen einen Kreis.
Wir wissen, wie sie sich bewegen werden, daher brauchen wir nicht viele Informationen, um sie zu speichern oder zu berechnen. Ihre Liste war einigermaßen genau. Im Allgemeinen haben wir noch ein paar Dinge damit zu tun, z. B. wer die Kugel abgeschossen hat und welche Art sie hat.
Da alle Projektile sehr ähnlich sind, können wir sie vorab zuweisen, um den Aufwand für die dynamische Erstellung zu vermeiden. Ich kann ein Array von 1000 Projektilen zuweisen, und jetzt kann mit nur einem Index auf sie zugegriffen werden, und sie sind alle sequentiell im Speicher, sodass ihre Verarbeitung schnell vonstatten geht.
Sie haben eine feste Lebensdauer / Reichweite, sodass ich alte Aufzählungszeichen verfallen lassen und den Speicher sehr schnell in neue Aufzählungszeichen umwandeln kann.
Sobald sie etwas getroffen haben, kann ich sie auch auslaufen lassen, sodass sie eine begrenzte Lebensdauer haben.
Da wir wissen, wann sie erstellt wurden, wenn wir neue benötigen und wir keine freien in unserer vorab zugewiesenen Liste haben, kann ich einfach die ältesten nehmen und sie recyceln, und die Leute werden es nicht bemerken, wenn die Kugeln etwas früher ablaufen .
Sie werden (normalerweise) als Sprites oder als Low-Poly-Modelle gerendert und beanspruchen nur sehr wenig Platz auf dem Bildschirm, sodass sie schnell gerendert werden können.
Unter Berücksichtigung all dieser Faktoren sind Kugeln in der Regel relativ billig. Wenn unser Budget jemals durch Aufzählungszeichen aufgebraucht und gerendert wurde, haben wir es im Allgemeinen neu gestaltet, um die Anzahl der Schüsse zu begrenzen, die Sie gleichzeitig abgeben können (dies wird in vielen alten Arcade-Spielen zu sehen sein). Verwenden Sie Strahlwaffen, die sich sofort bewegen oder verlangsamen Sie die Feuerrate, um sicherzustellen, dass wir innerhalb des Budgets bleiben.
quelle
num_characters * max_bullets_per_character
Eine der effizientesten Methoden zur Implementierung von Aufzählungszeichen ist wahrscheinlich die Verwendung des so genannten Treffer-Scans . Die Implementierung ist recht einfach: Wenn Sie feuern, überprüfen Sie, auf was die Waffe abzielt (verwenden Sie möglicherweise einen Strahl, um das nächste Objekt zu finden), und treffen Sie es dann, um Schaden zu verursachen. Wenn Sie den Eindruck erwecken möchten, dass eine tatsächliche, sich schnell bewegende, unsichtbare Kugel abgefeuert wurde, können Sie sie vortäuschen, indem Sie eine von der Entfernung abhängige Verzögerung hinzufügen, bevor Sie Schaden anrichten.
Bei diesem Ansatz wird im Wesentlichen davon ausgegangen, dass das abgefeuerte Projektil eine unendliche Geschwindigkeit hat und typischerweise für Waffentypen wie Laser und Partikelstrahlen / Kanonen und möglicherweise einige Formen von Scharfschützengewehren verwendet wird .
Der nächste Ansatz wäre, die abgefeuerte Kugel als Projektil zu modellieren, das als eigene Entität / Objekt modelliert wird, das einer Kollision unterliegt, und möglicherweise Schwerkraft und / oder Luftwiderstand, die seine Richtung und Geschwindigkeit verändern. Es ist aufgrund der zusätzlichen physikalischen Gleichungen komplexer als der Treffer-Scan-Ansatz und ressourcenintensiver, da es sich um ein tatsächliches Aufzählungsobjekt handelt, kann jedoch realistischere Aufzählungszeichen liefern.
Beim Verwalten von Kollisionen zwischen Geschossen auf Projektilbasis und anderen Objekten im Spiel kann die Kollisionserkennung erheblich vereinfacht werden, indem Sie Ihre Objekte in Quad- oder Octrees sortieren . Octrees werden hauptsächlich in 3D-Spielen verwendet, während Quadtrees in 2D- oder 3D-Spielen verwendet werden können. Die Verwendung eines dieser Bäume hat den Vorteil, dass Sie die Anzahl möglicher Kollisionsprüfungen erheblich reduzieren können. Wenn Sie beispielsweise 20 Objekte in der Ebene aktiv haben, ohne einen dieser Bäume zu verwenden, müssen Sie alle 20 auf eine Kollision mit der Kugel prüfen. Teilen Sie die 20 Objekte auf die Blätter (Endknoten) des Baums auf, und reduzieren Sie die Anzahl der Überprüfungen auf die Anzahl der Entitäten, die sich im selben Blatt wie das Aufzählungszeichen befinden.
Für diese Ansätze - Trefferabtastung und Projektil - können beide in 2D- oder 3D-Spielen frei verwendet werden. Es hängt mehr davon ab, was die Waffe ist und wie der Schöpfer entschieden hat, dass die Waffe funktionieren soll.
quelle
Ich bin auf keinen Fall ein Experte, aber um Ihre Frage zu beantworten, brauchen Sie viele der Dinge, die Sie erwähnen.
Für Ihr 2D-Beispiel könnten Sie eine Position und Geschwindigkeit für eine Kugel haben. (Je nachdem, wie Sie Ihre Aufzählungszeichen implementieren, benötigen Sie möglicherweise auch eine Lebensdauer oder eine maximale Entfernung.) Dies würde normalerweise 2 (x, y) Werte umfassen. Wenn es sich um Floats handelt, sind das 16 Bytes. Wenn Sie 100 Kugeln haben, sind das nur 1600 Byte oder ungefähr 1,5 KB. Das ist heute nichts an einer Maschine.
Als nächstes erwähnen Sie die Sprites. Sie würden nur ein einziges Sprite benötigen, um jede Kugel darzustellen. Ihre Größe hängt von der Bittiefe ab, mit der Sie zeichnen, und davon, wie groß sie auf dem Bildschirm angezeigt werden soll. Sogar unkomprimiert mit 256x256 bei 32 Bit pro Kanal und vollem Float, das ist 1 MB für das Sprite. (Und das wäre sehr groß!) Sie würden dasselbe Sprite an jeder Aufzählungsstelle zeichnen, benötigen jedoch keinen zusätzlichen Speicher für jede Kopie des Sprites. Ähnlich wäre es bei einem On-Hit-Effekt.
Sie erwähnen, dass alle 0,01 Sekunden geschossen wird. Das wären 100 Kugeln pro Sekunde von Ihrer Waffe. Selbst für eine futuristische Waffe ist das ziemlich viel! Laut diesem Wikipedia-Artikel :
Das wäre also die Rate eines Kampfhubschraubers!
Für eine große Welt, wie Sie sie in Battlefield / Call of Duty / etc. erwähnen, können sie alle diese Geschosspositionen berechnen, aber nicht alle zeichnen, wenn die Aktion weit entfernt ist. Oder sie können sie nicht simulieren, bis Sie sich nähern. (Ich muss zugeben, dass ich in diesem Punkt ein wenig schätze, da ich an nichts so Großem gearbeitet habe.)
quelle
Ich denke, Sie unterschätzen, wie schnell Computer sind. Dies war manchmal ein Problem auf den Systemen der 80er und 90er Jahre. Es ist teilweise der Grund, warum die ursprünglichen Space Invaders nicht zulassen, dass Sie eine weitere Kugel abschießen, bis die aktuelle getroffen wurde. Einige Spiele litten unter "Verlangsamung", wenn zu viele Sprites auf dem Bildschirm waren.
Aber heutzutage? Sie verfügen über genügend Prozessorleistung für Tausende von Vorgängen pro Pixel , die zum Strukturieren und Beleuchten erforderlich sind. Es gibt kein Problem mit Tausenden von sich bewegenden Objekten. Auf diese Weise können Sie zerstörbares Gelände (z. B. Red Faction) erstellen, in dem jedes Fragment mit anderen Fragmenten kollidiert und einer ballistischen Kurve folgt.
Algorithmisch muss man ein wenig vorsichtig sein - man kann nicht naiv jedes Objekt mit jedem anderen Objekt vergleichen, wenn man Tausende von Objekten hat. Aufzählungszeichen prüfen im Allgemeinen nicht auf Kollisionen mit anderen Aufzählungszeichen.
Eine kleine Nebenanekdote: Die erste Version von Networked Doom (das Original aus den 90ern) hat für jede abgefeuerte Kugel ein Paket über das Netzwerk gesendet. Wenn ein oder mehrere Spieler das Maschinengewehr bekamen, konnte dies das Netzwerk leicht überfordern. In den 90er Jahren gab es viele Leute, die Doom in Universitäts- oder Arbeitsnetzwerken spielten und Probleme mit ihren Netzwerkadministratoren hatten, als das Netzwerk unbrauchbar wurde.
quelle
Ich bin kein Experte, arbeite aber in meiner Freizeit an einem Multiplayer-2D-Shooter-Spiel.
Meine Methode
Zwischen Client und Server gibt es unterschiedliche Aufzählungszeichenklassen (selbst wenn offline gespielt wird, wird eine Serverinstanz in einem separaten Prozess gestartet und über das Hauptspiel verbunden).
Bei jedem Tick (60 pro Sekunde) ermittelt der Client eine Peilung zwischen dem Mauszeiger des Spielers und der Mitte des Bildschirms (wo sich sein Charakter befindet) und es ist Teil der Informationen, die an den Server gesendet werden. Wenn der Spieler in diesem Moment auch feuert (vorausgesetzt, die Waffe ist geladen und bereit), wird eine serverseitige Kugelinstanz mit nur einigen Koordinaten und einem Grundschaden erstellt (der sich aus den Statistiken der Waffe ergibt, die geschossen hat) es). Die Aufzählungsinstanz verwendet dann einige mathematische Funktionen, um eine X- und Y-Geschwindigkeit aus der vom Client erfassten Peilung zu berechnen.
Bei jedem weiteren Tick bewegt sich die Kugel um diese Koordinaten und reduziert ihren Grundschaden um einen vordefinierten Betrag. Wenn dieser Wert unter 1 fällt oder auf ein festes Objekt in der Welt trifft, wird die Aufzählungszeicheninstanz gelöscht, und da das Testen von Punktkollisionen in 2D unglaublich billig ist, haben selbst schnelle Schusswaffen einen vernachlässigbaren Einfluss auf die Leistung.
Was den Client betrifft, werden die Aufzählungszeicheninformationen nicht über das Netzwerk empfangen (es hat sich beim Testen als verschwenderisch erwiesen), sondern als Teil der Aktualisierung pro Tick hat jedes Zeichen einen 'abgefeuerten' Booleschen Wert, der, wenn er wahr ist, einen lokalen Wert erstellt bullet-Objekt, das fast genau wie ein Server-Objekt funktioniert. Der einzige Unterschied besteht darin, dass es ein Sprite hat.
Dies bedeutet, dass die Kugel, die Sie sehen, auf dem Server zwar nicht ganz genau dargestellt wird, ein Unterschied für einen Spieler jedoch kaum spürbar ist und die Netzwerkvorteile etwaige Inkonsistenzen aufwiegen.
Hinweis zu verschiedenen Methoden
Einige Spiele, einschließlich meiner eigenen, bewegen die Kugeln bei jedem Tick wie physische Objekte, während andere nur einen Vektor in Schussrichtung erstellen oder den gesamten Pfad der Kugel in dem Tick berechnen, den sie erstellt hat, z. Strike-Spiele. Es gibt ein paar kleine clientseitige Tricks, um es zu verschleiern, wie eine Animation des Geschossabschusses, aber in jeder Hinsicht ist jedes Geschoss nur ein Laser .
Bei 3D-Modellen mit möglicherweise komplexen Trefferfeldern ist es Standard, Kollisionen ZUERST mit einem einfachen Begrenzungsrahmen zu testen. Gelingt dies, fahren Sie mit einer detaillierteren Kollisionserkennung fort.
quelle
Es heißt Kollisionserkennung. 8-Bit-Computer verwendeten dazu Player-Missile-Grafiken in Hardware. Moderne Spiele-Engines verwenden Physik-Engines und lineare Algebra. Die aktuelle Richtung einer Waffe wird als 3D-Vektor dargestellt. Das ergibt eine unendliche Linie in Schussrichtung. Jedes sich bewegende Objekt hat eine oder mehrere Begrenzungskugeln, da dies das einfachste Objekt ist, um eine Kollision mit einer Linie zu erkennen. Wenn sich die beiden kreuzen, ist das ein Treffer. Wenn nicht, gibt es keinen Treffer. Aber die Szenerie könnte im Weg sein, so dass auch darauf geachtet werden muss (unter Verwendung von hierarchischen Begrenzungsvolumina). Das nächste Objekt mit einer Kreuzung ist das getroffene Objekt.
quelle