Je sais que les singletons sont mauvais, mon ancien moteur de jeu utilisait un objet 'Game' singleton qui gère tout, de la conservation de toutes les données à la boucle de jeu réelle. Maintenant j'en fais un nouveau.
Le problème est que, pour dessiner quelque chose en SFML, vous utilisez window.draw(sprite)
où window est un sf::RenderWindow
. Il y a 2 options que je vois ici:
- Créer un objet Game singleton que chaque entité du jeu récupère (ce que j'ai utilisé auparavant)
- Faites-en le constructeur des entités:
Entity(x, y, window, view, ...etc)
(c'est juste ridicule et ennuyeux)
Quelle serait la bonne façon de le faire tout en gardant le constructeur d'une entité à seulement x et y?
Je pourrais essayer de garder une trace de tout ce que je fais dans la boucle de jeu principale et dessiner manuellement leur sprite dans la boucle de jeu, mais cela semble aussi compliqué, et je veux également un contrôle total absolu sur une fonction de dessin entière pour l'entité.
la source
Réponses:
Stockez uniquement les données nécessaires au rendu de l'image-objet à l'intérieur de chaque entité, puis récupérez-les dans l'entité et passez-les à la fenêtre pour le rendu. Pas besoin de stocker une fenêtre ou d'afficher des données à l'intérieur d'entités.
Vous pourriez avoir une classe de jeu ou de moteur de niveau supérieur qui détient un niveau classe (contient toutes les entités actuellement utilisées) et une classe Renderer (contient la fenêtre, la vue et tout autre élément pour le rendu).
Ainsi, la boucle de mise à jour du jeu dans votre classe de niveau supérieur pourrait ressembler à:
la source
Logger::getInstance().Log(...)
au lieu de justeLog(...)
? Pourquoi initialiser la classe au hasard lorsqu'on lui a demandé si vous pouvez le faire manuellement une seule fois? Une fonction globale référençant des globaux statiques est beaucoup plus simple à créer et à utiliser.L'approche simple consiste simplement à faire de ce qui était autrefois
Singleton<T>
unT
place. Les globaux ont également des problèmes, mais ils ne représentent pas un tas de travail supplémentaire et du code standard pour appliquer une contrainte triviale. C'est fondamentalement la seule solution qui n'implique pas (potentiellement) de toucher le constructeur de l'entité.L'approche la plus difficile, mais peut-être meilleure, consiste à transférer vos dépendances là où vous en avez besoin . Oui, cela pourrait impliquer de passer un
Window *
à un tas d'objets (comme votre entité) d'une manière qui a l'air grossière. Le fait qu'il ait l'air dégoûtant devrait vous dire quelque chose: votre design peut être dégoûtant.La raison pour laquelle cela est plus difficile (au-delà d'impliquer davantage de frappe) est que cela conduit souvent à refactoriser vos interfaces de sorte que la chose dont vous "avez besoin" pour passer est nécessaire par moins de classes de niveau feuille. Cela rend beaucoup la laideur inhérente au passage de votre moteur de rendu à tout, et améliore également la maintenabilité générale de votre code en réduisant la quantité de dépendances et de couplage, dont vous avez rendu très évidente la prise en compte des dépendances comme paramètres. . Lorsque les dépendances étaient des singletons ou des globales, il était moins évident à quel point vos systèmes étaient interconnectés.
Mais c'est potentiellement une entreprise majeure . Le faire à un système après coup peut être carrément douloureux. Il peut être beaucoup plus pragmatique pour vous de simplement laisser votre système seul, avec le singleton, pour l'instant (surtout si vous essayez réellement de livrer un jeu qui sinon fonctionne très bien; les joueurs ne s'en soucieront généralement pas si vous avez un singleton ou quatre là-dedans).
Si vous voulez essayer de le faire avec votre conception existante, vous devrez peut-être publier beaucoup plus de détails sur votre implémentation actuelle car il n'y a pas vraiment de liste de contrôle générale pour effectuer ces modifications. Ou venez en discuter dans le chat .
D'après ce que vous avez publié, je pense qu'un grand pas dans la direction "pas de singleton" consisterait à éviter que vos entités aient accès à la fenêtre ou à la vue. Cela suggère qu'ils se dessinent eux-mêmes, et vous n'avez pas besoin que les entités les dessinent elles-mêmes . Vous pouvez adopter une méthodologie où les entités contiennent simplement les informations qui permettraient pour être dessinées par un système externe (qui a les références de fenêtre et de vue). L'entité expose simplement sa position et le sprite qu'elle doit utiliser (ou une sorte de référence audit sprite, si vous souhaitez mettre en cache les sprites réels dans le moteur de rendu lui-même pour éviter d'avoir des instances en double). Il est simplement demandé au moteur de rendu de dessiner une liste particulière d'entités, qu'il parcourt, lit les données et utilise son objet fenêtre en interne pour appeler
draw
avec l'image-objet recherchée pour l'entité.la source
Hériter de sf :: RenderWindow
SFML vous encourage en fait à hériter de ses classes.
À partir de là, vous créez des fonctions de dessin de membre pour les entités de dessin.
Vous pouvez maintenant le faire:
Vous pouvez même aller plus loin si vos Entités vont tenir leurs propres sprites uniques en faisant hériter Entity de sf :: Sprite.
Maintenant,
sf::RenderWindow
il suffit de dessiner des entités, et les entités ont maintenant des fonctions commesetTexture()
etsetColor()
. L'entité pourrait même utiliser la position de l'image-objet comme sa propre position, vous permettant d'utiliser lasetPosition()
fonction pour déplacer l'entité ET son image-objet.Au final , c'est plutôt sympa si vous avez juste:
Voici quelques exemples d'implémentations rapides
OU
la source
Vous évitez les singletons dans le développement de jeux de la même manière que vous les évitez dans tous les autres types de développement logiciel: vous passez les dépendances .
Avec cela à l'écart, vous pouvez choisir de passer les dépendances directement en tant que types nus (comme
int
,Window*
, etc.) ou vous pouvez choisir de les passer dans un ou plusieurs types personnalisés emballage (commeEntityInitializationOptions
).La première façon peut devenir ennuyeuse (comme vous l'avez découvert), tandis que la seconde vous permettra de tout passer dans un seul objet et de modifier les champs (et même de spécialiser le type d'options) sans contourner et changer chaque constructeur d'entité. Je pense que la dernière façon est meilleure.
la source
Les singletons ne sont pas mauvais. Au lieu de cela, ils sont faciles à abuser. D'autre part, les globaux sont encore plus faciles à abuser et ont beaucoup plus de problèmes.
La seule raison valable de remplacer un singleton par un global est de pacifier les haineux religieux singleton.
Le problème est d'avoir une conception qui inclut des classes dont il n'existe qu'une seule instance globale et qui doivent être accessibles de partout. Cela se désagrège dès que vous finissez par avoir plusieurs instances du singleton, par exemple dans un jeu lorsque vous implémentez un écran partagé, ou dans une application d'entreprise suffisamment grande lorsque vous remarquez qu'un seul enregistreur n'est pas toujours une si bonne idée après tout .
En bout de ligne, si vous avez vraiment une classe où vous avez une seule instance globale que vous ne pouvez pas raisonnablement faire circuler par référence , singleton est souvent l'une des meilleures solutions dans un pool de solutions sous-optimales.
la source
Injectez les dépendances. Vous pouvez désormais créer différents types de ces dépendances via une usine. Malheureusement, arracher des singletons d'une classe qui les utilise, c'est comme tirer un chat par ses pattes de derrière sur un tapis. Mais si vous les injectez, vous pouvez échanger des implémentations, peut-être à la volée.
Vous pouvez maintenant injecter différents types de fenêtres. Cela vous permet d'écrire des tests sur le RenderSystem avec différents types de fenêtres afin que vous puissiez voir comment votre RenderSystem se cassera ou fonctionnera. Ce n'est pas possible, ou plus difficile, si vous utilisez des singletons directement à l'intérieur de "RenderSystem".
Maintenant, il est plus testable, modulaire et il est également découplé d'une implémentation spécifique. Cela ne dépend que d'une interface, pas d'une implémentation concrète.
la source