Ich weiß, Singletons sind schlecht. Meine alte Spiel-Engine hat ein einzelnes 'Game'-Objekt verwendet, das alles von der Speicherung aller Daten bis zur eigentlichen Spiel-Schleife verarbeitet. Jetzt mache ich einen neuen.
Das Problem ist, etwas in SFML zu zeichnen, das Sie dort verwenden, window.draw(sprite)
wo ein Fenster ist sf::RenderWindow
. Es gibt 2 Optionen, die ich hier sehe:
- Erstelle ein Singleton-Game-Objekt, das jede Entität im Spiel abruft (was ich vorher verwendet habe)
- Machen Sie dies zum Konstruktor für Entities:
Entity(x, y, window, view, ...etc)
(Das ist einfach lächerlich und nervig.)
Was wäre der richtige Weg, um dies zu tun, während der Konstruktor einer Entität auf x und y beschränkt bleibt?
Ich könnte versuchen, alles, was ich in der Hauptspielschleife mache, im Auge zu behalten und das Sprite einfach manuell in der Spielschleife zu zeichnen, aber auch das scheint chaotisch zu sein, und ich möchte auch die absolute vollständige Kontrolle über eine gesamte Zeichenfunktion für die Entität.
quelle
Antworten:
Speichern Sie nur die Daten, die zum Rendern des Sprites in jeder Entität benötigt werden. Rufen Sie sie dann von der Entität ab und übergeben Sie sie zum Rendern an das Fenster. Sie müssen keine Fenster speichern oder Daten in Entitäten anzeigen.
Sie könnten eine Spiel- oder Engine- Klasse der obersten Ebene haben , die eine Level- Klasse (die alle derzeit verwendeten Entitäten enthält ) und eine Renderer- Klasse (die das Fenster, die Ansicht und alles andere zum Rendern enthält).
Die Spielaktualisierungsschleife in Ihrer höchsten Klasse könnte also so aussehen:
quelle
Logger::getInstance().Log(...)
statt nurLog(...)
? Warum die Klasse zufällig initialisieren, wenn Sie gefragt werden, ob Sie sie nur einmal manuell ausführen können? Eine globale Funktion, die auf statische Globale verweist, ist viel einfacher zu erstellen und zu verwenden.Der einfache Ansatz besteht darin, das, was früher
Singleton<T>
global war , einfach zu machenT
. Globale Unternehmen haben ebenfalls Probleme, aber sie stellen keinen Haufen zusätzlicher Arbeit und zusätzlichen Code dar, um eine triviale Einschränkung durchzusetzen. Dies ist im Grunde die einzige Lösung, bei der der Entity-Konstruktor nicht (möglicherweise) berührt wird.Der schwierigere, aber möglicherweise bessere Ansatz besteht darin , Ihre Abhängigkeiten an den Ort weiterzuleiten, an dem Sie sie benötigen . Ja, dies kann bedeuten, dass Sie eine
Window *
an eine Reihe von Objekten (wie Ihre Entität) auf eine Weise übergeben, die grob aussieht. Die Tatsache, dass es grob aussieht, sollte Ihnen etwas sagen: Ihr Design könnte grob sein.Der Grund, warum dies schwieriger ist (abgesehen davon, dass mehr Eingaben erforderlich sind), ist, dass dies häufig zu einer Umgestaltung Ihrer Schnittstellen führt, so dass weniger Klassen auf Blattebene das benötigen, was Sie "übergeben" müssen. Dies führt dazu, dass ein Großteil der Hässlichkeit, die mit der Übergabe Ihres Renderers an alles verbunden ist, verschwindet und die allgemeine Wartbarkeit Ihres Codes verbessert wird, indem die Anzahl der Abhängigkeiten und Kopplungen verringert wird, deren Ausmaß Sie sehr deutlich gemacht haben, indem Sie die Abhängigkeiten als Parameter verwendet haben . Wenn es sich bei den Abhängigkeiten um Singletons oder Globals handelte, war es weniger offensichtlich, wie stark Ihre Systeme miteinander verbunden waren.
Aber es ist möglicherweise ein großes Unterfangen. Es kann geradezu schmerzhaft sein, es einem System nachträglich anzutun. Es mag für Sie weitaus pragmatischer sein, Ihr System vorerst mit dem Singleton in Ruhe zu lassen (insbesondere, wenn Sie versuchen, ein Spiel zu veröffentlichen, das ansonsten einwandfrei funktioniert; es ist den Spielern im Allgemeinen egal, ob Sie es haben oder nicht) ein Singleton oder vier drin).
Wenn Sie versuchen möchten, dies mit Ihrem vorhandenen Design zu tun, müssen Sie möglicherweise viel mehr Details zu Ihrer aktuellen Implementierung veröffentlichen, da es keine allgemeine Checkliste für diese Änderungen gibt. Oder kommen Sie und diskutieren Sie im Chat .
Nach dem, was Sie gepostet haben, besteht ein großer Schritt in Richtung "kein Singleton" darin, zu vermeiden, dass Ihre Entitäten Zugriff auf das Fenster oder die Ansicht haben müssen. Es deutet darauf hin, dass sie sich selbst zeichnen, und Sie müssen keine Entitäten haben, die sich selbst zeichnen . Sie können eine Methode übernehmen , wo die Einheiten nur die Informationen enthalten , die würde erlaubenSie müssen von einem externen System gezeichnet werden (mit Fenster- und Ansichtsreferenzen). Die Entität legt nur ihre Position und das zu verwendende Sprite offen (oder eine Art Verweis auf das Sprite, wenn Sie die tatsächlichen Sprites im Renderer selbst zwischenspeichern möchten, um doppelte Instanzen zu vermeiden). Dem Renderer wird einfach gesagt, dass er eine bestimmte Liste von Entitäten zeichnen soll, die er durchläuft, aus denen er die Daten liest und deren intern gehaltenes Fensterobjekt verwendet,
draw
um das nach der Entität gesuchte Sprite aufzurufen .quelle
Erben Sie von sf :: RenderWindow
SFML ermutigt Sie tatsächlich, von seinen Klassen zu erben.
Von hier aus erstellen Sie Elementzeichnungsfunktionen zum Zeichnen von Objekten.
Jetzt können Sie dies tun:
Sie können sogar noch einen Schritt weiter gehen, wenn Ihre Entities ihre eigenen Sprites behalten, indem Sie Entity von sf :: Sprite erben lassen.
Jetzt
sf::RenderWindow
können nur noch Entities gezeichnet werden und Entities haben nun Funktionen wiesetTexture()
undsetColor()
. Das Objekt könnte sogar die Position des Sprites als seine eigene Position verwenden, sodass Sie diesetPosition()
Funktion sowohl zum Bewegen des Objekts als auch seines Sprites verwenden können.Am Ende ist es ziemlich schön, wenn Sie nur haben:
Im Folgenden finden Sie einige kurze Beispiele für Implementierungen
ODER
quelle
Sie vermeiden Singletons in der Spieleentwicklung genauso, wie Sie sie in jeder anderen Art der Softwareentwicklung vermeiden: Sie übergeben die Abhängigkeiten .
Mit diesem aus dem Weg, können Sie die Abhängigkeiten direkt als nackte Typen passieren (wie
int
,Window*
etc) , oder Sie können sie wählen , in einer oder mehr benutzerdefinierten passieren Wrapper - Typen (wieEntityInitializationOptions
).Der erste Weg kann ärgerlich werden (wie Sie herausgefunden haben), während der zweite es Ihnen ermöglicht, alles in einem Objekt zu übergeben und die Felder zu ändern (und sogar den Optionstyp zu spezialisieren), ohne jeden Entitätskonstruktor zu umgehen und zu ändern. Ich denke, der letztere Weg ist besser.
quelle
Singletons sind nicht schlecht. Stattdessen sind sie leicht zu missbrauchen. Auf der anderen Seite sind Globals noch leichter zu missbrauchen und haben noch viel mehr Probleme.
Der einzig gültige Grund, einen Singleton durch einen globalen zu ersetzen, ist die Befriedung der religiösen Singleton-Hasser.
Das Problem besteht darin, ein Design zu haben, das Klassen enthält, von denen es nur eine einzige globale Instanz gibt und auf die von überall aus zugegriffen werden muss. Dies bricht auseinander, sobald Sie mehrere Instanzen des Singletons haben, zum Beispiel in einem Spiel, in dem Sie einen geteilten Bildschirm implementieren, oder in einer ausreichend großen Unternehmensanwendung, in der Sie feststellen, dass ein einzelner Logger nicht immer eine so gute Idee ist .
Unterm Strich ist Singleton oft eine der besseren Lösungen in einem Pool suboptimaler Lösungen , wenn Sie wirklich eine Klasse haben, in der Sie eine einzige globale Instanz haben, die Sie nicht vernünftigerweise als Referenz weitergeben können.
quelle
Abhängigkeiten injizieren. Dies hat den Vorteil, dass Sie jetzt verschiedene Arten dieser Abhängigkeiten über eine Factory erstellen können. Leider ist das Herausreißen von Singletons aus einer Klasse, in der sie verwendet werden, so, als würde man eine Katze mit den Hinterbeinen über einen Teppich ziehen. Aber wenn Sie sie injizieren, können Sie Implementierungen austauschen, vielleicht im laufenden Betrieb.
Jetzt können Sie verschiedene Arten von Fenstern injizieren. Auf diese Weise können Sie Tests mit verschiedenen Fenstertypen gegen das RenderSystem schreiben, um zu sehen, wie Ihr RenderSystem beschädigt oder funktioniert. Dies ist nicht möglich oder schwieriger, wenn Sie Singletons direkt in "RenderSystem" verwenden.
Jetzt ist es testbarer, modularer und auch von einer bestimmten Implementierung entkoppelt. Es hängt nur von einer Schnittstelle ab, nicht von einer konkreten Implementierung.
quelle