Sé que los singletons son malos, mi viejo motor de juego usaba un objeto singleton 'Game' que maneja todo, desde mantener todos los datos hasta el bucle real del juego. Ahora estoy haciendo uno nuevo.
El problema es que, para dibujar algo en SFML, se usa window.draw(sprite)
donde window es an sf::RenderWindow
. Hay 2 opciones que veo aquí:
- Crea un objeto Singleton Game que cada entidad del juego recupere (lo que usé antes)
- Haga de este el constructor de entidades:
Entity(x, y, window, view, ...etc)
(esto es simplemente ridículo y molesto)
¿Cuál sería la forma correcta de hacer esto mientras se mantiene el constructor de una entidad solo para x e y?
Podría intentar hacer un seguimiento de todo lo que hago en el bucle principal del juego, y dibujar manualmente su sprite en el bucle del juego, pero eso también parece desordenado, y también quiero un control total absoluto sobre una función de dibujo completa para la entidad.
c++
design-patterns
oop
Acumulador
fuente
fuente
Respuestas:
Solo almacene los datos necesarios para representar el sprite dentro de cada entidad, luego recupérelos de la entidad y páselos a la ventana para su representación. No es necesario almacenar ninguna ventana o ver datos dentro de las entidades.
Podría tener una clase de Juego o Motor de nivel superior que tenga un Nivel de clase (tiene todas las entidades que se utilizan actualmente), y un Procesador de clase (contiene la ventana, ver y cualquier otra cosa para la representación).
Entonces el ciclo de actualización del juego en tu clase de nivel superior podría verse así:
fuente
Logger::getInstance().Log(...)
lugar de soloLog(...)
? ¿Por qué inicializar la clase al azar cuando se le pregunta si puede hacerlo manualmente solo una vez? Una función global que hace referencia a globales estáticos es mucho más simple de crear y usar.El enfoque simple es simplemente hacer lo que solía ser
Singleton<T>
global en suT
lugar. Los globales también tienen problemas, pero no representan un montón de trabajo extra y código repetitivo para imponer una restricción trivial. Esta es básicamente la única solución que no implicará (potencialmente) tocar el constructor de la entidad.El enfoque más difícil, pero posiblemente mejor, es pasar sus dependencias a donde las necesita . Sí, esto podría implicar pasar
Window *
a un grupo de objetos (como su entidad) de una manera que parezca asquerosa. El hecho de que parezca asqueroso debería decirle algo: su diseño puede ser asqueroso.La razón por la que esto es más difícil (más allá de involucrar más tipeo) es que esto a menudo conduce a la refactorización de sus interfaces para que lo que "necesita" pasar sea necesario por menos clases de nivel de hoja. Esto hace que gran parte de la fealdad inherente al pasar su renderizador a todo desaparezca, y también mejora la capacidad de mantenimiento general de su código al reducir la cantidad de dependencias y el acoplamiento, la medida en que lo hizo muy obvio al tomar las dependencias como parámetros . Cuando las dependencias eran simples o globales, era menos obvio cuán interconectados estaban sus sistemas.
Pero es potencialmente un importante empresa . Hacerlo en un sistema después del hecho puede ser francamente doloroso. Puede ser mucho más pragmático para ti simplemente dejar tu sistema solo, con el singleton, por ahora (especialmente si estás intentando enviar un juego que de lo contrario funciona bien; a los jugadores generalmente no les importará si tienes un singleton o cuatro allí).
Si desea intentar hacer esto con su diseño existente, es posible que deba publicar muchos más detalles sobre su implementación actual, ya que en realidad no hay una lista de verificación general para realizar estos cambios. O ven a discutirlo en el chat .
Por lo que ha publicado, creo que un gran paso en la dirección "no singleton" sería evitar la necesidad de que sus entidades tengan acceso a la ventana o vista. Sugiere que se dibujan a sí mismos, y no es necesario que las entidades se dibujen a sí mismas . Puede adoptar una metodología donde las entidades solo contienen la información que permitiría mismas para que sean dibujadas por algún sistema externo (que tiene las referencias de ventana y vista). La entidad simplemente expone su posición y el sprite que debe usar (o algún tipo de referencia a dicho sprite, si desea almacenar en caché los sprites reales en el renderizador para evitar tener instancias duplicadas). Simplemente se le dice al renderizador que dibuje una lista particular de entidades, a través de las cuales recorre, lee los datos y usa su objeto de ventana interno para llamar
draw
con el sprite buscado para la entidad.fuente
Heredar de sf :: RenderWindow
SFML realmente lo alienta a heredar de sus clases.
Desde aquí, puede crear funciones de dibujo de miembros para entidades de dibujo.
Ahora puedes hacer esto:
Incluso puede llevar esto un paso más allá si sus Entidades van a mantener sus propios sprites únicos haciendo que Entity herede de sf :: Sprite.
Ahora
sf::RenderWindow
solo puede dibujar Entidades, y las entidades ahora tienen funciones comosetTexture()
ysetColor()
. La Entidad incluso podría usar la posición del sprite como su propia posición, permitiéndole usar lasetPosition()
función tanto para mover la Entidad Y su sprite.Al final , es bastante bueno si solo tienes:
A continuación se presentan algunas implementaciones de ejemplo rápido
O
fuente
Evitas los singletons en el desarrollo de juegos de la misma manera que los evitas en cualquier otro tipo de desarrollo de software: pasas las dependencias .
Con eso fuera del camino, se puede elegir para pasar las dependencias directamente como tipos desnudos (como
int
,Window*
, etc.) o se puede optar por pasar en uno o más tipos de encargo envoltorio (comoEntityInitializationOptions
).La primera forma puede ser molesta (como descubrió), mientras que la segunda le permitirá pasar todo en un objeto y modificar los campos (e incluso especializar el tipo de opciones) sin tener que cambiar y cambiar cada constructor de entidades. Creo que la última forma es mejor.
fuente
Los singletons no son malos. En cambio, son fáciles de abusar. Por otro lado, los globales son aún más fáciles de abusar y tienen muchos más problemas.
La única razón válida para reemplazar un singleton con un global es pacificar a los odiadores religiosos singleton.
El problema es tener un diseño que incluye clases de las cuales solo existe una instancia global y que debe ser accesible desde cualquier lugar. Esto se rompe tan pronto como terminas teniendo múltiples instancias del singleton, por ejemplo, en un juego cuando implementas una pantalla dividida, o en una aplicación empresarial suficientemente grande cuando notas que un solo registrador no siempre es una gran idea después de todo .
En pocas palabras, si realmente tiene una clase donde tiene una única instancia global que no puede pasar razonablemente por referencia , singleton es a menudo una de las mejores soluciones en un conjunto de soluciones subóptimas.
fuente
Inyectar dependencias. Una ventaja de hacerlo es que ahora puede crear varios tipos de estas dependencias a través de una fábrica. Desafortunadamente, extraer singletons de una clase que los usa es como tirar de un gato por las patas traseras a través de una alfombra. Pero si los inyecta, puede intercambiar implementaciones, tal vez sobre la marcha.
Ahora puede inyectar varios tipos de ventanas. Esto le permite escribir pruebas contra el RenderSystem con varios tipos de ventanas para que pueda ver cómo su RenderSystem se romperá o funcionará. Esto no es posible, o más difícil, si usa singletons directamente dentro de "RenderSystem".
Ahora es más comprobable, modular y también está desacoplado de una implementación específica. Solo depende de una interfaz, no de una implementación concreta.
fuente