Kompilierzeitprobleme mit modularer Programmierung
-
Th69 schrieb:
Trotzdem kann ich bei dir nicht nachvollziehen, warum du bei modularer Programmierung größere Kompilierzeiten hast als bei einer Datei.
Denn bei Änderung an einer Datei müßte ja der ganze Code neu kompiliert werden (und dies jedesmal, anstatt nur einige wenige kleinere Dateien).Ja, aber wie gesagt gibt es bei mir relativ viele Module, die voneinander abhängen (z.B. Spieler - Gegner, Spieler - Game, etc.)... Siehe dazu unten das Design.
Th89 schrieb:
Vllt. könntest du ja mal dein Klassendesign exemplarisch hier aufzeigen, d.h. die Header und deren Includes.
Gut möglich, dass das Design so nicht gut ist, aber damit hab ich eben noch nicht so grosse Erfahrung (bzw. das ist mein erstes Spiel, wo ich richtig modular programmiere und aufs Design achte).
Also es handelt sich um ein Jump'n'Run. Ich muss noch erwähnen, dass die Klassen
EnemyundWeaponnoch nicht implementiert sind, ich hab sie jedoch so im Design vorgesehen.Zuerst mal die Vererbungshierarchie:
[b]Master[/b] (verwaltet alles) im Moment noch alles statische Member, soll jedoch noch Singleton werden sozusagen global, von überall zugreifbar [b]Surface[/b] (Oberfläche) | +----------+------+-------+--------+ | | | | [b]MainMenu[/b] [b]InfoMenu[/b] .. [b]Game[/b] [b]Editor[/b] [b]Object[/b] (Spielelemente, [b]Tile[/b] die Kollision berücksichtigen) (Bausteine der Welt) | +--------------+---------------+ | | | [b]Player[/b] [b]Enemy[/b] [b]Weapon[/b] (abgefeuerte Waffen)Zusätzlich noch einige weniger relevante Klassen.
Hier die Membertypen von wichtigen Klassen:
Master --> Surface* (Basisklassenzeiger (Polymorphie)) --> Grafikklassen (Fenster, Event, etc.), die überall benötigt werden Game --> Player --> Enemy --> Weapon --> TileNun ist es so, dass Kollisionsabfragen in
Objectpassieren, d.h. da werden schon mal die KlassenTileundGame(aktuelle Oberfläche) benötigt. Bei den KlassenPlayer,EnemyundWeaponmuss ich wahrscheinlich eine Dreiecksabhängigkeit einrichten, weil sich diese Klassen jeweils gegenseitig beeinträchtigen können.
Gameselber benötigt natürlich alle Klassen, die Typen der Member sind.
Was ein Problem darstellt, ist die Tatsache, dass z.B.Playerindirekt über die BasisklasseObjectaufGameZugriff haben muss, um z.B. Kollisionsabfragen mit den Tiles zu handhaben. Ich habe das mit einem Zeiger gelöst, jedoch hab ich momentan das Problem, dass eine Vorwärtsdeklaration nicht ausreicht, weil eindynamic_cast<Game*>(Surface*)durchgeführt werden muss. Aber das lässt sich evtl. dadurch lösen, dass beim Erstellen derPlayer-Instanz ein Zeiger aufGameübergeben wird. Was wiederum nicht geht, weil im Konstruktor vonGameja auchPlayerkonstruiert wird, und derthis-Zeiger vonGamezu diesem Zeitpunkt noch auf die BasisklasseSurfaceverweist...
Grundsätzlich wäre fast alles mit Vorwärtsdeklarationen und Zeigern als Member zu lösen. Was ich jedoch unnötig finde, weil auf ich eigene Speicherverwaltung lieber verzichte, wo sichs vermeiden lässt (und wie gesagt auf The Big Three). Oder ist das die gängige Variante (Pimpl scheint mir arg übertrieben für mein verhältnismässig kleines Spiel)?
Dazu:
Th89 schrieb:
Aus
Das führt dazu, dass ich für jede kleine Klasse, die mehrfach vorkommen kann, einen eigenen Kopierkonstruktor, Destruktor und Zuweisungsoperator implementieren muss - was meiner Ansicht nach nicht gerade optimal ist.
schließe ich, daß du noch einigen Nachholbedarf beim Design und deren Anwendung in C++ hast
Das will ich auch nicht bestreiten

Aber was ist denn daran so falsch?
Und was meinst du genau mit folgender Aussage?Th89 schrieb:
denn das Verwenden von eingebetteten Objekten führt ja zu Kopien, so daß keine Eindeutigkeit der Objekte mehr gewährleistet ist.
-
Nexus schrieb:
Was Forward Declarations betrifft: Da hab ich auch einige drin. Jedoch impliziert das immer, dass man als Member nur Zeiger oder Referenzen haben kann. Das führt dazu, dass ich für jede kleine Klasse, die mehrfach vorkommen kann, einen eigenen Kopierkonstruktor, Destruktor und Zuweisungsoperator implementieren muss - was meiner Ansicht nach nicht gerade optimal ist.
Warum sollte man wegen Forward Declaration mehr Kopierkonstruktor, Destruktor und Zuweisungsoperator implementieren müssen? Thema Smartpointer...
shared_ptr<T> kann für einen unvollständigen Typ T verwendet werden:
class X; // forward declaration shared_ptr<X> pX; // "X *"Zum Dereferenzieren und Freigeben von pX muss X aber vollständig bekannt sein - boost prüft das und wirft andernfalls eine Compile-Fehlermeldung.
Nexus schrieb:
Wie macht ihr das eigentlich?
#include-Direktiven fast nur in CPP-Dateien?In meinen privaten Projekten: Ja.
In Projekten an der Arbeit: Kommt auf den Codestil an, auch wenn ich immer versuchen werde, soviel wie möglich in den Source zu ziehen.cu André
-
Nexus schrieb:
Master (verwaltet alles)
im Moment noch alles statische Member,
soll jedoch noch Singleton werden
sozusagen global, von überall zugreifbarHmm. Das würde ich schon mal anderst lösen. Mach da lieber ein Objekt draus, dass du dann direkt in der main initialisierst und hald einen Zeiger, oder eine Referenz an die restlichen Klassen übergibst, damit du darauf zugreifen kannst.
Fände ich sauberer, als da mit Singletons zu arbeiten.
-
drakon schrieb:
Nexus schrieb:
Master (verwaltet alles)
im Moment noch alles statische Member,
soll jedoch noch Singleton werden
sozusagen global, von überall zugreifbarHmm. Das würde ich schon mal anderst lösen. Mach da lieber ein Objekt draus, dass du dann direkt in der main initialisierst und hald einen Zeiger, oder eine Referenz an die restlichen Klassen übergibst, damit du darauf zugreifen kannst.
Fände ich sauberer, als da mit Singletons zu arbeiten.
Das hättest du anders zitieren sollen. So macht es den Eindruck, als wollte Nexus sich als Dichter betätigen.
-
camper schrieb:
Das hättest du anders zitieren sollen. So macht es den Eindruck, als wollte Nexus sich als Dichter betätigen.

Entweder ich verstehe dich falsch, oder du hast ein Smylie vergessen.
-
drakon schrieb:
Fände ich sauberer, als da mit Singletons zu arbeiten.
Ich finde das hört sich schon ziemlich nach Singleton an. Überall Zeiger rumzureichen finde ich da viel viel ekliger.
Nexus schrieb:
Grundsätzlich wäre fast alles mit Vorwärtsdeklarationen und Zeigern als Member zu lösen.
Mach das bloß nicht. Ist imo unsinning, genauso mit Smartpointern. Ich würde nie "reine" Membervariablen gegen Kompilierzeit eintauschen.
-
drakon schrieb:
camper schrieb:
Das hättest du anders zitieren sollen. So macht es den Eindruck, als wollte Nexus sich als Dichter betätigen.

Entweder ich verstehe dich falsch, oder du hast ein Smylie vergessen.
Du irrst, indem du recht hast.
-
camper schrieb:
drakon schrieb:
camper schrieb:
Das hättest du anders zitieren sollen. So macht es den Eindruck, als wollte Nexus sich als Dichter betätigen.

Entweder ich verstehe dich falsch, oder du hast ein Smylie vergessen.
Du irrst, indem du recht hast.

-
camper schrieb:
Das hättest du anders zitieren sollen. So macht es den Eindruck, als wollte Nexus sich als Dichter betätigen.
Dass man es als Gedicht erkennen könnte, war eigentlich nie meine Absicht - aber meine stichwortartige Beschreibung kommt da halt schon nahe

Badestrand schrieb:
drakon schrieb:
Fände ich sauberer, als da mit Singletons zu arbeiten.
Ich finde das hört sich schon ziemlich nach Singleton an. Überall Zeiger rumzureichen finde ich da viel viel ekliger.
Nexus schrieb:
Grundsätzlich wäre fast alles mit Vorwärtsdeklarationen und Zeigern als Member zu lösen.
Mach das bloß nicht. Ist imo unsinning, genauso mit Smartpointern. Ich würde nie "reine" Membervariablen gegen Kompilierzeit eintauschen.
Puh, endlich mal jemand, der das Ganze so sieht wie ich

Was das Zeiger-Rumreichen betrifft, wäre das wirklich total eklig, da dann jede Funktion, die entweder Eingaben oder Bildschirmausgaben handhabt, noch einen zusätzlichen Parameter hätte (oder jede Klasse noch einen zusätzlichen Zeiger). Zum Teil wären auch noch ganz andere Funktionen betroffen, weil z.B. auch eine Instanz einer Zufallsgeneratorklasse inMastersteht. Das würde sich hinten und vorne nicht lohnen.Naja, ich hab mich einfach gewundert; wenn man beginnt, modular zu programmieren, sind plötzlich wieder Unmengen "Hilfsmittel" erforderlich, dass man es nur schafft, nicht schlechtere Voraussetzungen als bei nicht-modularer Programmierung zu haben... Wahrscheinlich nehm ich doch lieber die längere Kompilierzeit in Kauf...
Ach ja, noch was: Gibt es so was wie meine Masterklasse häufig in der Praxis? Ich finde es sehr praktisch, da ich in
main()gerade mal zwei Anweisungen habe. Das Unschöne ist vielleicht, dassMasternur statische Member und Methoden hat, aber wie gesagt mach ich da vielleicht noch ein Singleton draus.
-
Nexus schrieb:
Naja, ich hab mich einfach gewundert; wenn man beginnt, modular zu programmieren, sind plötzlich wieder Unmengen "Hilfsmittel" erforderlich, dass man es nur schafft, nicht schlechtere Voraussetzungen als bei nicht-modularer Programmierung zu haben... Wahrscheinlich nehm ich doch lieber die längere Kompilierzeit in Kauf...
Was du aber bedenken solltest: In der Anfangsphase wo man viele Interfaces aendert und oft nur mit mockups herumwirft und jedweder code total instabil (aus aenderungssicht - nicht bug-sicht) ist, sind mehrere uebersetzungseinheiten natuerlich doof, da so das linken laenger dauert.
sobald du aber einen stand erreicht hast wo ein teil des codes interface-stabil ist, sinken die compiletimes rapide. denk einfach nur daran, dass du ja einen grossteil der dateien nachdem sie einmal fertig sind kaum noch anfasst. am anfang, wo du aber jede datei dauernd aenderst - ists natuerlich etwas stoerend.
und auch aktuell, also trotz grossem recompile kann ich mir nicht vorstellen dass die compilezeiten soviel groesser sind. denn alles was mehr gemacht wird sind mehrmals header parsen -> und das laesst sich mit precompiled headers sogar umgehen.
wenn du compilezeiten extrem senken willst, dann schau dir das pimpl-idiom an. ich bin kein grosser fan davon, wenn es aber um reduzierung von abhaengigkeiten geht ist es einfach super.
20 sekunden recompile deutet auf entweder einen sehr langsamen rechner hin oder auf viel code. und bei viel code: warum aenderst du die zentralen stellen dauernd?
-
und bei viel code: warum aenderst du die zentralen stellen dauernd?
:p
Weisst du, was mir da manchmal passiert?
Ich schaue mal ein wenig im Core, ob alles stimmt usw. Und aus reiner Gewohnheit mache ich da auch aus dem Reflex raus ctrl+s.. Und ich bemerke meinen Fehler auch sogleich beim drücken. *grml*
Lösung: ctrl+F7 und mal was trinken gehen.
-
drakon schrieb:
und bei viel code: warum aenderst du die zentralen stellen dauernd?
:p
Weisst du, was mir da manchmal passiert?
Ich schaue mal ein wenig im Core, ob alles stimmt usw. Und aus reiner Gewohnheit mache ich da auch aus dem Reflex raus ctrl+s.. Und ich bemerke meinen Fehler auch sogleich beim drücken. *grml*
Lösung: ctrl+F7 und mal was trinken gehen.
dann ist dein editor komisch... wenn es keine aenderung gibt, sollte er die datei nicht modifizieren, auch bei einem ctrl+s nicht...
aber klar, sowas passiert mal dass man aus versehen eine zentrale datei unnoetigerweise "aendert" - aber das ist hoffentlich ausnahme und nicht regel

-
dann ist dein editor komisch... wenn es keine aenderung gibt, sollte er die datei nicht modifizieren, auch bei einem ctrl+s nicht...
aber klar, sowas passiert mal dass man aus versehen eine zentrale datei unnoetigerweise "aendert" - aber das ist hoffentlich ausnahme und nicht regel
Meist ist hald so, dass ich dennoch eine Taste drücke und dann speichere..

Ja, ich schaue schon, dass das nicht all zu oft passiert. Es ist in der Tat recht mühsam nacher alles neu compilen zu müssen..

-
Nexus schrieb:
Nun ist es so, dass Kollisionsabfragen in
Objectpassieren, d.h. da werden schon mal die KlassenTileundGame(aktuelle Oberfläche) benötigt.Also wenn schon würde ich Tile auch von Object ableiten und somit Tile auch als Kollisionobjekt ansehen. In der Klasse Object gibt es eine Methode, welche ein anderes Object annimmt und dann die Kollisionsabfrage ausführt.
Nexus schrieb:
Bei den Klassen
Player,EnemyundWeaponmuss ich wahrscheinlich eine Dreiecksabhängigkeit einrichten, weil sich diese Klassen jeweils gegenseitig beeinträchtigen können.Wird wohl nicht nötig sein, zumindest nicht in den Headern. Da kannst du überall Forward Declaration anwenden.
Du wirst ja wohl kaum ein Player, Enemy oder Weapon kopieren wollen. Von überall her, wo du eine Instanz auf den Player, Enemy oder Weapon übergeben tust, soll es ja auch die gleiche sein. Also wirst du in erster Linie Zeiger herumreichen. Vor allem auch, weil du die Spieler, Gegner und Waffen auf dem Heap erstellen wirst. Und es sicher Möglichkeiten gibt, dass es keine Gegner, keine Waffen oder keine Spieler hat, also Nullzeiger.Nexus schrieb:
Gameselber benötigt natürlich alle Klassen, die Typen der Member sind.Kommt drauf an, wie zentral du Game gestaltest. Vergiss nicht, dass man in Module auslagern sollte. Man kann auch Funktionsweisen oder Handlungen in Objekte packen, auch Beschreibungen sind möglich. Oder anders ausgedrückt:
Nicht nur Nomen sind Objekte, sondern auch Adjektive, Verben, Adverben, Pronomen und was es sonst noch alles gibt
Nexus schrieb:
Was ein Problem darstellt, ist die Tatsache, dass z.B.
Playerindirekt über die BasisklasseObjectaufGameZugriff haben muss, um z.B. Kollisionsabfragen mit den Tiles zu handhaben.Das leuchtet mir überhaupt nicht ein. Wieso kann der Player nicht direkt mit den Tiles interagieren? Game sollte höchstens den Player verschieben oder sowas ähnliches.
Nexus schrieb:
Was wiederum nicht geht, weil im Konstruktor von
Gameja auchPlayerkonstruiert wird, und derthis-Zeiger vonGamezu diesem Zeitpunkt noch auf die BasisklasseSurfaceverweist...
Ah, da haben wir wieder so ein Problem. Du erstellst den Player als Eigenschaft von Game. Aber ist er das wirklich? Ich finde das irgendwie nicht. Einem Game Objekt sollte ein Player Objekt zugewiesen sein, vielleicht auch mehrere, aber es sollte den Player sicher nicht besitzen.
Ein Spieler sollte meiner Meinung nach anderswo erstellt und dem Spiel dann übergeben werden.Nexus schrieb:
Grundsätzlich wäre fast alles mit Vorwärtsdeklarationen und Zeigern als Member zu lösen. Was ich jedoch unnötig finde, weil auf ich eigene Speicherverwaltung lieber verzichte, wo sichs vermeiden lässt (und wie gesagt auf The Big Three).
Auf "The Big Three" verzichtest du, in dem du Zeiger verwendest. Aber wenn du möglichst Zeiger meiden willst, weil du Angst vor der eigenen Speicherverwaltung hast, dann solltest du vielleicht eine andere Sprache wählen? C# oder Java? Bei C++ sind ja gerade solche Dinge ganz entscheidend und vieles wird darüber aufgebaut. Man verzichtet einfach auf einen ganz entscheidenden Teil der Sprache, wenn man keine eigene Speicherverwaltung einsetzt. Shared Pointer und ähnliches sollten nur Hilfskonstrukte für gewisse Situationen sein, aber nicht für alles.
Ich hoffe ich konnte dir etwas helfen. Vor allem überwinde deine Angst vor der Speicherverwaltung!
Eine "Master-Option"-Klasse, mache ich höchst selten. Das hat den einfach Grund, dass wenn man die Sache genügend aufsplittet in Objekte und Module, es irgendwann keine Option geben wird, welche für alle oder mehr als die Hälfte gilt. Man sollte zudem, meiner Meinung nach, nicht in solchen Dimensionen denken. Wenn eine Option verändert wird, dann betrifft das direkt entsprechende Objekte und dort sollten die Eigenschaften entsprechend gesetzt werden. Man sollte nicht Optionen zusammenschaufeln und in eine gemeinsame Klasse verfrachten. Ausser das sie Optionen sind, haben diese nämlich nichts gemeinsam!
Grüssli
-
Vielen Dank für die hilfreichen Antworten! Es ist wirklich nett, wie ihr euch Zeit nehmt!
Shade Of Mine schrieb:
Was du aber bedenken solltest: In der Anfangsphase [...] sind mehrere uebersetzungseinheiten natuerlich doof, da so das linken laenger dauert.
sobald du aber einen stand erreicht hast wo ein teil des codes interface-stabil ist, sinken die compiletimes rapide.Hmm, das hat in der Tat was, daran hab ich eigentlich gar nicht gedacht

Shade Of Mine schrieb:
20 sekunden recompile deutet auf entweder einen sehr langsamen rechner hin oder auf viel code. und bei viel code: warum aenderst du die zentralen stellen dauernd?
Nein, ich denke nicht, dass der Rechner zu langsam ist. Eher die zweite Möglichkeit. Und wie gesagt lassen sich häufige Änderungen nicht immer umgehen, weil man plötzlich Details bemerkt oder neue Features hinzufügen will, die man vorher noch nicht eingeplant hat. Aber es stimmt schon, ich sollte mich vielleicht ein bisschen einschränken mit zentralen Änderungen.
drakon schrieb:
Lösung: ctrl+F7 und mal was trinken gehen.

Hast du demnach auch das Problem mit langen Kompilierzeiten? Oder sind deine Projekte einfach so riesig?

Dravere schrieb:
Also wenn schon würde ich Tile auch von Object ableiten und somit Tile auch als Kollisionobjekt ansehen. In der Klasse Object gibt es eine Methode, welche ein anderes Object annimmt und dann die Kollisionsabfrage ausführt.
Nein, dann würde es von der Logik her keinen Sinn mehr machen. Ein Tile ist ja ein fester Bestandteil der Welt, d.h. es kann nur rasterförmig ausgerichtet sein (eben in einer Tilemap) und bewegt sich nicht. Objekte wie der Spieler oder abgefeuerte Waffen hingegen sind mobil und reagieren auf Kollision mit der Welt (also den Tiles).
Dravere schrieb:
Nexus schrieb:
Bei den Klassen
Player,EnemyundWeaponmuss ich wahrscheinlich eine Dreiecksabhängigkeit einrichten, weil sich diese Klassen jeweils gegenseitig beeinträchtigen können.Wird wohl nicht nötig sein, zumindest nicht in den Headern. Da kannst du überall Forward Declaration anwenden.
Du wirst ja wohl kaum ein Player, Enemy oder Weapon kopieren wollen. Von überall her, wo du eine Instanz auf den Player, Enemy oder Weapon übergeben tust, soll es ja auch die gleiche sein. Also wirst du in erster Linie Zeiger herumreichen. Vor allem auch, weil du die Spieler, Gegner und Waffen auf dem Heap erstellen wirst. Und es sicher Möglichkeiten gibt, dass es keine Gegner, keine Waffen oder keine Spieler hat, also Nullzeiger.Ja, also momentan verwalte ich diese Klassen in STL-Containern innerhalb von
Game(die eben leer sind falls kein entsprechendes Objekt existiert). Falls es etwas rumzureichen gibt, benutze ich eher Referenzen. Aber das muss ich eh noch ausbauen, jetzt wird z.B. auf die Tiles statt einfacher Übergabe relativ kompliziert zugegriffen -dynamic_cast<Game*>(Master::MySurface)->Tiles(natürlich noch abgekürzt)
Dravere schrieb:
Nexus schrieb:
Was ein Problem darstellt, ist die Tatsache, dass z.B.
Playerindirekt über die BasisklasseObjectaufGameZugriff haben muss, um z.B. Kollisionsabfragen mit den Tiles zu handhaben.Das leuchtet mir überhaupt nicht ein. Wieso kann der Player nicht direkt mit den Tiles interagieren? Game sollte höchstens den Player verschieben oder sowas ähnliches.
Wie ich schon weiter oben angedeutet habe, hab ich das momentan noch relativ kompliziert (eher global) gelöst. Aber wenn der Spieler direkt mit den Tiles interagieren sollte, müsste ja bei dessen Erstellung im Konstruktor eine Referenz auf die Tiles übergeben werden, und auf Gegner genauso. Gegner wiederum hätten Referenzen auf Spieler und Tiles, was zu Konflikten führt, wenn eines der Objekte noch nicht konstruiert wurde und eine Referenz darauf übergeben wird.
Dravere schrieb:
Nexus schrieb:
Gameselber benötigt natürlich alle Klassen, die Typen der Member sind.Kommt drauf an, wie zentral du Game gestaltest.
Dravere schrieb:
Nexus schrieb:
Was wiederum nicht geht, weil im Konstruktor von
Gameja auchPlayerkonstruiert wird, und derthis-Zeiger vonGamezu diesem Zeitpunkt noch auf die BasisklasseSurfaceverweist...
Ah, da haben wir wieder so ein Problem. Du erstellst den Player als Eigenschaft von Game. Aber ist er das wirklich? Ich finde das irgendwie nicht. Einem Game Objekt sollte ein Player Objekt zugewiesen sein, vielleicht auch mehrere, aber es sollte den Player sicher nicht besitzen.
Ein Spieler sollte meiner Meinung nach anderswo erstellt und dem Spiel dann übergeben werden.Daran hab ich auch relativ lange überlegt. Ich hab auch eine Klasse
Mapin Betracht gezogen (für ein Level/eine Karte). Schlussendlich hab ich mich fürGameentschieden, weil das so schön zu der Polymorphie mit denSurfaces (verschiedene Menüoberflächen) passt (Bei mir ist es so, dass es verschiedene gleichwertige Oberflächen gibt (Hauptmenü, Info, Hilfe, Editor, Game)). Zudem ist es auch logisch sinnvoll, wenn Spieler, Gegner, Tiles gerade erstellt werden, wenn man das Spiel startet (also eine Instanz vonGameerstellt). Von mir aus gesehen macht es keinen Sinn, Spieler etc. separat zu erstellen und anGamezu übergeben, weil sie ja unmittelbar daran gebunden sind (welchen Sinn hat ein Spieler ohne Spiel?)
Dravere schrieb:
Auf "The Big Three" verzichtest du, in dem du Zeiger verwendest.
Ich meinte, ich müsse Copy-Ctor, Dtor und Op= selber implementieren, wenn ich Zeiger als Member habe - was ich bei Stackvariablen nicht muss.
Dravere schrieb:
Aber wenn du möglichst Zeiger meiden willst, weil du Angst vor der eigenen Speicherverwaltung hast, dann solltest du vielleicht eine andere Sprache wählen? C# oder Java? Bei C++ sind ja gerade solche Dinge ganz entscheidend und vieles wird darüber aufgebaut. Man verzichtet einfach auf einen ganz entscheidenden Teil der Sprache, wenn man keine eigene Speicherverwaltung einsetzt. Shared Pointer und ähnliches sollten nur Hilfskonstrukte für gewisse Situationen sein, aber nicht für alles.
Vielleicht hab ich einen falschen Eindruck erweckt, es ist ja nicht so, dass ich ängstlich vor Zeigern und eigener Speicherverwaltung zurückschrecke - ich hab immer noch genügend Orte, wo ich mit
newunddeletearbeite
Ich meinte vielmehr, dass eigene Speicherverwaltung halt viele Fehlerrisiken birgt und man viel stärker aufpassen muss. Ich sehe deshalb automatische Speicherverwaltung als "Geschenk" an, das auch eingesetzt werden sollte, wenn es geht (im Übrigen ist es auch noch schneller). Wie Badestrand in seinem Post gesagt hat, würde ich Stackvariablen gegen "praktische" Vorwärtsdeklarationen lieber nicht eintauschen.Dravere schrieb:
Eine "Master-Option"-Klasse, mache ich höchst selten. Das hat den einfach Grund, dass wenn man die Sache genügend aufsplittet in Objekte und Module, es irgendwann keine Option geben wird, welche für alle oder mehr als die Hälfte gilt. Man sollte zudem, meiner Meinung nach, nicht in solchen Dimensionen denken. Wenn eine Option verändert wird, dann betrifft das direkt entsprechende Objekte und dort sollten die Eigenschaften entsprechend gesetzt werden. Man sollte nicht Optionen zusammenschaufeln und in eine gemeinsame Klasse verfrachten. Ausser das sie Optionen sind, haben diese nämlich nichts gemeinsam!
Hm... Was meinst du genau mit "Optionen"? Ich hab das Gefühl, wir stellen uns unter einer Masterklasse nicht ganz das Gleiche vor.
Bei mir ist es so, dass nur die ganz zentralen Dinge dort geregelt werden. Der Master ist bezeichnenderweise (sorry, wusste keinen besseren Namen als "Master" :)) die oberste Direktive, die alles regelt. Er überwacht den Verlauf des Programms und gibt den einzelnen Oberflächen (abstrakte BasisklasseSurface) die Kontrolle, wenn eine entsprechende Eingabe getätigt wurde. Abgesehen von der Kontrollfunktion besitzt er noch einige für Grafikroutinen benötigte Variablen (z.B. das Fenster, oder Event für Eingaben, Schriftarten), dient also auch als Ersatz für globale Variablen.Aber ganz dezentral kannst du das Programm ja auch nicht steuern (ich nehme nicht an, dass du sehr vieles in der
main()-Funktion stehen hast), oder? Ich hab bei mir gerade mal zwei Funktionen inmain(), nämlich eine für die Initialisierung und eine für den ständigen Verlauf.So, wieder viel geschrieben

Ich hoffe, ihr könnt meine Sichtweise und mein Design einigermassen nachvollziehen - bei irgendwelchen Unklarheiten sofort fragen
-
Nexus schrieb:
Nein, dann würde es von der Logik her keinen Sinn mehr machen. Ein Tile ist ja ein fester Bestandteil der Welt, d.h. es kann nur rasterförmig ausgerichtet sein (eben in einer Tilemap) und bewegt sich nicht. Objekte wie der Spieler oder abgefeuerte Waffen hingegen sind mobil und reagieren auf Kollision mit der Welt (also den Tiles).
Das bedeutet für mich eigentlich nur, dass du noch nicht genügend aufgesplittet hast. Dann gibt es eben Kollisionsobjekte und bewegliche Objekte. Ein Player wäre demnach beides, ein Tile nur ein Kollisionsobjekt. Durch die Auftrennung passiert dann auch der Logikfehler nicht mehr.
Nexus schrieb:
Ja, also momentan verwalte ich diese Klassen in STL-Containern innerhalb von
Game(die eben leer sind falls kein entsprechendes Objekt existiert). Falls es etwas rumzureichen gibt, benutze ich eher Referenzen. Aber das muss ich eh noch ausbauen, jetzt wird z.B. auf die Tiles statt einfacher Übergabe relativ kompliziert zugegriffen -dynamic_cast<Game*>(Master::MySurface)->Tiles(natürlich noch abgekürzt)
Das ist nicht nur kompliziert, sondern sogar langsam! Ein dyamic_cast ist teuer, erst recht, wenn er immer wieder ausgeführt wird. Zudem zeigt ein dynamic_cast, wie so oft, darauf hin, dass ein Designfehler vorliegt. Es sollte ohne gehen!
Ein Spieler ist nicht da, um sich zu bewegen, Regeln sind dafür da. Und das Spiel, welches die Tiles beinhaltet und auch den Spieler hat, sollte mit den Regeln den Spieler verschieben. Es kann also alles intern gelöst werden.Nexus schrieb:
Wie ich schon weiter oben angedeutet habe, hab ich das momentan noch relativ kompliziert (eher global) gelöst.
Ich würde eher sagen, sehr vereinfacht und global gemacht, anstatt genügend differenziert

Nexus schrieb:
Aber wenn der Spieler direkt mit den Tiles interagieren sollte, müsste ja bei dessen Erstellung im Konstruktor eine Referenz auf die Tiles übergeben werden, und auf Gegner genauso.
Nein. Ich hätte da eher an eine Location gedacht. Also sowas wie:
class Player { // ... private: Tile* m_location; // ... public: void set_location(Tile* location) { m_location = location; }; Tile* get_location() const { return m_location; }; };Wobei man das sicher ausbauen könnte. Vielleicht in das bewegliche Objekt nehmen. Zudem dann überprüfen, ob es eine Kollision gibt oder was auch immer, bzw. ob der Player dort stehen kann. Wenn nicht, muss das Spiel mit den Regeln richtig reagieren und den Spieler anders verschieben. Die Regeln können ja auch als Klassen eingebaut werden. Dann wird einem Regelobjekt der Spieler übergeben und gesagt, er müsse zum Punkt x,y,z gehen. Die Regel probiert das auszuführen und wenn es nicht geht, übergibt sie die Sache anderen Regeln usw.
Hier werden dann auch nur Zeiger durchgereicht oder Referenzen.Nexus schrieb:
Gegner wiederum hätten Referenzen auf Spieler und Tiles, was zu Konflikten führt, wenn eines der Objekte noch nicht konstruiert wurde und eine Referenz darauf übergeben wird.
Wieso hat der Gegner Referenzen auf Spieler? Und wieso überhaupt Referenzen? Wieso nicht Pointer und dann die Übergabe eines Nullpointers erlauben? Ein Spieler könnte keine Waffe haben, ein Spieler oder Gegner kann nirgends stehen.
Es muss nicht nur das Sinn machen, was der Spieler zu sehen bekommt. Die Funktionsweise eines Spieles sollte mehr können

Wieso willst du im übrigen alles mit Referenzen lösen? Referenzen kann man nur ein einziges Mal initialisieren, danach kannst du nie mehr ein anderes Objekt zuweisen. Wenn du deinen Klassen immer nur Referenzen übergibst als Member, dann bindest du sie viel zu stark an die entsprechenden Objekte. Das ist meistens gar nicht nötig. Zudem dürfte es Probleme beim "normalen" Kopieren geben.
Nexus schrieb:
Daran hab ich auch relativ lange überlegt. Ich hab auch eine Klasse
Mapin Betracht gezogen (für ein Level/eine Karte). Schlussendlich hab ich mich fürGameentschieden, weil das so schön zu der Polymorphie mit denSurfaces (verschiedene Menüoberflächen) passt (Bei mir ist es so, dass es verschiedene gleichwertige Oberflächen gibt (Hauptmenü, Info, Hilfe, Editor, Game)). Zudem ist es auch logisch sinnvoll, wenn Spieler, Gegner, Tiles gerade erstellt werden, wenn man das Spiel startet (also eine Instanz vonGameerstellt). Von mir aus gesehen macht es keinen Sinn, Spieler etc. separat zu erstellen und anGamezu übergeben, weil sie ja unmittelbar daran gebunden sind (welchen Sinn hat ein Spieler ohne Spiel?)
Das der Spieler gerade im Warteraum ist? Bei den Optionen? In den Highscores? Jedenfalls nicht im Spiel.
Zudem sollte man viel eher anschauen, was für Vorteile bringt einem eine entsprechende Kapselung. Sollte wirklich das Spiel den Spieler erstellen? Ich würde eher sagen, das Spiel soll mit dem Spieler interagieren. Erstellen tut sich der Spieler wahrscheinlich selbst, zum Beispiel über ein Profil oder ähnliches. Dann kann er den Namen selber festlegen und die Farbe seiner Spielfigur, oder sowas in der Art
Nexus schrieb:
Ich meinte, ich müsse Copy-Ctor, Dtor und Op= selber implementieren, wenn ich Zeiger als Member habe - was ich bei Stackvariablen nicht muss.
Also hier liegen irgendwie mehrere Missverständnisse vor:
1. Eine Deep-Copy ist nicht immer nötig. Manchmal will man wirklich nur den Zeiger kopieren und nicht das, worauf der Zeiger zeigt. Siehe es am Beispiel von Player oben. Die Location sollte dort als Zeiger kopiert werden, also eine "flache" Kopie. Man möchte ja auf das gleiche Tile verweisen.
2. Wenn du Zeiger verwendest, dann musst du ja nicht mehr kopieren! Wenn du dem Spiel ein Zeiger auf einen Spieler übergibst, dann soll der Zeiger gespeichert werden und nur der Zeiger weitergegeben werden (oder allenfalls eine Referenz). Der Spieler wird gar nie kopiert, wodurch er keiner der grossen Drei benötigt.
3. Stackvariablen? Bei Stackvariablen brauchst du das je nach dem trotzdem. Und Membervariablen kommen nicht unbedingt auf den Stack! Die kommen dorthin, wo die Klasse erstellt wurde, also womöglich auch auf den Heap. Der Stack ist sowieso begrenzt, man sollte nicht zu viel dort drauf setzen.Nexus schrieb:
Vielleicht hab ich einen falschen Eindruck erweckt, es ist ja nicht so, dass ich ängstlich vor Zeigern und eigener Speicherverwaltung zurückschrecke - ich hab immer noch genügend Orte, wo ich mit
newunddeletearbeite
Ich meinte vielmehr, dass eigene Speicherverwaltung halt viele Fehlerrisiken birgt und man viel stärker aufpassen muss. Ich sehe deshalb automatische Speicherverwaltung als "Geschenk" an, das auch eingesetzt werden sollte, wenn es geht ...Du hast doch Angst vor den Fehlerrisiken. Aber wenn du sie nie antriffst, dann wirst du immer Angst davor haben. Wenn du automatische Speicherverwaltung als ein "Geschenk" siehst, dann bist du, wie gesagt, in der falschen Sprache. Ich sehe viel eher die nicht automatische Speicherverwaltung als ein wundervolles Geschenk! Sicher, man kann sich damit den ganzen Körper wegsprengen, aber wenn man ein wenig Übung hat, und die sollte man sich aneignen, dann ist das kein Problem mehr.
Nexus schrieb:
... (im Übrigen ist es auch noch schneller). Wie Badestrand in seinem Post gesagt hat, würde ich Stackvariablen gegen "praktische" Vorwärtsdeklarationen lieber nicht eintauschen.
Stackvariablen können nur temporär erstellt werden oder müssen kopiert werden. Klar bei der Erstellung ist der Stack schneller, wenn du aber danach immer kopieren musst, ist die Übergabe eines Zeigers deutlich schneller, da du auf dem Heap nur ein einziges Mal allokierst.
Nexus schrieb:
Hm... Was meinst du genau mit "Optionen"? Ich hab das Gefühl, wir stellen uns unter einer Masterklasse nicht ganz das Gleiche vor.
Scheint, dass du noch viel mehr darunter verstehst als ich.
Nexus schrieb:
Bei mir ist es so, dass nur die ganz zentralen Dinge dort geregelt werden.
Alles?

Nexus schrieb:
Der Master ist bezeichnenderweise (sorry, wusste keinen besseren Namen als "Master" :)) die oberste Direktive, die alles regelt. Er überwacht den Verlauf des Programms und gibt den einzelnen Oberflächen (abstrakte Basisklasse
Surface) die Kontrolle, wenn eine entsprechende Eingabe getätigt wurde. Abgesehen von der Kontrollfunktion besitzt er noch einige für Grafikroutinen benötigte Variablen (z.B. das Fenster, oder Event für Eingaben, Schriftarten), dient also auch als Ersatz für globale Variablen.Eindeutig, alles

Schon nur das "Ersatz für globale Variablen" zeigt auf, dass da was nicht stimmen kann. Globale Variablen braucht man nicht und man braucht auch kein Ersatz dafür. Deshalb ist das Singleton-Pattern auch so umstritten, weil es oft als Ersatz für globale Variablen herangezogen wird, was es nicht sein sollte.Nexus schrieb:
Aber ganz dezentral kannst du das Programm ja auch nicht steuern (ich nehme nicht an, dass du sehr vieles in der
main()-Funktion stehen hast), oder? Ich hab bei mir gerade mal zwei Funktionen inmain(), nämlich eine für die Initialisierung und eine für den ständigen Verlauf.Ich habe normalerweise kaum etwas in der Main, weil ich eben dezentral programmiere. Wenn ich viel in die Main schreiben würde, dann würde diese zu einem zentralen Punkt werden, nicht?
Aber man kann dezentral programmieren, man muss nur genügend aufsplitten. Gerade deine Master und Game Klassen sind für mich eindeutige Hinweise darauf, dass du noch nicht genügend gesplittet hast.
Aber mal ein wenig allgemeiner und ich bitte dich, die Frage nicht böse zu verstehen. Wieviel hast du in C++ schon gemacht? Und wieviel hast du schon zu C++ gelesen, welche Bücher? Versteh mich bitte nicht falsch, aber ich habe ein wenig das Gefühl, dass ein Spiel zu programmieren, vielleicht eine Stufe zu hoch ist.
So, das war nun wirklich ein riesiger Post. Ich könnte noch mehr schreiben, muss aber essen gehen und möchte auch noch selber heute was programmieren

Grüssli
-
Versteh mich bitte nicht falsch, aber ich habe ein wenig das Gefühl, dass ein Spiel zu programmieren, vielleicht eine Stufe zu hoch ist.
So, wie ich Nexus einschätze, reicht sein können für ein einfaches Spiel allemale. Sein Problem ist hald, wie ich vermute, dass er sich im vornherein zu viele Gedanken macht.
Imhos sollte er einfach mal anfangen und dann wird er früher, oder später merken, was an dem Design Mist ist. Das prägt sich dann auch mehr ein und man kennt Alternativen. (Weil es hier vlt. schlecht war, heisst ja nicht, dass es wo anderst nicht mal von Nutzen sein kann).Hast du demnach auch das Problem mit langen Kompilierzeiten? Oder sind deine Projekte einfach so riesig?
Es ist mittlerweile Recht gross geworden. (Die grösse .cpp hat ca. 1500 Zeilen. )

Um da jetzt mal den Vergleich zu machen. Dort ist mein gesamtes GUI System drin. Und das habe ich imho schon recht gut gemacht, aber ich sage dir, wenn ich, dass ich am liebsten alles nochmal neu schreiben würde, weil so richtig gefällt mir das ganze eigl. nicht. Aber ich habe auch keine Lust das ganze nochmal zu machen, vor allem, da ich dann wahrscheinlich auch nicht 100% zufrieden bin.
- Ich denke mal, dass es da vielen gleich geht. Man ist irgendwie nie richtig zufriede mit dem, was man hat. 
-
Dravere schrieb:
Das bedeutet für mich eigentlich nur, dass du noch nicht genügend aufgesplittet hast. Dann gibt es eben Kollisionsobjekte und bewegliche Objekte. Ein Player wäre demnach beides, ein Tile nur ein Kollisionsobjekt. Durch die Auftrennung passiert dann auch der Logikfehler nicht mehr.
Ja, so könnte man es auch sehen. Doch ist etwas erst richtig objektorientiert, wenn es so weit wie möglich aufgesplittet ist? Ich hab irgendwie das Gefühl, ich würde mit dieser Einstellung nur das Projekt unnötig unübersichtlich machen (im Moment kann ich mir mein Design relativ gut vorstellen und es macht für mich auch einigermassen Sinn von der Logik her).
Dravere schrieb:
Das ist nicht nur kompliziert, sondern sogar langsam! Ein dyamic_cast ist teuer, erst recht, wenn er immer wieder ausgeführt wird. Zudem zeigt ein dynamic_cast, wie so oft, darauf hin, dass ein Designfehler vorliegt. Es sollte ohne gehen!
Ein Spieler ist nicht da, um sich zu bewegen, Regeln sind dafür da. Und das Spiel, welches die Tiles beinhaltet und auch den Spieler hat, sollte mit den Regeln den Spieler verschieben. Es kann also alles intern gelöst werden.Du meinst also (falls ich das mit dem Spieler als Eigenschaft von
Gamedurchziehe), dass der Spieler sich selbst nicht um die eigene Bewegung, Kollisionsabfrage, etc. kümmern sollte? Was sind denn seine Aufgaben? Ich hab bisher eher gedacht, jede Klasse solle nur das tun, was auch ihrem Aufgabenbereich entspricht. Also fürGamewäre das meiner Meinung nach, dem Spieler die Kontrolle weiterzugeben (FunktionPlayer::Think()) und nicht alles selbstständig in die Hand zu nehmen.
Und was dendynamic_castbetrifft: Ich muss es noch irgendwie hinkriegen, dass man direkt einen Zeiger aufGamehat (also höchstens am Anfang oder gar nie dynamisch gecastet). Oder eben gar keinen Zeiger aufGameselber und es mit Locations oder ähnlich lösen.Dravere schrieb:
Nein. Ich hätte da eher an eine Location gedacht. [...] Wobei man das sicher ausbauen könnte. Vielleicht in das bewegliche Objekt nehmen. Zudem dann überprüfen, ob es eine Kollision gibt oder was auch immer, bzw. ob der Player dort stehen kann. Wenn nicht, muss das Spiel mit den Regeln richtig reagieren und den Spieler anders verschieben. Die Regeln können ja auch als Klassen eingebaut werden. Dann wird einem Regelobjekt der Spieler übergeben und gesagt, er müsse zum Punkt x,y,z gehen. Die Regel probiert das auszuführen und wenn es nicht geht, übergibt sie die Sache anderen Regeln usw.
Hier werden dann auch nur Zeiger durchgereicht oder Referenzen.So was ähnliches wie eine Location (wusste nicht, dass das so heisst) hab ich mir auch schon überlegt. Nur stellte ich mir die Frage, wann ich diese initialisieren sollte, wenn nicht schon zu Beginn? Der Spieler muss ja, sobald er im Spiel ist, mit den Tiles interagieren können.
Dravere schrieb:
Wieso hat der Gegner Referenzen auf Spieler? Und wieso überhaupt Referenzen? Wieso nicht Pointer und dann die Übergabe eines Nullpointers erlauben? Ein Spieler könnte keine Waffe haben, ein Spieler oder Gegner kann nirgends stehen.
Wieso willst du im übrigen alles mit Referenzen lösen? Referenzen kann man nur ein einziges Mal initialisieren, danach kannst du nie mehr ein anderes Objekt zuweisen. Wenn du deinen Klassen immer nur Referenzen übergibst als Member, dann bindest du sie viel zu stark an die entsprechenden Objekte. Das ist meistens gar nicht nötig. Zudem dürfte es Probleme beim "normalen" Kopieren geben.Naja, dabei bin ich davon ausgegangen, dass
Gameeben Container der einzelnen Spielelemente (Gegner, Waffen, Tiles) besitzt und man daher Referenzen übergibt, weil ja immer der gleiche Container referenziert wird und er auch nicht NULL sein kann (im Falle von keinen Gegnern hat man halt einen leeren Container, aber man hat einen).Dravere schrieb:
Das der Spieler gerade im Warteraum ist? Bei den Optionen? In den Highscores? Jedenfalls nicht im Spiel.
Zudem sollte man viel eher anschauen, was für Vorteile bringt einem eine entsprechende Kapselung. Sollte wirklich das Spiel den Spieler erstellen? Ich würde eher sagen, das Spiel soll mit dem Spieler interagieren. Erstellen tut sich der Spieler wahrscheinlich selbst, zum Beispiel über ein Profil oder ähnliches. Dann kann er den Namen selber festlegen und die Farbe seiner Spielfigur, oder sowas in der Art
Hier gehen unsere Vorstellungen schon wieder auseinander

Als Spieler bezeichnete ich nur die Spielfigur selber. Falls ich noch Profile machen würde (was ich momentan nicht vorhabe), würde ich die separat regeln. Und die Spielfigur existiert ja wirklich nur während des Spiels und wird am Ende auch wieder zerstört, also nichts mit Menü, Optionen oder Highscores.Dravere schrieb:
Du hast doch Angst vor den Fehlerrisiken. Aber wenn du sie nie antriffst, dann wirst du immer Angst davor haben. Wenn du automatische Speicherverwaltung als ein "Geschenk" siehst, dann bist du, wie gesagt, in der falschen Sprache. Ich sehe viel eher die nicht automatische Speicherverwaltung als ein wundervolles Geschenk! Sicher, man kann sich damit den ganzen Körper wegsprengen, aber wenn man ein wenig Übung hat, und die sollte man sich aneignen, dann ist das kein Problem mehr.
Wer sagt, dass ich sie nie antreffe? Ich habe wohl schon genügend Stunden mit Fehlersuche wegen eines fehlerhaften Pointers verbracht :p. Ich denke, ich könne im Weiteren auch von mir behaupten, dass ich mich einigermassen mit Speicherverwaltung auskenne. Klar lernt man immer dazu, und mit Zeigern in Verbindung mit Designfragen habe ich vielleicht nicht so viel Erfahrung, aber mich hat es zum Beispiel auch sehr interessiert, eigene dynamische Container zu schreiben (also die Angst vor Zeigern ist es nicht). Ich schätze Zeiger wirklich sehr, manchmal sind sie unheimlich praktisch, aber ich ziehe Stackvariablen generell eher vor. Und eine andere Sprache wäre auch sonst eher nichts für mich, C++ gefällt mir...
Dravere schrieb:
Stackvariablen können nur temporär erstellt werden oder müssen kopiert werden. Klar bei der Erstellung ist der Stack schneller, wenn du aber danach immer kopieren musst, ist die Übergabe eines Zeigers deutlich schneller, da du auf dem Heap nur ein einziges Mal allokierst.
Naja, ich würde natürlich auch Stackvariablen nicht unnötig kopieren, sondern meistens als Referenz übergeben.
Mir kommt gerade noch etwas in den Sinn: Vorwärtsdeklarationen haben auch nur den Vorteil, dass im Header keine anderen Klassen benötigt werden, oder? Spätestens wenn die Zeiger initialisiert werden, müssen ja die Klassen auch bekannt sein (also in der Implementierungsdatei), womit man wieder lange Kompilierzeiten hätte.Dravere schrieb:
Eindeutig, alles

Schon nur das "Ersatz für globale Variablen" zeigt auf, dass da was nicht stimmen kann. Globale Variablen braucht man nicht und man braucht auch kein Ersatz dafür. Deshalb ist das Singleton-Pattern auch so umstritten, weil es oft als Ersatz für globale Variablen herangezogen wird, was es nicht sein sollte.Ich hab gedacht, dass ich mit dem Ausdruck "Ersatz für globale Variablen" nicht auf Verständnis stossen würde

Es geht wie gesagt vor allem um Grafikdinge, die fast überall benötigt werden. Und ich bin der Ansicht, das ist besser halbwegs global (die Klasse bietet ja immer noch Schutz durch Zugriffsspezifizierer) als hunderte von unnötigen und unübersichtlichen Parameterübergaben zu machen. Und nur wegen des Dogmas "globale Variablen und ähnliche Konstrukte sind böse" werde ich sicher nicht darauf verzichten und mich mit ellenlangen Parameterlisten rumschlagen.Dravere schrieb:
Ich habe normalerweise kaum etwas in der Main, weil ich eben dezentral programmiere. Wenn ich viel in die Main schreiben würde, dann würde diese zu einem zentralen Punkt werden, nicht?
Ja, stimmt auch wieder, ich hab zentral eher im Sinne einer alles verwaltenden Klasse gemeint. Aber ich kann mir trotzdem nicht ganz vorstellen, wie du das beispielsweise löst. Hast du in der
main()eine Instanziierung und dann einen Funktionsaufruf, und dann wird alles innerhalb der Klassen weitergegeben?Dravere schrieb:
Aber mal ein wenig allgemeiner und ich bitte dich, die Frage nicht böse zu verstehen. Wieviel hast du in C++ schon gemacht? Und wieviel hast du schon zu C++ gelesen, welche Bücher? Versteh mich bitte nicht falsch, aber ich habe ein wenig das Gefühl, dass ein Spiel zu programmieren, vielleicht eine Stufe zu hoch ist.
Naja, ich programmiere natürlich als Hobby, bin jedoch sehr interessiert. Ich programmiere etwas mehr als zwei Jahre und denke, ich kenne mich auch einigermassen mit den Grundlagen aus. Auch, was Spieleprogrammierung selber betrifft (also algorithmisches), kenne ich mich schon ein bisschen aus. Gerade Kollisionsabfragen (vor allem auf dreieckige Tiles) finde ich nicht gerade einfach. Das Problem ist halt, dass ich bisher noch nicht viel von modularer Programmierung gewusst habe, und deshalb fast alles, was Designfragen betrifft, relativ neu für mich ist. Jedoch bin ich auch bereit, das zu lernen, daran soll das Spiel sicher nicht scheitern. Bisher hab ich schon einige Spiele programmiert, und ein gutes Spiel impliziert meiner Ansicht nach nicht zwangsläufig ein perfektes Design...
P.S. Danke, drakon
Dravere schrieb:
So, das war nun wirklich ein riesiger Post. Ich könnte noch mehr schreiben, muss aber essen gehen und möchte auch noch selber heute was programmieren

Ich bin dir wirklich sehr dankbar, dass du dir extra so viel Zeit für mich nimmst!
Es sind auch andere Meinungen erwünscht
drakon schrieb:
Es ist mittlerweile Recht gross geworden. (Die grösse .cpp hat ca. 1500 Zeilen. )
Um da jetzt mal den Vergleich zu machen. Dort ist mein gesamtes GUI System drin. Und das habe ich imho schon recht gut gemacht, aber ich sage dir, wenn ich, dass ich am liebsten alles nochmal neu schreiben würde, weil so richtig gefällt mir das ganze eigl. nicht. Aber ich habe auch keine Lust das ganze nochmal zu machen, vor allem, da ich dann wahrscheinlich auch nicht 100% zufrieden bin.- Ich denke mal, dass es da vielen gleich geht. Man ist irgendwie nie richtig zufriede mit dem, was man hat.

Das kenne ich nur zu gut. Ich hab ein älteres Spiel von mir (ein Weltraumshooter), von dem man denken könnte, es sei relativ gut gelungen, wenn man es spielt. Wenn man aber den Code anschaut... Der reinste Albtraum

Das war eben noch zu Zeiten, wo ich noch keinen grossen Wert auf Design etc. gelegt habe... Deshalb probier ich jetzt auch von Anfang an, das ein bisschen zu beachten. Im Nachhinein noch den grundlegenden Aufbau neu zu gestalten ist nahezu unmöglich...Hehe, zum ersten Mal die Meldung "Maximal sind 10 Smilies erlaubt". Ich musste daher etwas kürzen...
-
drakon schrieb:
So, wie ich Nexus einschätze, reicht sein können für ein einfaches Spiel allemale. Sein Problem ist hald, wie ich vermute, dass er sich im vornherein zu viele Gedanken macht.
Wie gut kennst du denn Nexus? *das schon immer mal fragen wollte*
drakon schrieb:
Imhos sollte er einfach mal anfangen und dann wird er früher, oder später merken, was an dem Design Mist ist. Das prägt sich dann auch mehr ein und man kennt Alternativen. (Weil es hier vlt. schlecht war, heisst ja nicht, dass es wo anderst nicht mal von Nutzen sein kann).
Dem kann man sicherlich zum Teil zustimmen. Nur ob man die Alternativen kennt ist manchmal fraglich. Man denkt, man kenne sie. Tatsächlich gibt es aber vieles, was man noch nicht kennt.
Nexus schrieb:
Ja, so könnte man es auch sehen. Doch ist etwas erst richtig objektorientiert, wenn es so weit wie möglich aufgesplittet ist?
Naja, jein. Sicher nicht so weit wie möglich, man muss schon eine sinnvolle Grenze finden. Du musst nicht die Moleküle und Atome oder gar die Elektronen, Neutronen und Protonen als Klassen darstellen

Aber ich denke man sollte ruhig ein wenig mehr aufsplitten, als zu wenig. Es vereinfacht einem auch später Ergänzungen zu erstellen. Sonst müsste man plötzlich neue Bereiche aufsplitten und das kann sehr mühsam werden.Nexus schrieb:
Ich hab irgendwie das Gefühl, ich würde mit dieser Einstellung nur das Projekt unnötig unübersichtlich machen (im Moment kann ich mir mein Design relativ gut vorstellen und es macht für mich auch einigermassen Sinn von der Logik her).
Das ist ein Trugschluss, dem ich leider früher auch verfallen war. Durch eine sinnvolle und bessere Aufsplittung schafft man mehr Ordnung im Projekt. Es sind dann mehr Klassen da, aber deutlich besser geordnet und somit steigt die Übersicht.
Nexus schrieb:
Du meinst also (falls ich das mit dem Spieler als Eigenschaft von
Gamedurchziehe), dass der Spieler sich selbst nicht um die eigene Bewegung, Kollisionsabfrage, etc. kümmern sollte?Genau!
Nexus schrieb:
Was sind denn seine Aufgaben? Ich hab bisher eher gedacht, jede Klasse solle nur das tun, was auch ihrem Aufgabenbereich entspricht. Also für
Gamewäre das meiner Meinung nach, dem Spieler die Kontrolle weiterzugeben (FunktionPlayer::Think()) und nicht alles selbstständig in die Hand zu nehmen.Ich weiss nicht ob du die Aufgabe kennst, aber wahrscheinlich schon, da sie sehr verbreitet ist und leider ein völlig falschen Bild vermittelt. Aber ich will es mal daran probieren zu erklären, wie du es machst und wie man es machen könnte.
In vielen Übungsaufgaben für Objektorientierung wird das Beispiel der Formen herangezogen. Man hat ein Quadrat, eine Ellipse, ein Rechteck und ein Kreis. Das sind alles Formen, also Shapes. Also wird eine Grundklasse erstellt, mit dem Namen Shape. Und entsprechend Klassen davon abgeleitet, für das Quadrat, für die Ellipse, für das Rechteck, für den Kreis. Das Quadrat und der Kreis werden meistens sogar vom Rechteck, bzw. der Ellipse abgeleitet.
In der Shape-Klasse hat es einedraw-Methode, welche pur-virtuell ist. Die entsprechenden abgeleiteten Klassen überladen diese Methode und zeichnen sich selbst. Zusätzlich ist jede der vier Formen als Rechteck beschreibbar. Also wird in der Shape-Klasse die Möglichkeit zur Speicherung der Koordinaten hingesetzt. Vielleicht eben schon einmal gehört?Und das ist FALSCH!
Das Problem hier ist, dass das Zeichnen und die Daten in die gleiche Klasse gestopft werden. Dabei sollten diese getrennt werden.
Es gibt somit immer noch eine Klasse Shape mit der pur-virtuellen Methodedraw. Davon abgeleitet wird eine Klasse Rechteck und eine Klasse Ellipse. Man bemerke, dass keine für den Kreis oder das Quadrat abgeleitet werden!
Zusätzlich wird eine neue Klasse eingeführt. Man könnte sie RechteckDaten oder ähnliches nennen, da es eigentlich genau die Daten für ein Rechteck sind, aber wir nennen sie jetzt mal ShapeData. ShapeData beinhaltet die Daten, welche vorhin in der Klasse Shape waren. Nun wird ShapeData an diedraw-Methode übergeben.
Der Kreis oder das Quadrat, werden jetzt einfach dadurch ausgedrückt, dass man ShapeData eben die richtigen Daten übergibt. Man könnte auch noch zwei freie Funktionen machen, welche ein entsprechendes ShapeData erstellen, also für einen Kreis oder ein Quadrat.Sowas kommt auch in den Bereich von MVC (Model - View - Controller), was aber oft nur mit der GUI in Verbindung gebracht wird. Man kann das gleiche Prinzip aber eigentlich auch auf andere Dinge anwenden.
Um zurück auf dein Spiel zu kommen. Dein Spieler wäre somit nur ein Modell. Eine Art von ShapeData. Dann gibt es irgendwo Controllers und zwar Mehrzahl! Diese verändern die Daten des Spielers. Am Ende gibt es vielleicht noch mehrere Views, welche den Spieler darstellen. Der Spieler macht nichts mehr, er ist nur noch ein Zustand!
Game kann, muss aber kein Controller sein. Es ist möglich, dass Game nur Regeln besitzt, also andere Objekte, an welche Game den Spieler übergibt. Und die Regeln sind so vernetzt, dass eine Regel weiss, welche andere Regel in einem gewissen Zustand eintritt. Die Regeln sind somit die Controller. Ein Klasse für die Anzeige des Spielers, wird dann natürlich auch noch benötigt.Nexus schrieb:
So was ähnliches wie eine Location (wusste nicht, dass das so heisst) hab ich mir auch schon überlegt. Nur stellte ich mir die Frage, wann ich diese initialisieren sollte, wenn nicht schon zu Beginn? Der Spieler muss ja, sobald er im Spiel ist, mit den Tiles interagieren können.
Wie man dem sagt, keine Ahnung. Location = Ort, erschien mir logisch

Und nein, der Spieler muss doch nicht zu Beginn wissen, wo er steht. Entweder startet man doch ein neues Spiel, dann wird erst festgelegt, wo er startet und entsprechend wird er hingestellt. Wenn man ein Spiel lädt, wird er wahrscheinlich woanders stehen und entsprechend hingestellt
Nexus schrieb:
Naja, dabei bin ich davon ausgegangen, dass
Gameeben Container der einzelnen Spielelemente (Gegner, Waffen, Tiles) besitzt und man daher Referenzen übergibt, weil ja immer der gleiche Container referenziert wird und er auch nicht NULL sein kann (im Falle von keinen Gegnern hat man halt einen leeren Container, aber man hat einen).Der gleiche Container? Übergibst du den ganzen Container?
Nexus schrieb:
Hier gehen unsere Vorstellungen schon wieder auseinander

Als Spieler bezeichnete ich nur die Spielfigur selber. Falls ich noch Profile machen würde (was ich momentan nicht vorhabe), würde ich die separat regeln. Und die Spielfigur existiert ja wirklich nur während des Spiels und wird am Ende auch wieder zerstört, also nichts mit Menü, Optionen oder Highscores.Da wird eine Antwort wohl überflüssig, wenn man an MVC denkt, da die Spielfigur bei mir nur noch ein Zustand wäre. Deshalb auch anderswo erstellt werden könnte, usw. usf.
Nexus schrieb:
Wer sagt, dass ich sie nie antreffe?
Ich, hast du das nicht gelesen?

Nexus schrieb:
... aber ich ziehe Stackvariablen generell eher vor.
Irgendwann musst du mir mal erklären, was du unter Stackvariablen verstehst. Was du alles auf den Stack packst, da müsste schon längsten ein Stackoverflow stackgefunden ... eh stattgefunden ... haben

Nexus schrieb:
Mir kommt gerade noch etwas in den Sinn: Vorwärtsdeklarationen haben auch nur den Vorteil, dass im Header keine anderen Klassen benötigt werden, oder? Spätestens wenn die Zeiger initialisiert werden, müssen ja die Klassen auch bekannt sein (also in der Implementierungsdatei), womit man wieder lange Kompilierzeiten hätte.
Sobald du ein
newmachst oder eine Methode verwenden willst, oder dereferenzieren möchtest, dann brauchst du die vollständige Deklaration, ja. Also in der *.cpp hat man meistens dann ein Include der entsprechenden Header-Datei.
Aber das ist ja der Trick, dass dann die Sache nicht länger geht! Es wird immer nur die eine Übersetzungseinheit übersetzt. Das parsen von ein paar Header fällt da nicht ins Gewicht. Das Kompilieren dagegen ist aufwendig und das machst du dann nur für wenig Quellcode. Der Linker wird dann wahrscheinlich noch ein wenig Zeit benötigen, aber auch nicht wirklich viel.Nexus schrieb:
Und nur wegen des Dogmas "globale Variablen und ähnliche Konstrukte sind böse" werde ich sicher nicht darauf verzichten und mich mit ellenlangen Parameterlisten rumschlagen.
1. Ellenlange Parameterlisten sind meistens nicht nötig, wenn man das ein wenig sinnvoll strukturiert.
2. Ich lass dich gerne selber auf die Schnauze fallen, da habe ich kein Problem damit.
Bin schliesslich auch selber auf diese gefallen, bis ich es endlich nicht mehr gemacht habe.Nexus schrieb:
Ja, stimmt auch wieder, ich hab zentral eher im Sinne einer alles verwaltenden Klasse gemeint. Aber ich kann mir trotzdem nicht ganz vorstellen, wie du das beispielsweise löst. Hast du in der
main()eine Instanziierung und dann einen Funktionsaufruf, und dann wird alles innerhalb der Klassen weitergegeben?Meistens sieht das irgendwie so aus:
int main() { App* app = new App(); // Allenfalls die parameter weitergeben von der main, wenn man sie möchte. app->init(); // oder app->start(); oder app->startup(); // Ich konnte bisher noch nie wirklich einig mit mir werden xD delete app; }Manchmal kommt die App auch auf den Stack, kommt drauf an, wieviel Informationen bereits in der App gekapselt sind.
In der App werden dann alle weiteren Klassen initialisiert, meistens schon bei der Konstruktion. Und die einen Klassen haben dann wieder Klassen in sich drin, bzw. sind abhängig. Oder gewisse werden noch nicht gebaut und erst wenn sie benötigt werden. Kommt alles ein wenig drauf an, was für ein Programm es wird. Eine Masterklasse gibt es aber definitiv nicht. App ist vielleicht eine zentrale Klasse, weil mit ihr alles startet. Im Verlauf des Programmes wird aber kaum noch auf sie zurückgegriffen, weil App schon dafür sorgt, dass alle anderen Klassen korrekt vernetzt sind.
Nexus schrieb:
Naja, ich programmiere natürlich als Hobby, bin jedoch sehr interessiert. Ich programmiere etwas mehr als zwei Jahre und denke, ich kenne mich auch einigermassen mit den Grundlagen aus. Auch, was Spieleprogrammierung selber betrifft (also algorithmisches), kenne ich mich schon ein bisschen aus. Gerade Kollisionsabfragen (vor allem auf dreieckige Tiles) finde ich nicht gerade einfach. Das Problem ist halt, dass ich bisher noch nicht viel von modularer Programmierung gewusst habe, und deshalb fast alles, was Designfragen betrifft, relativ neu für mich ist. Jedoch bin ich auch bereit, das zu lernen, daran soll das Spiel sicher nicht scheitern. Bisher hab ich schon einige Spiele programmiert, und ein gutes Spiel impliziert meiner Ansicht nach nicht zwangsläufig ein perfektes Design...
2 Jahre programmiert und noch nie modular? 2 Jahre C++? Kann man ja fast nicht glauben

Und was hat du schon als Bücher über C++ gelesen? Die sind nämlich auch noch wichtig. Die können einem ganz neue Sichtweisen geben und man macht riesige Schritte nach vorne.Und nein, ein gutes Spiel hängt meistens nicht vom Code dahinter ab. Aber die Wartung und Erweiterung des Spieles schon

Und teilweise auch die Lauffähigkeit, was Geschwindigkeit und Bugs betrifft
Nexus schrieb:
Ich bin dir wirklich sehr dankbar, dass du dir extra so viel Zeit für mich nimmst!
Immer wieder gerne. Es ist auch schön, wenn jemand die Tipps ernst nimmt und sich was überlegt.
Nexus schrieb:
Es sind auch andere Meinungen erwünscht
Da wäre ich auch froh drum. Ich bin schliesslich auch nicht der Ober-C++-Guru.
drakon schrieb:
Ich denke mal, dass es da vielen gleich geht. Man ist irgendwie nie richtig zufriede mit dem, was man hat.
Nexus schrieb:
Das kenne ich nur zu gut. Ich hab ein älteres Spiel von mir (ein Weltraumshooter), von dem man denken könnte, es sei relativ gut gelungen, wenn man es spielt. Wenn man aber den Code anschaut... Der reinste Albtraum
*das auch kennt*
Nexus schrieb:
Hehe, zum ersten Mal die Meldung "Maximal sind 10 Smilies erlaubt". Ich musste daher etwas kürzen...
*ist soeben auch passiert* ... :=) <- ausgetrickst
Grüssli
-
Ich glaube ich habe hier noch nie eine Unterhaltung gesehen, wo bei jedem Post so viel drin steckt.

Dravere schrieb:
Wie gut kennst du denn Nexus? *das schon immer mal fragen wollte*
Naja. Eigentlich gar nicht, aber ich weiss, dass er sicher genügend Fitt ist, um ein kleines Spiel zu schreiben. Um es mal so auszudrücke: "Ich bin erstaunt, was Leute ohne eigentlich viel Erfahrung mit C++ zu haben anschaubare Spiele bringen können." (Also vom Ergebnis her). Sprich sollte das meiner Einschätzung nach bei ihm auch gut gelingen.

Aber warum "schon immer mal fragen wollt" ?Dravere schrieb:
Nur ob man die Alternativen kennt ist manchmal fraglich. Man denkt, man kenne sie. Tatsächlich gibt es aber vieles, was man noch nicht kennt.
Ja, klar geht es darum. Ich meine nur, dass er einfach mal etwas auf eine Art machen soll. Dann merkt man im Nachhinein, dass etwas anderes, was man da hald noch nicht kannte besser gewesen wäre und setzt es dann auf diese Art um. Ein andermal macht man sich vlt. Gedanken und merkt, dass dort die erste Möglichkeit viel mehr bietet, als die zweite Variante. Wenn man jetzt von Anfang an von jemanden vorgesagt bekommt hätte, was man nehmen soll, wäre man das zweite mal nicht von selbst draufgekommen. So meine ich das. Man lernt selber mehr.
Ich merke auch, wie ihr mich verleitet so viel zu posten. So Schluss. Habe noch anderes zu tun. 