SFML und Renderer
-
Hallo!
Ich wollte mal etwas anderes machen als durchgehend auf der Konsole programmieren und hab mir mal SFML angesehen. Beim Rumspielen ist mir dann folgendes aufgefallen:
Wenn ich ein Window zeichne sind die Elemente des Fensters innerhalb der
while(wnd.isOpen())
-Schleife angesiedelt. Sagen wir ich will verschiedene Elemente im Fenster zeichnen: dann muss ich jedes Element in dieser Schleife platzieren. Ich finde das aber nicht hübschNun dachte ich folgendes:
Ich schreibe mir für die zu zeichnenden Elemente Klassen, welche von Drawable erben. Folgender Quellcode:class SimpleShape : public sf::Drawable { public: SimpleShape(sf::RenderTarget &target) { m_Shape = sf::CircleShape(100.f); this->m_Shape.setFillColor(sf::Color::Black); } void objectLogic() { if(sf::Keyboard::isKeyPressed(sf::Keyboard::E)) { this->m_Shape.move(0.5f, 0); } } private: virtual void draw(sf::RenderTarget &target, sf::RenderStates states) const { target.draw(m_Shape); } sf::CircleShape m_Shape; }; int main() { sf::RenderWindow window(sf::VideoMode(500, 500), "Test"); SimpleShape sh(window); while(window.isOpen()) { sf::Event evt; while(window.pollEvent(evt)) { if(evt.type == sf::Event::Closed) window.close(); } window.clear(sf::Color::White); window.draw(sh); sh.objectLogic(); window.display(); } return 0; }
Schon schöner, aber in meinen Augen immer noch nicht hübsch. Was ich gern machen würde:
Einen Container (Vektor?) der die zu zeichnenden Elemente speichert und ich in der while vom Window selbst einfach nur den Vektor via Iterator durchgehe. Ich finde es auch nicht schön, dass ich jedes Objekt zuerst zeichnen muss und danach die "ObjektLogik" (ich nenn es einfach mal so) noch extra implementieren muss.Die Frage: Wie mache ich das schöner?
Bei einem Vektor habe ich folgendes Problem: der Vektor will ja einen Typ von mir wissen, da es sich um verschiedene Objekte handelt (z.B. "Player", "GameField", ...) weiß ich nicht welchen Typ er will. Mit Templates habe ich mir da auch nichts brauchbares zusammendichten können.Auf GitHub (oder auch google) habe ich das ganze via einer Klasse "GameObject" gelöst gefunden. Aber das verstehe ich nicht ganz?
Am schönste wäre es, wenn ich eine Klasse hätte (einen Renderer) der mir die alle zu zeichnenden Elemente entgegen nimmt, zeichnet und zeitgleich die Logik implementiert. Nur wie ich das mache: keine Ahnung
Danke schonmal
-
Langfristig willst du Spiellogik und Grafik sowieso trennen, sodass du sie mit unterschiedlichen Frameraten berechnen kannst. Einen etwas fortgeschrittenen Artikel dazu findest du hier.
Der klassische Ansatz wäre eine Basisklasse
Entity
, von der die spezifischen Spiel-Einheiten ableiten. Allerdings solltest du einen Vorteil in dieser Abstraktion sehen, da du dafür konkrete Typinformationen verlierst. Achte auch darauf, die konkreten Funktionalitäten schön über virtuelle Funktionen zu implementieren, und nicht etwa explizite Fallunterscheidungen je nach Einheit einzubauen. Ansonsten spricht nichts Grundsätzliches dagegen, einen separaten Container pro Spiel-Einheit zu haben. Das vereinfacht nämlich Aktionen, in denen du die volle Typinformation mehrerer Einheiten brauchst, wie z.B. Kollision.Ich selbst hatte in einem etwas älteren Spiel ebenfalls eine Renderer-Klasse, die mehrere Überladungen einer Funktion
Draw()
anbietet, z.B.void Draw(const Enemy& enemy); void Draw(const Player& player); void Draw(const Scenery& scenery);
Die einzelnen Klassen
Player
,Enemy
etc. sind dabei hauptsächlich Logik-Objekte, der grösste Teil der Grafikinformationen wird direkt inRenderer
verarbeitet. In dem Fall war es auch performancemässig kein Problem, in denDraw()
-Funktionen jeweils neuesf::Sprite
s zu erstellen und direkt zu zeichnen. Andernfalls könntest du die Sprites auch in den Entity-Klassen speichern. Analog zum Renderer hatte ich eine Physik-Klasse, die Kollisionen und Bewegungen der einzelnen Einheiten berechnet. In einer übergeordneten Game-Klasse waren alle Einheiten gespeichert, und zwar in jeweils voll typisierten, separaten Containern.P.S.: Mach Konstruktoren
explicit
. DeinSimpleShape
-Konstruktor braucht zudem keinen Parameter. Die einzelnen Member würde ich mit der Konstruktor-Initialisierungsliste initialisieren.
-
Und wenn ich jetzt 30 Objekte zum zeichnen habe soll ich
draw
30 mal überladen? Ich weiß nicht, dass klingt nicht besonders schönIch habe mir das eher so gedacht, eine Klasse
Renderer
der ich die Objekte übergeben kann und im while vom Fenster dann etwas in der Art vonrenderer.drawAll()
und damit werden alle Objekte gezeichnet. Das wiederrum bedeutet halt, dass ich die Objekte irgendwo lagern muss - und ich nicht weiß, wie ich einen Container machen kann, der verschiedene Objekte entgegen nimmt
-
Lokart schrieb:
Und wenn ich jetzt 30 Objekte zum zeichnen habe soll ich
draw
30 mal überladen?Nein, du erstellst eben eine Hierarchie und fasst Ähnliches zusammen. Aber ich sag ja nicht, dass du das so machen musst, ist eine von vielen Möglichkeiten. Oft sieht man auch die Tendenz weg von klassischen Vererbungshierarchien, hin zu komponenten-basiertem Design.
Lokart schrieb:
Das wiederrum bedeutet halt, dass ich die Objekte irgendwo lagern muss - und ich nicht weiß, wie ich einen Container machen kann, der verschiedene Objekte entgegen nimmt
Wenn du das so machst, musst du ja nur die Verweise auf die Objekte lagern, die Objekte selbst sind sonst schon irgendwo gespeichert. Dazu kannst du z.B. ein
Drawable*
als Interface verwenden.Aber warum zuerst an im Renderer speichern, statt direkt zu zeichnen? Führst du irgendwelche Zwischenschritte durch?
-
Okay, ich habs für mein Vorhaben mal bisschen abgeändert.
In erster Linie aber schlage ich mich gerade mit den einzelnen Szenen rum (Intro, Menu, Spiel selbst). Funktioniert auch, nur eine Frage hätte ich:Ich hab die Funktion
show
der Game-Klasse selbst für die einzelnen Szenen überladen und rufe beispielsweise das Intro wie folgt auf:this->show(new Intro);
Es mag vlt. total übertrieben sein, aber: wo ruft C++ an dieser Stelle eigentlich, wenn überhaupt, den Destruktor auf? Testweise habe ich den Destruktor in der Intro-Klasse so definiert, dass er "bla" auf der Konsole ausgeben sollte, wenn er aufgerufen wird - leider wird er das nicht.
Es geht mir nur drum, dass ich das Intro "los bin" wenn ich z.B. im Menü bin.
-
Was du mit new anlgst musst du auch wieder mit delete frei geben. Da du das hier nicht tust, räumt niemand für dich das Intro auf.
//dit vielleicht wäre es doch besser, wenn du noch etwas auf der Konsole bleibst. Das sind echt Grundlagen, die beim Grafikdesign sitzen müssen.