Asesoramiento sobre architecture de juegos / patrones de layout

He estado trabajando en un 2d RPG por un time, y me he dado count de que he tomado algunas malas decisiones de layout. Hay algunas cosas en particular que me están causando problemas, así que me preguntaba qué tipo de layouts usaban otras personas para superarlos.

Para un poco de historia, comencé a trabajar en ello en mi time libre el verano pasado. Inicialmente estaba haciendo el juego en C #, pero hace aproximadamente 3 meses, decidí cambiar a C ++. Quería tener un buen event handling C ++ ya que ha pasado un time desde que lo usé mucho, y pensé que un proyecto interesante como este sería un buen motivador. He estado usando la biblioteca de impulso extensivamente y he estado usando SFML para charts y FMOD para audio.

Tengo un poco de código escrito, pero estoy considerando eliminarlo y empezar de nuevo.

Estas son las principales áreas de preocupación que tengo y quería get algunas opiniones sobre la manera correcta en que otros las resolvieron o las resolverían.

1. Dependencias cíclicas Cuando estaba haciendo el juego en C #, realmente no tenía que preocuparme por esto, ya que no es un problema allí. Pasar a C ++, esto se ha convertido en un problema bastante importante y me hizo pensar que puede haber diseñado cosas incorrectamente. Realmente no puedo imaginar cómo desacoplar mis classs y aún hacer que hagan lo que quiero. Aquí hay algunos ejemplos de una cadena de dependencia:

Tengo una class de efecto de estado. La class tiene una serie de methods (Aplicar / No aplicar, Marcar, etc.) para aplicar sus efectos contra un personaje. Por ejemplo,

virtual void TickCharacter(Character::BaseCharacter* character, Battles::BattleField *field, int ticks = 1); 

Estas funciones se llamarían cada vez que el personaje infligido con el efecto de estado tome un turno. Sería útil implementar efectos como Regen, Poison, etc. Sin embargo, también introduce dependencies en la class BaseCharacter y en la class BattleField. Naturalmente, la class BaseCharacter necesita realizar un seguimiento de los efectos de estado que están activos actualmente en ellos, por lo que se trata de una dependencia cíclica. Battlefield necesita hacer un seguimiento de las partes en combate, y la class party tiene una list de personajes base que introduce otra dependencia cíclica.

2 – Eventos

En C # hice un uso extensivo de delegates para enganchar events en personajes, campos de batalla, etc. (por ejemplo, había un delegado para cuando cambiaba la salud del personaje, cuando cambiaba una estadística, cuando se agregaba / eliminaba un efecto de estado, etc. .) y los componentes charts / del campo de batalla se unirían a esos delegates para imponer sus efectos. En C ++, hice algo similar. Obviamente no hay un equivalente directo a los delegates de C #, así que en su lugar creé algo como esto:

 typedef boost::function<void(BaseCharacter*, int oldvalue, int newvalue)> StatChangeFunction; 

y en mi class de personaje

 std::map<std::string, StatChangeFunction> StatChangeEventHandlers; 

cada vez que cambiaba la estadística del personaje, iteraba y llamaba cada StatChangeFunction en el map. Mientras funciona, me preocupa que este sea un mal enfoque para hacer las cosas.

3 – Gráficos

Esto es lo más importante. No está relacionado con la biblioteca de charts que estoy usando, pero es más una cuestión conceptual. En C #, he acoplado charts con muchas de mis classs, y sé que es una idea terrible. Queriendo hacerlo desacoplado esta vez probé un enfoque diferente.

Para implementar mis charts, imaginaba todos los charts relacionados en el juego como una serie de pantallas. Es decir, hay una pantalla de título, una pantalla de estado de personaje, una pantalla de map, una pantalla de deviseio, una pantalla de batalla, una pantalla GUI de batalla, y básicamente podría astackr estas pantallas una encima de la otra según sea necesario para crear los charts del juego. Cualquiera que sea la pantalla activa es propiedad de la input del juego.

Diseñé un administrador de pantalla que presionaría y mostraría pantallas en function de la input del usuario.

Por ejemplo, si estuvieras en una pantalla de map (un manejador de input / visualizador para un map de mosaico) y presionas el button de inicio, emitiría una llamada al administrador de pantalla para presionar una pantalla de menu principal sobre la pantalla del map y marcar el map pantalla para no dibujar / actualizar. El jugador navegaría por el menu, que emitiría más commands al administrador de pantalla, según correspondiera, para insert nuevas pantallas en la stack de pantallas, y luego las abriría mientras el usuario cambia las pantallas / cancela. Finalmente, cuando el jugador salga del menu principal, lo abrirá y volverá a la pantalla del map, lo comentará para ser dibujado / actualizado y continuará desde allí.

Las pantallas de batalla serían más complejas. Tendría una pantalla para actuar como background, una pantalla para visualizar a cada parte en la batalla, y una pantalla para visualizar la interfaz de usuario para la batalla. La interfaz de usuario se engancharía en los events de caracteres y los usaría para determinar cuándo actualizar / volver a dibujar los componentes de la interfaz de usuario. Finalmente, cada ataque que tenga una secuencia de commands de animation disponible llamaría a una capa adicional para animarse antes de salir de la stack de la pantalla. En este caso, cada capa se marca consistentemente como dibujable y actualizable y obtengo una stack de pantallas que manejan mis charts de batalla.

Si bien no he podido lograr que el administrador de pantalla funcione a la perfección, creo que puedo hacerlo con bastante time. Mi pregunta al respecto es, ¿es esto un enfoque que vale la pena? Si es un mal layout, quiero saberlo ahora antes de invertir demasiado time haciendo todas las pantallas que voy a necesitar. ¿Cómo construyes los charts para tu juego?

En general, no diría que nada de lo que enliste deba hacer que deseche el sistema y comience nuevamente. Esto es algo que cada progtwigdor quiere hacer aproximadamente del 50 al 75% del path a través de cualquier proyecto en el que esté trabajando, pero lleva a un ciclo interminable de desarrollo y nunca termina nada. Entonces, para ese fin, algunos retroalimentan en cada sección.

  1. Esto puede ser un problema, pero generalmente es más una molestia que cualquier otra cosa. ¿Estás utilizando #pragma una vez o #ifndef MY_HEADER_FILE_H #define MY_HEADER_FILE_H … #endif en la parte superior o rodeando tus files .h respectivamente? De esta forma, el file .h solo existe una vez dentro de cada ámbito. Si es así, mi recomendación es eliminar todas las declaraciones #include y comstackr, agregando las que sean necesarias para volver a comstackr el juego.

  2. Soy un fanático de este tipo de sistemas y no veo nada de malo en ello. Lo que es un evento en C # es comúnmente reemplazado por un sistema de events o un sistema de postría (puede search aquí las preguntas para encontrar más información). La key aquí es mantenerlos al mínimo cuando las cosas sucedan, lo cual ya suena como si estuvieras haciendo, así que no debería preocuparte por las mínimas preocupaciones.

  3. Esto también parece ser el path correcto para mí y es lo que hago para mis propios motores, tanto a nivel personal como profesional. Esto hace que el sistema de menu se convierta en un sistema de estado que tiene el menu raíz (antes de que comience el juego) o el HUD del jugador como la pantalla 'raíz' que se muestra, dependiendo de cómo lo configure.

Entonces, para resumir, no veo nada digno de reinicio en lo que te estás encontrando. Es posible que desee un reemploop más formal del sistema de events en el futuro, pero eso llegará a time. Cyclic includes es un obstáculo que todos los progtwigdores de C / C ++ tienen que saltar constantemente, y trabajar para desacoplar los charts parece lógico 'próximos pasos'.

¡Espero que esto ayude!

Sus dependencies cíclicas no deberían ser un problema siempre y cuando sea progresivo declarando las classs donde pueda en los files de encabezado y realmente #incluyendolas en los files .cpp (o lo que sea).

Para el sistema de events, dos sugerencias:

1) Si desea mantener el patrón que está usando ahora, considere cambiar a un boost :: unordenetworking_map en lugar de std :: map. El mapeo con cadenas como keys es lento, especialmente desde .NET hace algunas cosas bonitas bajo el capó para ayudar a acelerar las cosas. Usando unordenetworking_map hashes las cadenas para que las comparaciones sean generalmente más rápidas.

2) Considere cambiar a algo más poderoso como boost :: signals. Si lo haces, puedes hacer cosas buenas como hacer que los objects de tu juego sean rastreables derivando de boost :: signals :: trackable, y deja que el destructor se encargue de limpiar todo en lugar de tener que anular el logging manualmente desde el sistema de events. También puede tener múltiples señales apuntando a cada ranura (o viceversa, no recuerdo la nomenclatura exacta) por lo que es muy similar a hacer += en un delegate en C #. El mayor problema con las señales boost :: es que debe comstackrse, no solo los encabezados, por lo que dependiendo de su plataforma puede ser difícil comenzar a usarlo.