Quiero deshacerme de mi patrón de diseño make-everything-static-and-global, pero ¿cómo?

11

Estoy haciendo un pequeño rastreador de mazmorras en el espacio, y me gustaría escuchar algunos consejos sobre cómo hacer que el motor del motor sea más agradable. Básicamente, actualmente todo se basa en una gran cantidad de gerentes:

  • BackgroundManager: tiene un AddBackground(image, parallax)método para crear efectos de fondo geniales.
  • ConfigManager: lee / crea el archivo de configuración y también contiene los datos leídos de ese archivo de configuración.
  • DrawManager: tiene un Draw(sprite)método para dibujar cosas en la pantalla y cosas como SetView(view), GetView()etc.
  • EntityManager: contiene todas las entidades activas y tiene métodos para agregar, eliminar y encontrar entidades.
  • DungeonManager (en realidad se llama un GridManager, pero esto es por simplicidad): tiene métodos como GenerateDungeon(), PlaceEnemies(), PlaceFinish()etc.

Ahora ni siquiera estoy a la mitad de la lista de todos mis gerentes, así que creo que esto tiene que cambiar. Mi juego también se basa en pantallas, lo que lo hace más molesto porque no necesito la mitad de las cosas que mis gerentes están administrando, por ejemplo, el menú principal (no necesito física / entidades / mazmorras en mi menú principal) !)

Ahora pensé en hacer que los gerentes no sean estáticos, y darle a ScreenMainGame una instancia de todos los gerentes específicos del juego. Pero eso hace que llamar / hacer algo relacionado con los gerentes sea un gran desastre ... por ejemplo, si quisiera sacar mi mazmorra de cualquier lugar que no esté ScreenMainGame.Draw(), tendría que hacer esto ...

((ScreenMainGame)ScreenManager.CurrentScreen).GridManager.Draw()

Eso es realmente feo.

Entonces, compañeros desarrolladores de juegos, ¿alguno de ustedes tiene una idea de cómo resolver este desastre? Se lo agradeceria!

Dlaor
fuente
No estoy seguro exactamente cómo ayudarlo, pero recomendaría leer un buen libro sobre patrones de diseño. He estado leyendo Head First Design Patterns y es muy fácil de entender. Los ejemplos están en Java, pero creo que estaría lo suficientemente cerca de C # para ayudarlo. Lo siento si mi sugerencia es demasiado novata.
Amplify91
¿Qué estás usando para interactuar con la tarjeta gráfica? XNA? SlimDX?
BlueRaja - Danny Pflughoeft
@BlueRaja: estoy usando SFML.NET.
Dlaor
En ese caso, simplemente debe usar los métodos normales para tratar estos problemas en C #: consulte mi último enlace a continuación, así como ¿Qué es la inyección de dependencia?
BlueRaja - Danny Pflughoeft

Respuestas:

10

¿Qué pasa con un motor basado en componentes ?

Tendría una clase principal llamada Engine, que mantendría una lista de GameScreens, que ellos mismos tendrían una lista de Components.

El motor tiene un Updatey un Drawmétodo y ambos llaman los GameScreen's Updatey los Drawmétodos, que pasan por cada componente y llaman Updatey Draw.

Presentado así, estoy de acuerdo en que suena como un diseño pobre y repetitivo. Pero créanme, mi código se volvió mucho más limpio mediante el uso de un enfoque basado en componentes que con todas mis antiguas clases de administrador .

También es mucho más simple mantener dicho código, ya que solo está pasando por una gran jerarquía de clases y no tiene que buscar BackgroundManagertodos los diferentes fondos específicos. Sólo tienes una ScrollingBackground, ParallaxBackground, StaticBackground, etc, todos ellos derivados de una Backgroundclase.

Eventualmente construirá un motor bastante sólido que puede reutilizar en todos sus proyectos con muchos componentes y métodos auxiliares de uso frecuente (por ejemplo, FrameRateDisplayercomo una utilidad de depuración, una Spriteclase como sprite básico con una textura y métodos de extensión para vectores y generación de números aleatorios).

Ya no tendrías una BackgroundManagerclase, sino una Backgroundclase que se administraría sola.

Cuando comienza tu juego, todo lo que tienes que hacer es básicamente esto:

// when declaring variables:
Engine engine;

// when initializing:
engine = new Engine();
engine.Initialize();
engine.LoadContent();
engine.AddGameScreen(new MainMenuScreen());

// when updating:
engine.Update();

// when drawing:
engine.Draw();

Y eso es todo para el código de inicio del juego.

Luego, para la pantalla del menú principal:

class MainMenuScreen : MenuScreen // where MenuScreen derives from the GameScreen class
{
    const int ENEMY_COUNT = 10;

    StaticBackground background;
    Player player;
    List<Enemy> enemies;

    public override void Initialize()
    {
        background = new StaticBackground();
        player = new Player();
        enemies = new List<Enemy>();

        base.AddComponent(background); // defined within the GameScreen class
        base.AddComponent(player);
        for (int i = 0; i < ENEMY_COUNT; ++i)
        {
            Enemy newEnemy = new Enemy();
            enemies.Add(newEnemy);
            base.AddComponent(newEnemy);
        }
    }
}

Tienes la idea general.

También mantendría la referencia de Enginedentro de todas sus GameScreenclases, para poder agregar nuevas pantallas incluso dentro de una GameScreenclase (por ejemplo, cuando el usuario hace clic en el botón StartGame mientras está dentro de su MainMenuScreen, puede hacer la transición a GameplayScreen).

Lo mismo ocurre con la Componentclase: debe contener la referencia de su padre GameScreen, para tener acceso tanto a la Engineclase como a su padre GameScreenpara agregar nuevos componentes (por ejemplo, puede hacer una clase relacionada con HUD llamada DrawableButtonque contiene un DrawableTextcomponente y un StaticBackgroundcomponente).

Incluso puede aplicar otros patrones de diseño después de eso, como el "patrón de diseño de servicios" (no estoy seguro sobre el nombre exacto) donde puede mantener diferentes servicios útiles dentro de su Engineclase (simplemente mantiene una lista de IServicesy permite que otras clases agreguen servicios ellos mismos) ) Por ejemplo, mantendría un Camera2Dcomponente en todo mi proyecto como un servicio para aplicar su transformación al dibujar otros componentes. Esto evita tener que pasarlo como parámetro en todas partes.

En conclusión, ciertamente puede haber otros mejores diseños para un motor, pero el motor propuesto por este enlace me pareció muy elegante, extremadamente fácil de mantener y reutilizar. Yo personalmente recomendaría al menos intentarlo.

Jesse Emond
fuente
1
XNA admite todo esto de forma inmediata a través de GameComponentservicios y servicios. No hay necesidad de una Engineclase separada .
BlueRaja - Danny Pflughoeft
+1 por esta respuesta. Además, ¿tendría sentido poner el dibujo en un hilo separado del hilo de actualización del juego?
f20k
1
@BlueRaja - DannyPflughoeft: De hecho, pero asumí que XNA no se usó, así que le expliqué un poco más sobre cómo hacerlo.
Jesse Emond el
@ f20k: Sí, casi de la misma manera que XNA lo hace. Sin embargo, no sé cómo se implementa. = / Debe haber un artículo en algún lugar de Internet sobre cómo hacerlo. :)
Jesse Emond
Sí, no estoy usando XNA pero parece una buena alternativa a lo que estoy haciendo. Actualmente estoy leyendo el libro "Head First Design Patterns" para ver si hay más alternativas. ¡Salud!
Dlaor
1

Lo que desea hacer es separar su código en componentes y servicios. XNA ya tiene soporte incorporado para estos.

Vea este artículo sobre GameComponentsy servicios. Luego vea esta respuesta sobre el uso de servicios en XNA, y esta respuesta más general sobre servicios en cualquier idioma.

BlueRaja - Danny Pflughoeft
fuente
-3

El hecho de que sean estáticos o globales no significa que siempre tengan que cargarse / inicializarse. Use un patrón Singleton y permita que los gerentes puedan liberarse y cargarse nuevamente bajo demanda.

Bo Buchanan
fuente
44
Un singleton es solo una fachada para el problema subyacente que el OP está tratando de resolver aquí.