Wie organisiere ich eine Game Engine in C ++? Ist meine Verwendung der Vererbung eine gute Idee?


11

Ich bin ein Anfänger sowohl in der Spieleentwicklung als auch in der Programmierung.

Ich versuche, ein Prinzip beim Aufbau einer Spiel-Engine zu lernen.
Ich möchte ein einfaches Spiel erstellen. Ich bin an dem Punkt angelangt, an dem ich versuche, die Spiel-Engine zu implementieren.

Also dachte ich, meine Spiel-Engine sollte diese Dinge kontrollieren:

- Moving the objects in the scene
- Checking the collisions
- Adjusting movements based on collisions
- Passing the polygons to the rendering engine

Ich habe meine Objekte so gestaltet:

class GlObject{
private:
    idEnum ObjId;
    //other identifiers
public:
    void move_obj(); //the movements are the same for all the objects (nextpos = pos + vel)
    void rotate_obj(); //rotations are the same for every objects too
    virtual Polygon generate_mesh() = 0; // the polygons are different
}

und ich habe 4 verschiedene Objekte in meinem Spiel: Flugzeug, Hindernis, Spieler, Kugel und ich habe sie so entworfen:

class Player : public GlObject{ 
private:
    std::string name;
public:
    Player();
    Bullet fire() const; //this method is unique to the class player
    void generate_mesh();
}

Jetzt möchte ich in der Spiel-Engine eine allgemeine Objektliste haben, in der ich zum Beispiel nach Kollisionen suchen, Objekte verschieben usw. kann, aber ich möchte auch, dass die Spiel-Engine die Benutzerbefehle verwendet, um den Spieler zu steuern ...

Ist das eine gute Idee?

class GameEngine{
private:
    std::vector<GlObject*> objects; //an array containg all the object present
    Player* hPlayer; //hPlayer is to be read as human player, and is a pointer to hold the reference to an object inside the array
public:
    GameEngine();
    //other stuff
}

Der GameEngine-Konstruktor sieht folgendermaßen aus:

GameEngine::GameEngine(){
    hPlayer = new Player;
    objects.push_back(hPlayer);
}

Die Tatsache, dass ich einen Zeiger verwende, liegt daran, dass ich den fire()für das Player-Objekt eindeutigen Zeiger aufrufen muss .

Meine Frage ist also: Ist das eine gute Idee? Ist meine Verwendung der Vererbung hier falsch?



Ich habe sie nach dem Posten gesehen und tatsächlich ist Komposition eine großartige Idee! Ich habe immer Angst, c mit Klassen zu verwenden, anstatt ein echtes C ++ - Design zu haben!
Pella86

Ich denke, es gibt viel mehr Gründe, Komposition als Vererbung zu verwenden ... Meine Antwort enthält einige Beispiele für die damit verbundenen Flexibilitätsvorteile. Ein gutes Beispiel für die Implementierung finden Sie in den Artikeln, die ich verlinkt habe.
Coyote

Antworten:


12

Kommt darauf an, was du mit "gut" meinst. Es wird sicherlich die Arbeit erledigen. Es hat viele Vor- und Nachteile. Sie werden sie erst finden, wenn Ihr Motor viel komplexer ist. Siehe diese Frage zu SO zur Diskussion zu diesem Thema.

In beiden Fällen gibt es viele Meinungen, aber wenn sich Ihr Motorcode noch in einem frühen Stadium befindet, wird es schwierig sein, Ihnen zu erklären, warum er möglicherweise schlecht ist.

Im Allgemeinen hat Scott Meyers einige großartige Dinge zur Vererbung zu sagen. Wenn Sie sich noch in der Entwicklungsphase befinden, lesen Sie dieses Buch (Effective C ++), bevor Sie fortfahren. Es ist ein fantastisches Buch und eine Voraussetzung für alle Programmierer, die in unserem Unternehmen arbeiten. Aber um den Rat zusammenzufassen: Wenn Sie eine Klasse schreiben und die Verwendung von Vererbung in Betracht ziehen, stellen Sie absolut klar, dass die Beziehung zwischen der Klasse und ihrem Vorfahren eine 'ist eine' Beziehung ist. Das heißt, jedes B ist ein A. Verwenden Sie die Vererbung nicht nur als einfache Möglichkeit, B Zugriff auf die von A bereitgestellten Funktionen zu gewähren. Dies führt zu schwerwiegenden Architekturproblemen, und es gibt andere Möglichkeiten, dies zu tun.

Einfach ausgedrückt; Bei einer Engine jeder Größe werden Sie schnell feststellen, dass die Objekte selten in eine ordentliche hierarchische Struktur fallen, für die die Vererbung funktioniert. Verwenden Sie es, wenn es angebracht ist, aber geraten Sie nicht in die Falle, es für alles zu verwenden - lernen Sie andere Möglichkeiten, um Objekte miteinander in Beziehung zu setzen.


Ich stimme der Meinung über die Vererbung zu. Meine persönliche Faustregel lautet: Tu es nicht, es sei denn, du musst es oder es wäre dumm, es anders zu machen. Mit anderen Worten, Vererbung ist in 99% der Fälle eine schlechte Idee (zumindest :)). Es kann auch sinnvoll sein, die Spielelogik (Drehen / Verschieben) vom Rendern (generate_mesh ()) zu trennen. Eine andere Sache ist fire () - erwägen Sie eine Liste von Aktionen und eine generische Aktionsfunktion (my_action) - auf diese Weise erhalten Sie nicht eine Unmenge verschiedener Funktionen, die über alle Klassen verteilt sind. Halten Sie es in beiden Fällen einfach und überarbeiten Sie es rücksichtslos, wenn Sie auf Probleme stoßen.
Gilead

8

Für die Logikdaten und Funktionen Ihrer Spielobjekte (Entitäten) würde ich dringend empfehlen, Komposition anstelle von Vererbung zu verwenden. Ein guter Anfang wäre der komponentenbasierte Ansatz. In diesem Cowboy-Programmierartikel wird ausführlich beschrieben, wie diese Architektur implementiert werden kann und wie sie dem Tony Hawk-Franchise zugute kommt.

Auch dieser Artikel über Entitätssysteme hat mich inspiriert, als ich ihn zum ersten Mal las.

Vererbung ist ein großartiges Werkzeug für Polymorphismus. Mit Ausnahme sehr einfacher Projekte sollten Sie es vermeiden, die Entitäten und Ihre Spielelogiken zu strukturieren. Wenn Sie sich für die Vererbung entscheiden, müssen Sie mit Sicherheit Kopien desselben Codes replizieren und verwalten, um Funktionen zu implementieren, die nicht von einem gemeinsamen übergeordneten Element geerbt werden können. Und Sie werden wahrscheinlich anfangen, Ihre Klassen mit Logik und Daten zu verschmutzen, die am häufigsten verwendet werden, um zu vermeiden, dass zu viel Code repliziert wird.

Komponenten sind in zahlreichen Situationen sehr praktisch. Mit ihnen können Sie viel mehr erreichen:

Flexibilität bei Ihren Entitätstypen : Es gibt Zeiten in der Spieleentwicklung, in denen sich einige Entitäten plötzlich entwickeln und während der Spieleentwickler die Entscheidung trifft, dass der Entwickler sie implementieren muss. Es sind nur wenige Schritte erforderlich, um ein vorhandenes Verhalten hinzuzufügen, ohne Code zu replizieren. Komponentenbasierte Systeme ermöglichen es, dass Nicht-Programmierer weitere Änderungen vornehmen können. Beispielsweise kann jeder Entitätstyp anstelle einer Klasse durch eine Beschreibungsdatei dargestellt werden, die alle Komponentennamen und Parameter enthält, die zum Konfigurieren des Typs erforderlich sind. Auf diese Weise können Nicht-Programmierer Änderungen vornehmen und testen, ohne das Spiel neu zu kompilieren. Das einfache Bearbeiten der Beschreibungsdateien reicht aus, um die von jedem Entitätstyp verwendeten Komponenten hinzuzufügen, zu entfernen oder zu ändern.

Flexibilität bei bestimmten Instanzen und bestimmten Szenarien : In einigen Fällen, in denen Ihr Spieler Waffen oder Gegenstände ausrüsten muss, können Sie Ihrem Spieler eine Inventarkomponente hinzufügen. Während der Entwicklung Ihres Spiels benötigen Sie andere Entitäten, um ein Inventar zu haben (z. B. Feinde). Und irgendwann haben Sie in Ihrem Szenario eine Situation, in der ein Baum eine Art verstecktes Objekt enthalten soll. Mit Komponenten können Sie eine Inventarkomponente auch zu Entitäten hinzufügen, deren Typ ursprünglich nicht für eine solche ohne zusätzlichen Programmieraufwand ausgelegt ist.

Flexibilität zur Laufzeit : Komponenten bieten eine sehr effektive Möglichkeit, das Verhalten von Entitäten zur Laufzeit zu ändern. Sie können zur Laufzeit Komponenten hinzufügen und entfernen und so bei Bedarf Verhaltensweisen und Eigenschaften erstellen oder entfernen. Sie ermöglichen es Ihnen auch, verschiedene Datentypen in Gruppen zu unterteilen, die (manchmal) auf mehreren GPUs separat parallel berechnet werden können.

Zeitliche Flexibilität : Komponenten können verwendet werden, um die Kompatibilität zwischen Versionen aufrechtzuerhalten. Wenn beispielsweise zwischen den Versionen eines Spiels Änderungen vorgenommen werden, können Sie (nicht immer) einfach neue Komponenten hinzufügen, die die neuen Verhaltensweisen und Änderungen implementieren. Anschließend instanziiert das Spiel die richtigen Komponenten, die an Ihre Entitäten angehängt sind, abhängig von der geladenen Sicherungsdatei, der Peer-Verbindung oder dem Server, dem der Spieler beitritt. Dies ist äußerst praktisch in Szenarien, in denen Sie die Veröffentlichungsdaten der neuen Version nicht auf allen Plattformen (Steam, Mac App Store, iPhone, Android ...) steuern können.

Für einen besseren Einblick werfen Sie einen Blick auf die mit [komponentenbasierten] und [Entitätssystem] gekennzeichneten Fragen.


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.