Wie mache ich ein Spiel ohne OOP? [geschlossen]

10

Ich studiere derzeit Spieleentwicklung und übe das Erstellen von Spielen.

Ich benutze viel OOP in meinen Spielen. Beispielsweise ist jede abgefeuerte Rakete eine Instanz eines MissileObjekts und wird einer Liste von MissileObjekten hinzugefügt . Jeder Panzer im Spiel ist ein TankObjekt. Usw.

Darauf basiert das gesamte Programmdesign. Wenn ich beispielsweise eine Liste von MissileObjekten habe, kann jeder Frame die Raketen bewegen, zeichnen usw. TankWenn ich für jeden Panzer eine Instanz eines Objekts habe, kann ich für jeden Panzer prüfen, ob er mit etwas kollidiert usw.

Es fällt mir schwer, mir vorzustellen, wie ein Spiel (das komplexer als Pac-Man ist) in einer Nicht-OO-Sprache programmiert werden könnte. (Natürlich ohne Respektlosigkeit gegenüber Nicht-OO-Programmierern). Nicht nur, wie lange es dauern wird, sondern vor allem, wie ein Spiel auf diese Weise gestaltet werden könnte.

Ich kann mir nicht vorstellen, ein Spiel ohne objektorientierte Programmierung zu entwerfen, da mein gesamtes Verständnis für das Entwerfen eines Spielprogramms auf OOP basiert.

Ich möchte fragen: Gibt es heute Spiele, die nicht mit OOP programmiert wurden, ähnlich wie oben beschrieben? Gibt es professionelle Spiele, bei denen OOP nicht als Hauptfaktor im Entwicklungsprozess verwendet wird?

Wenn ja, können Sie mir eine Vorstellung davon geben, wie beispielsweise die Kollisionserkennung zwischen einem Panzer und N Raketen ohne OOP implementiert werden könnte?

user3150201
quelle
6
Ist das eine philosophische Frage? Selbst wenn Sie Ihre Panzer nicht "Objekte" nennen, werden Sie wahrscheinlich "Entitäten", "Schauspieler", "Agenten", "Strukturen" oder nur einen anderen Namen für dieselbe Idee, die eine Sammlung von ist, wollen Attribute und Verhaltensweisen, die ein rotierendes quaderförmiges Ding mit einem Turm ausmachen, der Dinge abschießen kann, genannt Panzer. Programmiersprachen werden verschiedene Möglichkeiten haben, dieselbe Idee zu formalisieren, aber am Ende wird es ein Panzer sein.
Anko
Viele Spiele verwenden ein komponentenbasiertes System, wie in dieser Antwort beschrieben: gamedev.stackexchange.com/a/31491/9366
John McDonald
Dies ist sowohl äußerst umfassend (was Sie möglicherweise durch Eingrenzen des Umfangs beheben könnten) als auch nicht wirklich spezifisch für die Spieleentwicklung (da das Erstellen von Software ohne OO-Techniken für einen Spieleentwickler keine bessere Antwort ist als für jeden anderen Softwareentwickler). Ich fürchte, das macht es hier zum Thema.
Es ist möglicherweise für StackOverflow geeignet, oder Sie können in der Hilfe nach einer Auswahl von Websites suchen, die für die Spieleentwicklung spezifisch sind (wie GDNet) und diese Art von breitem, diskussionsorientiertem Thema ermöglichen. Viel Glück!

Antworten:

16

Ich kann mir nicht vorstellen, ein Spiel ohne objektorientierte Programmierung zu entwerfen, da mein gesamtes Verständnis für das Entwerfen eines Spielprogramms auf OOP basiert.

Dann ist es wahrscheinlich gut für Sie, einige Programme im Nicht-OO-Stil zu schreiben. Selbst wenn Sie feststellen, dass dies für Sie nicht pragmatisch ist, werden Sie wahrscheinlich viel lernen, was Ihnen in Zukunft helfen wird.

Der OO-Stil eignet sich sehr gut für Spiele, da es bei Spielen fast immer um die Manipulation zustandsbehafteter Objekte geht. Ein Laserstrahl trifft auf den Roboter und der Zustand des Roboters ändert sich, während seine Identität gleich bleibt.

Es ist jedoch möglich, Spiele in einem funktionalen Stil zu programmieren . In einem funktionalen Stil ändert sich der Zustand nicht per se. Objekte sind unveränderlich. Anstatt Objekte zu ändern, stellen Sie die Frage, wie das Universum anders wäre, wenn ich dies ändern würde. und dann ein ganz neues Universum erzeugen, das die veränderte Eigenschaft hat. Natürlich können Sie einen Großteil des zuvor existierenden Universums wiederverwenden, da es unveränderlich ist .

Bei der funktionalen Programmierung muss jede Funktion ihren Rückgabewert ausschließlich aus den übergebenen Informationen berechnen. Es gibt keine Lesung aus "globalem Staat".

Wenn Sie dies tun, ist das grundlegende Problem, das Sie durch Ihren Kopf bekommen müssen, dass jedes Update zerstörungsfrei ist . Wenn der Laser auf den Roboter trifft, ändert sich der Zustand des Roboters nicht. Letztendlich berechnen Sie ein völlig neues Universum, das mit dem alten Universum identisch ist, außer dass der Roboter einen anderen Zustand hat. Wenn Sie dieses alte Universum brauchen, ist es immer noch da, unverändert.

Diese Reihe von Blog-Artikeln enthält weitere Gedanken zum Schreiben von Spielen in einem funktionalen Stil:

http://prog21.dadgum.com/23.html

Dieser Artikel befasst sich speziell mit Ihrer Frage "Shell trifft einen Panzer":

http://prog21.dadgum.com/189.html

Lesen Sie einfach den gesamten Blog. Es gibt gute Sachen und die Artikel sind kurz.

Eric Lippert
quelle
12

Jedes objektorientierte Programm kann in ein prozedurales Programm umgestaltet werden, indem alle Klassen durch Strukturen ersetzt und alle Elementfunktionen in eigenständige Funktionen konvertiert werden, die das Objekt thisals Argument verwenden.

Damit

 missile.setVelocity(100);

wird

 setMissileVelocity(missile, 100);

oder wenn diese Funktion trivial ist, tun Sie es einfach

 missile.velocity = 100;

Der Hauptunterschied zwischen objektorientierter Programmierung und prozeduraler Programmierung besteht darin, wie Sie Ihre Daten behandeln. In OOP sind Daten intelligent . Es verwaltet und manipuliert sich selbst. Bei der prozeduralen Programmierung sind Daten jedoch dumm . Es macht nichts alleine und muss von außen manipuliert werden.

Wenn Sie Strukturen sogar als zu objektorientiert betrachten, können Sie ein Array von Strukturen durch mehrere Arrays ersetzen, eines für alles, was eine Variable eines Flugkörpers wäre. Damit

struct Missile {
     int x;
     int y;
     int velocity;
}

Missile missiles[256];

wird

int missileX[256];
int missileY[256];
int missileVelocities[256];

In diesem Entwurf würde eine Funktion, die eine Operation mit mehreren Attributen auf derselben Rakete ausführt, jetzt einen Array-Index anstelle eines Verweises auf eine Struktur verwenden. Die Implementierung würde folgendermaßen aussehen:

function updateMissilePosition(int index) {
     missileX[index] += missileVelocity[index];
}
Philipp
quelle
1
Ist missileaber eine Instanz eines Objekts. In Nicht-OOP gibt es keine Instanzen. Bin ich richtig? Wenn ja, wie könnten Sie setMissileVelocity (missile, 100) einstellen?
user3150201
1
@ user3150201 Du bist nicht ganz richtig. Die meisten Nicht-OOP-Sprachen (und ich würde fast jede argumentieren, die für eine ernsthafte Spieleentwicklung geeignet ist) unterstützen Strukturen. Eine Struktur ist wie eine Klasse, nur dass sie nichts außer öffentlichen Variablen enthält. So ist es Ihnen erlauben würde , eine Art zu schaffen Missile, die eine Struktur mit mehreren Feldern, wie x, y, angleund velocity.
Philipp
@ user3150201 Aktualisierte Antwort mit einem Abschnitt darüber, wie es ohne Strukturen geht.
Philipp
Schöne Antwort Philipp, obwohl ich nicht verstehe, warum man nicht objektorientiert programmieren möchte. Es ist wirklich schwer, Nicht-OOP-Sprachen zu lesen, und es kann frustrierend sein. Der Code kann in kürzester Zeit zu einem Chaos werden.
Zhafur
2
@ Zhafur Du bist dir bewusst, dass deine Aussage ein massiver Flammenköder ist, oder?
Philipp
6

Ich mache es wie folgt:

  • Alle OOP-Klassen / Methoden haben Zugriff auf this. Um thisin einem Nicht-OO-Ansatz zu verwenden, übergeben Sie einfach in jedem Fall (siehe nächster Punkt) thisals ersten Parameter.
  • Zum Beispiel können Sie structs in Ihre Funktionen als übergeben this, aber ich finde, der beste Weg, um eine gute Cache-Leistung für produktive Objekte wie Entitäten oder Partikel zu erzielen, besteht darin, einfach einen einzelnen Index an mehrere Arrays von Grundelementen zu übergeben oder kleine structs. Dieser Index wird also für jedes einzelne Datenelement der ursprünglichen Klasse verwendet. Also zum Beispiel, wenn Sie hatten

...

class Entity //let's say you had 100 instances of this
{
   int a;
   char b;
   function foo() 
   {
      .../*can access 'this' herein*/
   }
}

Sie würden das durch ersetzen

int a[100];
char b[100];
function foo(int index);

Damit übergeben Sie jetzt einen Index an die Funktion, um das zu erhalten, was normalerweise wäre this.

Beachten Sie, dass Sie möglicherweise entweder Arrays von Grundelementen wie oben oder Arrays von structs verwenden möchten , je nachdem, wie Sie Ihre Daten am besten für eine gute Cache-Lokalität (Referenzlokalität) verschachteln. Eine anständige Cache-Leistung hängt natürlich viel mehr davon ab - insbesondere davon, auf welcher Sprache / Plattform Sie Ihren Code schreiben -, aber selbst in VM-basierten, dynamisch zugewiesenen Sprachen wie Java tendieren große lineare Arrays von Grundelementen dazu zeigen bessere Leistungsmerkmale als Objektinstanzen. Der Hauptgrund dafür ist, dass auf Objekte als Referenz zugegriffen wird. Dies bedeutet, dass Sie über den gesamten Speicher springen, um auf Daten zuzugreifen - ineffizient im Vergleich zum Zugriff auf Grundelemente, die von einem großen Array aus zusammenhängend sind.

Weitere Informationen zum Erstellen von Entitäten usw. als Array von Strukturen oder Grundelementen finden Sie unter Mick Wests Evolve your Hierarchy .

Ingenieur
quelle
0

Zusätzlich zu den vorhandenen Antworten möchten Sie möglicherweise wissen, wie Polymorphismus in einer prozeduralen Sprache ausgeführt wird.

Es gibt zwei Ansätze:

Typ speichern

In diesem Fall verfügt die Struktur über ein Feld für die Typkennung, wahrscheinlich eine Aufzählung, das mithilfe einer switchAnweisung überprüft wird, wenn eine typspezifische Aktion ausgeführt werden muss.

Der andere Weg ist:

Speichern von Funktionszeiger

Sie haben nicht erwähnt, in welcher Programmiersprache Sie Erfahrung haben, aber in verschiedenen Sprachen werden sie auch als Rückrufe, Delegaten, Ereignisse oder Funktionen höherer Ordnung oder einfach als Funktionsobjekte bezeichnet.

In diesem Fall speichern Sie keinen Typ, sondern einen Zeiger / Verweis auf eine Funktion, die die jeweilige Aktion ausführt. Wenn typspezifische Aktionen ausgeführt werden müssen, rufen Sie diese Funktion einfach auf. Dieser sieht den gewöhnlichen virtuellen Methoden sehr ähnlich.

Indem Sie zulassen, dass jede Funktion unabhängig eingestellt wird, erhalten Sie das Strategiemuster frei.


Bezüglich Ihres letzten Absatzes mit der Kollisionserkennung. Ich denke, Sie haben wahrscheinlich mehrere Arten von Panzern und Sie haben mehrere Arten von Raketen, und jede Kombination kann möglicherweise ein anderes Ergebnis haben, wenn sie kollidieren. Wenn Sie danach suchen, haben Sie ein Problem, das noch nicht einmal von OOP-Sprachen gelöst wurde: Mehrfachversand und Mehrfachmethoden, die mehrere thisParameter unterschiedlichen Typs haben können. Für dieses Problem gibt es wieder zwei Alternativen:

  • Verschachtelte Schalter für jede Kombination aus Panzer und Rakete.
  • Zweidimensionale Versandarrays, die Zeiger auf Funktionen für jede Kombination aus Panzer und Rakete enthalten.
Calmarius
quelle