Acabo de leer una respuesta a una pregunta sobre la estructuración del código del juego . Me hizo preguntarme sobre la GameManager
clase 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, GameManager
es 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 GameManager
clase no es una gran idea. Pero lo mismo puede suceder a una limpia GameApplication
o GameStateMachine
o GameSystem
que 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.
fuente
GameManager
efecto que describí anteriormente?Respuestas:
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.
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 .
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:
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.
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.
fuente
GameManager
que 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 esteGameManager
efecto.Esto no es realmente un problema con la programación de juegos, sino con la programación en general.
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"?
fuente
SceneManager
o unNetworkManager
? 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 enGameManager
algo, menos propenso a la acumulación de código.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 refactorizandoGameStatesCollection
. Boom, un objeto de dios.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.
fuente
En el pasado, generalmente termino con una clase de dios si hago lo siguiente:
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.
fuente
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.
fuente