¿Cómo evitar el objeto dios GameManager?

51

Acabo de leer una respuesta a una pregunta sobre la estructuración del código del juego . Me hizo preguntarme sobre la GameManagerclase omnipresente y cómo a menudo se convierte en un problema en un entorno de producción. Déjame describir esto.

Primero, hay prototipos. A nadie le importa escribir un gran código, solo tratamos de ejecutar algo para ver si el juego se acumula.

Luego hay una luz verde, y en un esfuerzo por limpiar las cosas, alguien escribe un GameManager. Probablemente para sostener un montón de GameStates, tal vez para almacenar algunos GameObjects, nada grande, en realidad. Un lindo y pequeño gerente.

En el reino pacífico de la preproducción, el juego se está perfilando bien. Los programadores tienen noches de sueño adecuadas y muchas ideas para diseñar la arquitectura con patrones de diseño geniales.

Luego comienza la producción y pronto, por supuesto, hay un momento crucial. La dieta equilibrada ya no existe, el rastreador de errores tiene muchos problemas, la gente está estresada y el juego tiene que ser lanzado ayer.

En ese momento, por lo general, GameManageres un verdadero desastre (para ser cortés).

La razón de eso es simple. Después de todo, al escribir un juego, bueno ... todo el código fuente está realmente aquí para administrar el juego . Es fácil agregar esta pequeña característica adicional o corrección de errores en el GameManager, donde todo lo demás ya está almacenado de todos modos. Cuando el tiempo se convierte en un problema, no hay forma de escribir una clase separada o dividir a este gerente gigante en subgerentes.

Por supuesto, este es un antipatrón clásico: el objeto de Dios . Es algo malo, un dolor para fusionarse, un dolor para mantener, un dolor para comprender, un dolor para transformar.

¿Qué sugeriría para evitar que esto suceda?

EDITAR

Sé que es tentador culpar a los nombres. Por supuesto, crear una GameManagerclase no es una gran idea. Pero lo mismo puede suceder a una limpia GameApplicationo GameStateMachineo GameSystemque termina siendo la cinta adhesiva-favorito para el final de un proyecto. No importa el nombre, tienes esa clase en algún lugar de tu código de juego, es solo un embrión por ahora: simplemente no sabes en qué monstruo se convertirá. Así que no espero respuestas de "culpar a los nombres". Quiero una manera de evitar que esto suceda, un archivo de codificación y / o un proceso que un equipo pueda seguir sabiendo que esto sucederá en algún momento de la producción. Es una lástima tirar, digamos, un mes de correcciones de errores y funciones de último minuto, solo porque todas ellas ahora están en un enorme administrador que no se puede mantener.

Laurent Couvidou
fuente
Ya sea que se llame GameManager o ControllerOfTheGame, o como quieras llamarlo, me refiero a evitar una clase que se encargue de controlar la lógica de todo el juego. Sabes que lo estás haciendo cuando lo haces, independientemente de lo que planeas llamar. Creo que en mi último juego tuve un controlador de nivel que comenzó solo para mantener el estado del nivel para guardar, pero si me había detenido y lo pensaba, era obvio que este objeto estaba destinado a manejar más detalles de lo que debería.
brandon
@brandon Permíteme reformular mi pregunta para ti: ¿cómo controlas la lógica de todo el juego sin exponerte al GameManagerefecto que describí anteriormente?
Laurent Couvidou
Depende de la lógica. Las cosas obvias como jugador, enemigo, estructura, etc., que claramente tienen su propia lógica, se ponen en sus respectivas clases. Lo que parece estar preguntando más es cómo manejar el estado del juego. Por lo general, habrá una clase que realiza un seguimiento del estado de todo el juego, pero en mi experiencia es una de las últimas cosas por las que debe preocuparse cuando realiza prototipos, y su propósito debe ser muy claro. Básicamente, usted hace una planificación inicial antes de crear prototipos o comienza desde cero cuando comienza la producción para evitar el uso de clases basura que eran solo para pruebas.
Brandon
Curiosamente, XNA crea este desastre potencial para usted. Por defecto, se crea una clase de Juego para administrar, bueno, todo. Contiene los principales métodos de actualización, dibujo, inicialización y carga. De hecho, estoy en el proceso de migrar a un nuevo proyecto porque mi juego (no tan complejo) se volvió demasiado difícil de manejar con todo dentro de esa clase. Además, los tutoriales de Microsoft parecen alentarlo.
Fibericon

Respuestas:

23

Luego hay una luz verde, y en un esfuerzo por limpiar las cosas, alguien escribe un GameManager. Probablemente para guardar un montón de GameStates, tal vez para almacenar algunos GameObjects, nada grande, de verdad. Un lindo y pequeño gerente.

Sabes, mientras leía esto, tenía pequeñas alarmas sonando en mi cabeza. Un objeto con el nombre "GameManager" nunca será lindo o pequeño. ¿Y alguien hizo esto para limpiar el código? ¿Cómo se veía antes? Bien, bromas aparte: el nombre de una clase debería ser una indicación clara de lo que hace la clase, y esto debería ser una cosa (también conocido como principio de responsabilidad única ).

Además, aún puede terminar con un objeto como GameManager, pero claramente, existe en un nivel muy alto, y debería ocuparse de tareas de alto nivel. ¿Mantener un catálogo de objetos del juego? Quizás. ¿Facilita la comunicación entre los objetos del juego? Seguro. Cálculo de colisiones entre objetos? ¡No! Esta es también la razón por la que el nombre Manager está mal visto: es demasiado amplio y permite muchos abusos bajo ese banner.

Una regla general rápida sobre el tamaño de las clases: si se encuentra con varios cientos de líneas de código por clase, algo comienza a salir mal. Sin ser demasiado celoso, algo más, digamos, 300 LOC es un olor a código para mí, y si vas a más de 1000, las campanas de advertencia deberían sonar. Al creer que de alguna manera que 1000 líneas de código es más fácil de entender que 4 clases bien estructuradas de 250 cada una, te estás engañando a ti mismo.

Cuando el tiempo se convierte en un problema, no hay forma de escribir una clase separada o dividir a este gerente gigante en subgerentes.

Creo que este es el caso solo porque el problema puede propagarse hasta el punto en que todo es un completo desastre. La práctica de refactorización es realmente lo que está buscando: necesita mejorar continuamente el diseño del código en pequeños incrementos .

¿Qué sugeriría para evitar que esto suceda?

El problema no es tecnológico, por lo que no debe buscar soluciones tecnológicas. El problema es este: hay una tendencia en su equipo a crear piezas de código monolíticas, y la creencia de que de alguna manera es beneficioso a mediano / largo plazo trabajar de esta manera. También parece que el equipo carece de un liderazgo arquitectónico fuerte, que dirigiría la arquitectura del juego (o al menos, esta persona está demasiado ocupada para realizar esta tarea). Básicamente, la única salida es hacer que los miembros del equipo reconozcan que este pensamiento es incorrecto. A nadie le favorece. La calidad del producto empeorará, y el equipo solo pasará aún más noches arreglando cosas.

La buena noticia es que los beneficios inmediatos y tangibles de escribir código limpio son tan grandes que casi todos los desarrolladores se dan cuenta de sus beneficios muy rápidamente. Convencer al equipo de trabajar de esta manera por un tiempo, los resultados harán el resto.

La parte difícil es que desarrollar una idea de lo que constituye un mal código (y un talento para encontrar rápidamente un mejor diseño) es una de las habilidades más difíciles de aprender en el desarrollo. Mi sugerencia gira en torno a la esperanza de que tengas a alguien lo suficientemente alto en el equipo que pueda hacer esto: es mucho más fácil convencer a la gente de esa manera.

Editar - Un poco más de información:

En general, no creo que tu problema se limite al desarrollo del juego. En esencia, es un problema de ingeniería de software, de ahí mis comentarios en esa dirección. Lo que puede ser diferente es la naturaleza de la industria de desarrollo de juegos, ya sea que esté más orientada a resultados y plazos que otros tipos de desarrollo, no estoy seguro.

Sin embargo, específicamente para el desarrollo de juegos, la respuesta aceptada a esta pregunta en StackOverflow con respecto a los consejos de "especialmente arquitectura de juegos" dice:

Siga los principios sólidos del diseño orientado a objetos ...

Esto es esencialmente exactamente lo que estoy diciendo. Cuando estoy bajo presión, también me encuentro escribiendo grandes piezas de código, pero me he metido en la cabeza que eso es una deuda técnica. Lo que suele funcionar bien para mí es pasar la primera mitad (o tres cuartos) del día produciendo una gran cantidad de código de calidad media, y luego tomar un descanso y pensarlo un momento; hacer un poco de diseño en mi cabeza, o en papel / pizarra, sobre cómo mejorar un poco el código. A menudo, noto código repetitivo, y puedo reducir las líneas de código totales al dividir las cosas, al tiempo que mejora la legibilidad. Esta vez invertido se amortiza tan rápido que llamarlo una "inversión" suena tonto; con frecuencia, recojo errores que podrían haber desperdiciado la mitad de mi día (una semana después) si hubiera permitido que continuara.

  • Arregle las cosas el mismo día que las codifique.
  • Se alegrará de haberlo hecho en cuestión de horas.

Llegar a creer realmente lo anterior es difícil; Logré hacerlo por mi propio trabajo solo porque he experimentado, una y otra vez, los efectos. Aun así, todavía es difícil para mí justificar la reparación del código cuando podría estar produciendo más ... Así que definitivamente entiendo de dónde vienes. Desafortunadamente, este consejo es quizás demasiado genérico y no es fácil de hacer. ¡Creo firmemente en ello, sin embargo! :)

Para responder a su ejemplo específico:

El código limpio del tío Bob hace un trabajo increíble al resumir cómo es el código de buena calidad. Estoy de acuerdo con casi todos sus contenidos. Entonces, cuando pienso en su ejemplo de una clase de gerente de 30 000 LOC, realmente no puedo estar de acuerdo con la parte de "buena razón". No quiero parecer ofensivo, pero pensar de esa manera conducirá al problema. No hay una buena razón para tener tanto código en un solo archivo: ¡son casi 1000 páginas de texto! Cualquier beneficio de la localidad (velocidad de ejecución o "simplicidad" del diseño) será anulado de inmediato por los desarrolladores que están completamente empantanados tratando de navegar esa monstruosidad, y eso es incluso antes de discutir la fusión, etc.

Si no está convencido, mi mejor sugerencia sería tomar una copia del libro anterior y echarle un vistazo. La aplicación de ese tipo de pensamiento lleva a las personas a crear voluntariamente un código limpio y autoexplicativo, que está bien estructurado.

Daniel B
fuente
Esto no es un defecto que he visto una vez. He visto esto en varios equipos, para varios proyectos. He visto a muchas personas talentosas y estructuradas que luchan con esto. Cuando está bajo presión, el límite de 300 líneas de código no es algo que le importe a su productor. Ni el jugador por cierto. Claro que esto es agradable y dulce, pero el diablo está en los detalles. El peor de los casos GameManagerque he visto hasta ahora fue de alrededor de 30,000 líneas de código, y estoy bastante seguro de que la mayoría estuvo aquí por una muy buena razón. Esto es extremo, por supuesto, pero esas cosas suceden en el mundo real. Estoy pidiendo una forma de limitar este GameManagerefecto.
Laurent Couvidou
@lorancou: agregué bastante información adicional a la publicación, fue demasiado para un comentario, espero que te dé una idea un poco más, incluso si no puedo señalarte ninguna arquitectura arquitectónica concreta ejemplos
Daniel B
Ah, no leí ese libro, así que definitivamente es una buena sugerencia. Ah, y no quise decir que hay una buena razón para agregar código en una clase de miles de líneas. Quise decir que todo el código en ese tipo de objeto de dios hace algo útil. Probablemente escribí eso demasiado rápido;)
Laurent Couvidou
@lorancou Jeje, eso es bueno ... de esa manera se encuentra la locura :)
Daniel B
"La práctica de refactorización es realmente lo que está buscando: necesita mejorar continuamente el diseño del código en pequeños incrementos". Creo que tienes un punto muy fuerte allí. El desorden atrae el desorden, esa es la verdadera razón detrás de esto, por lo que el desorden debe ser combatido a largo plazo. Voy a aceptar esta respuesta, ¡pero eso no significa que no tenga valor en los demás!
Laurent Couvidou
7

Esto no es realmente un problema con la programación de juegos, sino con la programación en general.

todo el código fuente está realmente aquí para administrar el juego.

Es por eso que debe fruncir el ceño ante cualquier codificador orientado a objetos que sugiera clases con "Administrador" en el nombre. Para ser justos, creo que es prácticamente imposible evitar los objetos "semigod" en el juego, pero los llamamos "sistema".

Cualquier software tiende a ensuciarse cuando se acerca la fecha límite. Eso es parte del trabajo. Y no hay nada intrínsecamente malo con la " programación de tipo de conducto ", especialmente cuando debe enviar su producto en unas pocas horas. No puede deshacerse de este problema totalmente, simplemente puede reducirlo.

Aunque obviamente, si estás tentado a poner cosas en el GameManager, significa que no tienes una base de código muy saludable. Podría haber dos problemas:

  • Tu código es demasiado complejo. Demasiados patrones, optimización prematura, ya nadie entiende el flujo. Intente usar más TDD cuando desarrolle, si puede, porque es una muy buena solución para combatir la complejidad.

  • Tu código no está bien estructurado. Estás (ab) usando la variable global, las clases no están unidas, no hay interfaz, sistema de eventos, etc.

Si el código parece estar bien, tendrá que recordar, para cada proyecto, "qué salió mal" y evitarlo la próxima vez.

Finalmente, también podría ser un problema de gestión del equipo: ¿hubo suficiente tiempo? ¿Sabían todos acerca de la arquitectura que buscaban? ¿Quién demonios permitió un compromiso con una clase con la palabra "Gerente"?

Raveline
fuente
No hay nada malo con la programación de cinta adhesiva, te lo doy. Pero estoy bastante seguro de que las clases de Manager están presentes en los motores de juego, al menos las he visto mucho. Son útiles siempre que no crezcan demasiado ... ¿Qué tiene de malo un SceneManagero un NetworkManager? No veo por qué deberíamos evitarlos, ni cómo ayuda reemplazar -Manager por -System. Estoy buscando sugerencias para una arquitectura de reemplazo para lo que generalmente entra en GameManageralgo, menos propenso a la acumulación de código.
Laurent Couvidou
¿Qué le pasa a un SceneManager? Bueno, ¿cuál es la diferencia entre una escena y un SceneManager? ¿Una red y un administrador de red? En el otro aspecto: no creo que este sea un problema de arquitectura, pero si pudieras dar un ejemplo concreto de algo que está en GameManager y no debería estar allí, sería más fácil sugerir una solución.
Raveline
OK, así que olvídate de lo de -Manager. Digamos que tienes una clase que maneja tus estados de juego, llamémosla GameStatesCollection. Al principio solo tendrás algunos estados de juego y formas de cambiar entre ellos. Luego están las pantallas de carga que vienen y complican todo esto. Luego, algún programador tiene que lidiar con un error cuando el usuario expulsa el disco mientras cambia de Nivel_A a Nivel_B, y la única forma de solucionarlo sin pasar medio día refactorizando GameStatesCollection. Boom, un objeto de dios.
Laurent Couvidou
5

Entonces, trayendo una posible solución del mundo más empresarial: ¿ha verificado la inversión de inyección de control / dependencia?

Básicamente, obtienes este marco que es un contenedor para todo lo demás en tu juego. Él, y solo eso, sabe cómo conectar todo y cuáles son las relaciones entre sus objetos. Ninguno de sus objetos crea una instancia dentro de sus propios constructores. En lugar de crear lo que necesitan, piden lo que necesitan del marco de trabajo de IoC; después de la creación, el marco de trabajo de IoC "sabe" qué objeto se necesita para satisfacer la dependencia y la llena. Acoplamiento suelto FTW.

Cuando su clase EnemyTreasure le pide al contenedor un objeto GameLevel, el marco sabe qué instancia darle. Como lo sabe Tal vez lo haya instanciado antes, tal vez le pregunte a GameLevelFactory cercano. El punto es que EnemyTreasure no sabe de dónde vino la instancia de GameLevel. Solo sabe que su dependencia ahora está satisfecha y puede continuar con su vida.

Oh, veo que su clase PlayerSprite implementa IUpdateable. Bueno, eso es genial, porque el marco suscribe automáticamente cada objeto IUpdateable al temporizador, que es donde está el bucle principal del juego. Every Timer :: tick () llama automáticamente a todos los métodos IUpdatedable :: update (). Fácil, verdad?

Siendo realistas, no necesitas este marco bighugeohmygod para hacer esto por ti: puedes hacer las partes importantes de ti mismo. El punto es que si te encuentras haciendo objetos tipo -Manager entonces, lo más probable es que no estés distribuyendo tus responsabilidades correctamente entre tus clases o te falten algunas clases en alguna parte.

drhayes
fuente
Interesante, la primera vez que escuché sobre IoC. Esto podría ser demasiado pesado para la programación del juego, ¡pero lo comprobaré!
Laurent Couvidou
IoC, ¿acaso no solíamos llamarlo sentido común?
Brice
Dada la posibilidad, un ingeniero va a hacer un marco de cualquier cosa. (= Además, no estoy abogando por usar el marco, estoy abogando por el patrón arquitectónico.
drhayes
3

En el pasado, generalmente termino con una clase de dios si hago lo siguiente:

  • siéntate en el editor del juego / IDE / bloc de notas antes de escribir un diagrama de clase (o simplemente un boceto de los objetos principales)
  • Comienza con una idea muy vaga del juego e inmediatamente codifícalo, luego comienza a iterar sobre nuevas ideas a medida que surjan
  • crear un archivo de clase llamado GameManager cuando no estoy haciendo un juego basado en el estado como una estrategia por turnos donde GameManager es en realidad un objeto de dominio
  • no me importa la organización del código desde el principio

Esto puede ser controvertido, de lo que me reiré si lo es, pero no puedo pensar en una sola vez, incluso en la creación de prototipos, que no debería escribir un diagrama de clase muy básico antes de escribir un prototipo jugable. Tiendo a probar ideas tecnológicas antes de planificar, pero eso es todo. Entonces, si quisiera hacer un portal, escribiría un código muy básico para que los portales funcionen, luego diría que es un buen momento para una planificación menor y luego puedo trabajar en el prototipo jugable.

Brandon
fuente
1

Sé que esta pregunta es antigua ahora, pero pensé que agregaría mis 2 centavos.

Cuando diseño juegos, casi siempre tengo un objeto Juego. Sin embargo, es de muy alto nivel y realmente no "hace nada" en sí mismo.

Por ejemplo, le dice a la clase Graphics que procese, pero no tiene nada que ver con el proceso de renderizado. Puede pedirle al LevelManager que cargue un nuevo nivel, pero no tiene nada que ver con el proceso de carga.

En resumen, uso un objeto semi-divino para orquestar el uso de las otras clases.

OMGtechy
fuente
2
Esto no es muy parecido a un dios, se llama abstracción. :)
Vaughan Hilts