Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!
Werbeanzeige
Zitat von »"Nexus"«
Ein weiterer Vorteil dieser Vorgehensweise besteht darin, dass du gleich automatische Objekte in Container packen kannst und keine Umwege über polymorphe Zeiger gehen musst. Hüte dich so gut es geht vor rohen, besitzenden Zeigern in STL-Containern, denn diese birgen nicht wenige Risiken.
Kommt drauf an, wie man seine Objekte gestaltet. Wenn man dem Ideal nahekommt, sie als reine Logikobjekte zu implementieren, hat man leichtgewichtige Instanzen. Natürlich muss man sich auch irgendwo um Ressourcen und ähnliches kümmern. Das muss aber nicht zwangsläufig Bestandteil Spielobjekt-Klasse selbst sein, denn damit erschwert man Objektsemantik recht stark (vor allem durch Exceptionsicherheit und Performance).Zitat von »"dot"«
Das setzt entsprechende Kopiersemantik seiner Objekte voraus die bei komplexen Spielobjekten wohl nicht gegeben ist.
Eben nicht, da das auf oberster Ebene passiert. Soll heissen, man hantiert mit statischen Typen. Irgendwo muss man den exakten, statischen Typen eines Objekts kennen – mindestens bei der Konstruktion. Sofern man es zustande bringt, Spielobjekte an einem gemeinsamen Ort zu konstruieren, hat man auch dieses Problem nicht mehr.Zitat von »"dot"«
Vor allem in Verbindung mit Polymorphie wäre das problematisch (slicing!).
Zitat von »"Nexus"«
Kommt drauf an, wie man seine Objekte gestaltet. Wenn man dem Ideal nahekommt, sie als reine Logikobjekte zu implementieren, hat man leichtgewichtige Instanzen. Natürlich muss man sich auch irgendwo um Ressourcen und ähnliches kümmern. Das muss aber nicht zwangsläufig Bestandteil Spielobjekt-Klasse selbst sein, denn damit erschwert man Objektsemantik recht stark (vor allem durch Exceptionsicherheit und Performance).Zitat von »"dot"«
Das setzt entsprechende Kopiersemantik seiner Objekte voraus die bei komplexen Spielobjekten wohl nicht gegeben ist.
Zitat von »"Nexus"«
Eben nicht, da das auf oberster Ebene passiert. Soll heissen, man hantiert mit statischen Typen. Irgendwo muss man den exakten, statischen Typen eines Objekts kennen – mindestens bei der Konstruktion. Sofern man es zustande bringt, Spielobjekte an einem gemeinsamen Ort zu konstruieren, hat man auch dieses Problem nicht mehr.Zitat von »"dot"«
Vor allem in Verbindung mit Polymorphie wäre das problematisch (slicing!).
Zitat von »"Nexus"«
Aber selbst ein ptr_container<Enemy> ist noch besser als ein ptr_container<Object>, sofern man Nutzen aus der zusätzlichen Typinformation ziehen kann. Und letzteres ist meist der Fall, da sich Gegner von anderen Objekten unterscheiden und speziell behandelt werden müssen (was nicht immer mit virtuellen Funktionen geht). Mit ptr_container meine ich übrigens ein Container-Klassentemplate, das seine Elemente als Zeiger speichert und die Speicherverwaltung übernimmt, wie die Pointer-Containers von Boost.
... Jo, auf jedenfall eine gute Möglichkeit.Zitat von »"Nexus"«
Gut, genau so habe ich mir das vorgestellt. Es scheint so, als ob mein Entwurf gar nicht so schlecht passen würde. Sowas wie "Objekt" könntest du als Basisklasse nehmen, von dem du dann z.B. kollidierbare Objekte ableitest, diese werden wiederum in Gegner, Spieler, Projektile unterteilt
Im Grunde ja, aber hier die Frage: Kollision gehört zur Gamelogik, sie baut allerdings auf grafische Elemente (Bildgröße, Alphablending, Rotation) auf. Oder anders ausgedrückt: Durch eine strikte Trennung müsste man hier redundante Informationen speichern.Zitat von »"Nexus"«
Das sehen zwar nicht alle gleich, aber ich persönlich finde es wichtig, Grafik und Logik zu trennen.
Zitat von »"Nexus"«
... Wenn diese darauf hinausläuft, dass wieder Fallunterscheidungen ins Spiel kommen, hat man das Prinzip nicht ganz verstanden.
Zitat von »"Nexus"«
Hüte dich so gut es geht vor rohen, besitzenden Zeigern in STL-Containern, denn diese birgen nicht wenige Risiken.
Es kommt halt sehr darauf an, wie das gesamte Design gestaltet ist. Nicht immer ist es angebracht, Logikobjekte 1:1 auf Ressourcen abzubilden (z.B. dann nicht, wenn eine Ressource von mehreren Objekten geteilt wird). Ich will damit nur sagen, dass die Ressource nicht unbedingt ein Attribut des Logikobjekts sein muss.Zitat von »"dot"«
Und das mit den Ressourcen ist beim kopieren auch sehr interessant.
Das kann ich nachvollziehen.Zitat von »"dot"«
Leichtgewichtigkeit bringt aber noch nicht gleich Wertsemantik mit sich. Die Lebensdauer is zum Beispiel auch interessant. Die Objekte in meiner Welt sollten idealerweise nicht plötzlich kopiert und zerstört werden.
Und das passiert mit Zeigern, die man in einem std::vector ablegt, nicht?Zitat von »"dot"«
Wenn du die Objekte in einem vector ablegst passierts dir z.B. dass Pointer/Referenzen/Iteratoren drauf auf einmal ungültig sind wenn der vector wächst usw.
Nicht direkt, aber man kann ja ohne weiteres Zeiger und Referenzen auf statische Objekte deklarieren (auch implizit bei Funktionsübergaben). Ich will nur sagen, dass es ohne Probleme möglich ist, Laufzeitpolymorphie auf automatisch verwaltete Objekte anzuwenden.Zitat von »"dot"«
Ja natürlich, so lange du mit statischen Typen hantierst nicht. So lange is ja aber auch keine Polymorphie im Spiel
Zitat von »"Nexus"«
Es kommt halt sehr darauf an, wie das gesamte Design gestaltet ist. Nicht immer ist es angebracht, Logikobjekte 1:1 auf Ressourcen abzubilden (z.B. dann nicht, wenn eine Ressource von mehreren Objekten geteilt wird). Ich will damit nur sagen, dass die Ressource nicht unbedingt ein Attribut des Logikobjekts sein muss.Zitat von »"dot"«
Und das mit den Ressourcen ist beim kopieren auch sehr interessant.
Zitat von »"Nexus"«
Und das passiert mit Zeigern, die man in einem std::vector ablegt, nicht?Zitat von »"dot"«
Wenn du die Objekte in einem vector ablegst passierts dir z.B. dass Pointer/Referenzen/Iteratoren drauf auf einmal ungültig sind wenn der vector wächst usw.
Zitat von »"Nexus"«
Nicht direkt, aber man kann ja ohne weiteres Zeiger und Referenzen auf statische Objekte deklarieren. Inwiefern hat Polymorphie etwas damit zu tun, ob Objekte automatisch oder dynamisch angefordert werden?Zitat von »"dot"«
Ja natürlich, so lange du mit statischen Typen hantierst nicht. So lange is ja aber auch keine Polymorphie im Spiel
Okay, ich kenn dein Projekt zu wenig, um das zu beurteilen. Kann durchaus sein, dass sich die Trennung nicht vernünftig durchführen lässt. Ich habe jetzt an einen Fall gedacht, bei dem die Kollision grundsätzlich unabhängig von den Grafikdaten ist.Zitat von »"Laguna"«
Im Grunde ja, aber hier die Frage: Kollision gehört zur Gamelogik, sie baut allerdings auf grafische Elemente (Bildgröße, Alphablending, Rotation) auf. Oder anders ausgedrückt: Durch eine strikte Trennung müsste man hier redundante Informationen speichern.
Da gehts aber mehr um Vererbung vs. Aggregation. Und bei diesem Beispiel bin ich klar der Meinung, dass Vererbung falsch ist (und meist aus Faulheit und/oder Unerfahrenheit resultiert).Zitat von »"Laguna"«
Diesbezüglich gabs auch mal einen informativen Thread auf Developia: Ein Character IST ein Sprite oder HAT ein Sprite?
Mir ist nichts bekannt, ich denke auch nicht, dass dynamisches Double-Dispatch ohne dynamic_cast und typeid (oder was Nachgebautem) funktioniert. Und diese beiden Operatoren sollte man eher sparsam einsetzen.Zitat von »"Laguna"«
Probleme stellen sich halt erst ein, wenn man 2 Typen gegeneinander prüft, weil das von C++ nicht nativ unterstützt wird. (Gibts da zufällig ne Boost-Lösung für?)
Okay, ich schau mal, ob ich gleich ein gutes Beispiel bringen kann.Zitat von »"Laguna"«
Was kann ich mir darunter vorstellen? Gibts ein kleines Code-Beispiel?
Ja, ich weiss, ich hab da was durcheinandergebracht. Aber wenigstens gleich gemerkt und editiert, aber du warst zu schnell. :pZitat von »"dot"«
Gar nicht. Ich hab auch nicht von dynamischer allokation etc. geredet sondern vom statischen/dynamischen Typ eines Objektes
Ich finde, auf oberster Ebene dürfen sie durchaus Wertsemantik haben. Sonst verfangen wir uns in Techniken von anderen Programmiersprachen (z.B. Clone()), was in C++ oft unnötig ist und eben auch Probleme bringt.Zitat von »"dot"«
Ich meinte lediglich dass slicing erst ein Problem wird wenn Polymorphie im Spiel ist und polymorphe Typen im allgemeinen keine Wertsemantik haben sollten. Wenn du lediglich mit statischen Typen hantierst hast du das Problem natürlich nicht, dann machst du aber auch keinen Gebrauch von Polymorphie.
C-/C++-Quelltext |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Object {}; // polymorph class Enemy : public Object {}; class Tree : public Object {}; class GraphicManager { public: void Draw(const Object& Obj); }; class World { public: void DrawEverything() const { MyGraphicMgr.Draw(MyEnemy); MyGraphicMgr.Draw(MyTree); } private: GraphicManager MyGraphicMgr; Enemy MyEnemy; Tree MyTree; }; |
Gut, dass du das machst, so wird die Diskussion vielfältiger.Zitat von »"dot"«
Wie gesagt, die Lösung mit statischer Typisierung und mehreren Containern ist für den geschilderten Anwendungsfall sicherlich eine gute Lösung, das will ich gar nicht bestreiten. Ich wollte nur ein paar der Konsequenzen davon ansprechen
Zitat von »"Nexus"«
Da gehts aber mehr um Vererbung vs. Aggregation. Und bei diesem Beispiel bin ich klar der Meinung, dass Vererbung falsch ist (und meist aus Faulheit und/oder Unerfahrenheit resultiert).Zitat von »"Laguna"«
Diesbezüglich gabs auch mal einen informativen Thread auf Developia: Ein Character IST ein Sprite oder HAT ein Sprite?
Zitat von »"Nexus"«
Mir ist nichts bekannt, ich denke auch nicht, dass dynamisches Double-Dispatch ohne dynamic_cast und typeid (oder was Nachgebautem) funktioniert. Und diese beiden Operatoren sollte man eher sparsam einsetzen.Zitat von »"Laguna"«
Probleme stellen sich halt erst ein, wenn man 2 Typen gegeneinander prüft, weil das von C++ nicht nativ unterstützt wird. (Gibts da zufällig ne Boost-Lösung für?)
C-/C++-Quelltext |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
class Player; class Enemy; class GameObject { private: GameObject(const GameObject&); GameObject& operator =(const GameObject&); public: GameObject() {} virtual ~GameObject() {} virtual void interact(GameObject& object) = 0; virtual void interact(Player& player) {} virtual void interact(Enemy& enemy) {} }; class Player : public GameObject { public: void interact(GameObject& object) { object.interact(*this); } void interact(Enemy& enemy) { // Spieler tut irgendwas mit Gegner } }; class Enemy : public GameObject { public: void interact(GameObject& object) { object.interact(*this); } void interact(Player& object) { // Gegner tut irgendwas mit Spieler } }; |
C-/C++-Quelltext |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
struct Schere; struct Stein; struct Papier; struct Taktik { virtual bool Besiegt(Taktik* Other) = 0; virtual bool Besiegt(Schere* Other) = 0; virtual bool Besiegt(Stein* Other) = 0; virtual bool Besiegt(Papier* Other) = 0; virtual ~Taktik() {} }; struct Schere : public Taktik { virtual bool Besiegt(Taktik* Other) { return !Other->Besiegt(this); } virtual bool Besiegt(Schere* Other) { return false; } virtual bool Besiegt(Stein* Other) { return false; } virtual bool Besiegt(Papier* Other) { return true; } }; struct Stein : public Taktik { virtual bool Besiegt(Taktik* Other) { return !Other->Besiegt(this); } virtual bool Besiegt(Schere* Other) { return true; } virtual bool Besiegt(Stein* Other) { return false; } virtual bool Besiegt(Papier* Other) { return false; } }; struct Papier : public Taktik { virtual bool Besiegt(Taktik* Other) { return !Other->Besiegt(this); } virtual bool Besiegt(Schere* Other) { return false; } virtual bool Besiegt(Stein* Other) { return true; } virtual bool Besiegt(Papier* Other) { return false; } }; int main() { Taktik* t[3] = {new Schere, new Stein, new Papier}; for (int j = 0; j < 3; ++j) { for (int i = 0; i < 3; ++i) { std::cout << t[j]->Besiegt(t[i]); } std::cout << std::endl; } for (int i = 0; i < 3; ++i) { delete t[i]; } } |
Werbeanzeige