Hallo
ja das sind so typische Sachen, die bei der Spieleentwicklung oft ein wenig dem zuwiederlaufen wie man in allen anderen Bereichen programmiert. Zum einen wegen Performance und zum anderen weil bei Spiele es oft vorkommt, dass an den erzeugten Daten/Objekte es viele Interessenten gibt und nicht nur einen oder zwei.
Verzei mir das ich nun viel schreiben werden. Ich will dir nicht einfach nur 5 Lösungs Zeilen hin werfen und sagen 'mach es so, das ist am besten' Punkt. Sondern ich sehe in deinen Überlegungen, so viel Talent, dass ich denke es hilft dir mehr, wenn du die Zusammenhänge erkennst und zukünftig dann selber immer Zielsicher die beste Lösung finden kannst
Nehmen wir deine Idee mit m_ShotList und m_SpriteList in CGame. Eigentlich eine gute Idee. Doch CGame (dein GameController) wird dadurch mit etwas beauftragt, was eigentlich nicht seine Aufgabe ist.
Alisa, räume dein Zimmer und deine Schminksachen auf, und wenn du schon dabei bist, sortiere bitte auch gleich meine Stifte bei meinem Schreibtisch und zwar mag ich es farbig Sortiert von hell nach dunkel, aber blau trotzdem als erstes weil den brauche ich meistens.
Kann man machen, aber 'weils nicht sein Ding ist', wird er diese Aufgabe niemals optimal lösen. Und wenn du schon ShotList und SpritList in ihm hast, dann werden zukünftig bei neuen Dingen sich erstrecht Lösungen aufdrängen, die dann auch wieder in CGame landen... Plötzlich hast du dann ein CGame welches irgendwie alles kann, aber alles was du dort aufrufst musst du mit vielen Funktionen und vielen verschiedenen Funktionsparametern, ganz genau 'erklären'.
Es wird mit der Zeit in deinem Spiel Aufgaben geben die wirklich zu CGame passen und dort rein sollten. Das wird dazu führen, das CGame immer mehr Bereiche bekommt um den es sich kümmern muss. Es wird unübersichtlich werden. Also solltest du dir immer genau überlegen, was wirklich dort rein passt. Sonst wird es zur Gottklasse/Gottobjekt.
Bei ganz kleinen Programmen, kann man sowas mal machen. Denn es ist die schnellste (und sogar übersichtlichste Lösung) wenn es etwas wirklich kleines ist. Maximal 'Taschenrechner der nur Grundrechenarten kann'. Sobald es aber auch nur ein bisschen größer wird, sollte man sowas lieber vermeiden.
Grundsatz: Jede Klasse sollte möglichst nur für eine Sache zuständig sein. Seine Domain.
Ok, du könntest nun eine Zwischenschicht bauen. Zwischen CGame und den Restlichen Dingen die im Spiel ablaufen. z.B. SceneController. Und SceneController kann dann ShotList und SpriteList tragen. Dazu dann noch paar Verwaltungsfunktionen, wie 'Spieler in Liste hinzufügen' und '..aus Liste löschen' und '...such/gib mir Spieler X aus der Liste' ....
Aber spätestens hier sollte es dann aufhören. In Liste rein/raus/suchen .. also die 3 Grundfunktionen, die sind gerade noch mit schmerz Ok. Aber alle weiteren Funktionen, sollten dann die Listen-Objekte selber können.
Was ich meine Ist: SceneController::AddShot(#) ... und CShot::CalcDamage()
Damit wäre CGame entlastet, und du hätest trotzdem noch eine (nicht zusehr überfüllte) Klasse die deine Anlaufstelle ist für die Verwaltungssachen. Aber du kannst dir schon selber ausrechnen, wenn man da nicht aufpasst und sich bei jedem neuen Ding überlegt ob es wirklich in SceneController rein gehört oder besser woanders hin... dann könnte es passieren das SceneController mit der Zeit zu mächtig/gross wird. Das Vieze-Gottobjekt.
Warum ? nun also nicht sowas wie: CManagerShotList , CManagerPlayerList und CManagerSpriteList
CManagerPlayList wäre dann eine Art Wraper um die m_Player. Es wäre sowas wie eine Spezialisierte 'List' Klasse.
So könnte dann CManagerPlayList funktionen haben für add/remove/get .. aber auch sowas wie GetPlayerMitBestenPunktestand() oder GetAllPlayersMitMehrAls0Lebenspunkte()
Dinge die zum selben Bereich gehören, wären zusammengefasst. Und dennoch wären Dinge die nicht zusammen gehören, getrennt.
Anfangs wird du zwar viele extrem kleine Klassen haben (die jeweils nur vielleicht 3 bis 5 Zeilen groß sind) und deswegen wird es dir unüberichtlicher vorkommen. Das ist genau der Grund warum bei absoluten Mini-Projekten so ein 'aufteilen' sogar unübersichtlicher sein kann als die Gottklasse die bei dem Mini-Projekt noch nicht Zeit hate um zu groß zu werden.
Doch wenn du mit der Zeit dann immer mehr die Details deines Spiels programmierst, werden auch diese kleinen Klassen viel mehr Funktionen bekommen. Und spätestens dann wird es so viel Übersichtlicher und viel leichter zu programmieren sein, als die Gottklasse.
Als Zwischenschritt bietet sich hier dann noch an, das du zwar deine Listen auf 3 Klassen aufteilst. Aber diese 3 Klassen evtl. nicht von anfang an auf 3 Quellcode Dateien verteils, sondern vielleicht ersteinmal in eine einzige Quellcodedatei. Ist zwar eigentlich Programmiertechnisch nicht so sauber und in ein paar Wochen wirst du vermutlich von alleine es dann auch auf mehere Dateien aufteilen wollen. Doch gerade wenn man vom Gefühl her sich an diese 'vielen kleinen Klassen' noch nicht gewöhnt hat (man fühlt sich unwohl, es kommt einen sehr unübersichtlich vor), dann ist das eine gute möglichkeit.
Nun deine Idee mit Singleton. Übrigens guter Gedanke
Der Singleton ist eigentlich davon nun Unabhängig. Er hat nicht wirklich damit zu tun, ob du nun alles in CGame rein haust, oder irgendwie anders aufteilst.
Das eine ist '
was/wo' du die Listen
ablegst und wo du die Funktionen der Listenverwaltung rein schreibst.
Das andere (Singleton) betrifft hingegen '
wie'
wird zugegriffen?
Also wie greift ein Objekt eines bestimmten Typs, auf Objekte/Listen einer ganz anderen/fremden Sachen zu? Der Singleton dient der Domain-übergreifenden Verknüpfung.
Im Grunde ist es oft Geschmackssache. Man kann jedem Objekt das Zugang zu einen der woanders gepflegten Listen (oder Managerklassen) braucht, entweder direkt eine Referenz Variable mitgeben welche auf die 'Globale Liste'/Managerklasse zeig oder eben Singleton's machen.
Die Referenz Variable wäre eher in Richtung ordentlicher Objektorientiertheit. Hat aber mehrere kleinere Nachteile:
- Wenn du viele Objekte mit der Zeit hast die eine Referenzvariable auf etwas anderes halten, wird der Speicherzugriff evtl. relevant. Der Speicherverbrauch ansich wäre dabei noch nicht einmal das Problem (der wäre sehr klein). Jedoch erhöht das die Speicherfragmentierung. Wenn man sowas ganz viel macht, dann bekommt man irgendwann Mikrorukler, oder komische ganz kurze Hänger, bei z.B. Level-Neustart.
- Wenn ein bestimmtes Objekt/Klasse nun nicht nur einen Verweis auf eine andere Liste/Klasse braucht, sondern sagen wir auf alle 3 (CManagerShotList , CManagerPlayerList und CManagerSpriteList), dann wird alleine diese wachsende Liste der Membervariablen in dem Objekt, schon bisschen unübersichtlicher.
- Und diese Referenzvariablen müssen auch beim erzeugen der Objekte, z.B. dem Kontruktor mit übergeben werden. Und dort wiederum musst du paar Zeilen programmieren für das zuweisen der Variablen...
Und der Aufwand dann bei jeder Klasse, die interesse an einer der anderen ManagerKlassen (oder anderen Listen) hat...
Gibt noch mehr sehr kleine Nachteile (die erst dann eine Rolle speielen, wenn man viele Dinge miteinander verknüpfen muss). Und auch diese drei genanten Nachteile sind nun nicht so schlimme Nachteile. Doch es wird wohl klar, das es nicht unbedingt für alle Probleme sooo die super Lösung sein ist.
Und hier kann man halt evtl. dann auch einfach Singletons verwenden. Denn diese haben genau diese Nachteile eben nicht. Sondern im Gegeteil, haben sie hier sogar ihre großen Stärken.
Bei Singletons solltest du aber immer im Hinterkopf behalten, sie dürfen/sollten nur dann verwendet werden, wenn wirklich ganz sicher niemals mehr als eine Instanz gebraucht wird!
Als Beispiel: Bei einem 'TastaturManager' wirst du vermutlich niemals einen Grund finden, warum du zwei verschiedene TastaturManager gleichzeitig brauchen könntest. Auch bei CManagerShotList , CManagerPlayerList und CManagerSpriteList vermutlich nicht.
Aber bei z.B. InventarList wird es vielleicht schon Gefährlich. Ist und bleibt es garantiert ein Singleplayerspiel, kann man aus InventarList auch einen Singleton machen (einen Manager sozusagen). Doch er wird zum Problem wenn aus dem anfänglich gedachten Singleplayer, dann ein kleiner Mehrspieler wird... wie in deinem Fall? z.B. ein Hotseat Mehrspieler, also ein Mehrspieler bei dem 2 Leute an einem Bildschirm sitzen und gleichzeitig z.B. die Tastatur nutzen, einer links AWSD und einer rechts Pfeil rauf/runter/links/rechts. Dann hast du plötzlich mehrere Spieler die jeweils ein eigenes Inventar brauchen. Dann passt die Lösung mit dem 'einen einzigen InventarList Singleton' nicht mehr.
Singleton sind halt immer Global. Das kann gut sein. Genau deswegen nimmt man sie ja auch. Wenn man es bewust überlegt verwendet. Aber sie sind eigentlich ein Hack des Objektorientierten Konzepts!
Sie haben Vorteile, und oft überwiegen sie auch. Gerade bei der Spieleprogrammierung finde ich sind sie für bestimmte Probleme, oft die mit Abstand beste Lösung. Deswegen verwende ich sie dort gerne. Ausserhalb der Spieleprogrammierung verwende ich sie aber fast nie. Liegt an der Konstelation: Viele Dinge der einen Sache, die interesse haben/zugang brauchen zu anderen Klassen (Domains, Themenbereiche). Und zugleich von diesen Domain/Themenbereichen auch garantiert nur eine einzige Instanz gebraucht wird.
Bei der Programmierung ganz allgemein, gibt es oft nicht die beste Lösung für einen ganz bestimmten Fall. Und manchmal kann es sogar Situationen geben, wo man eben gegen die Objektorientiertheit absichtlich verstößt. Oder sogar absichtlich Gottobjekte baut. Das ist auch OK. Aber mann sollte sich immer vorher genau darüber gedanken machen und überlegen welche Lösungen es gibt, welche Nachteile/Vorteile für das aktuelle Problem überwiegen, und sich dann ganz bewust entscheiden. Dann ist es auch absolut OK. Egal welche Lösung es dann wird. Zur Katastrophe wird es immer nur, wenn man ohne die Bewuste entscheidung ('hat sich durch Zufall irgendwie so ergeben') total vom sauberen Standard abweicht.
Von daher Masi, absolut super von dir! Du hast sofort etwas in deinem Code bemerkt und dir angefangen gedanken zu machen, noch bevor 'es sich Zufällig so ergeben hat und dann mit der Zeit zur Katastrophe ausgewuchert ist'. In dir steckt wohl sehr viel Talent und das richtige Gespür, für einen super Programmierer/Entwickler.