Considere un juego de cartas, como Hearthstone .
¡Hay cientos de tarjetas que hacen una gran variedad de cosas, algunas de las cuales son únicas incluso para una sola tarjeta! ¡Por ejemplo, hay una carta (llamada Nozdormu) que reduce los turnos de los jugadores a solo 15 segundos!
Cuando tienes una variedad tan amplia de efectos potenciales, ¿cómo evitas los números mágicos y los controles únicos en todo tu código? ¿Cómo se evita el método "Check_Nozdormu_In_Play" en la clase PlayerTurnTime? ¿Y cómo se puede organizar el código de modo que cuando agrega aún más efectos, no necesita refactorizar los sistemas centrales para admitir cosas que nunca antes han tenido que soportar?
architecture
software-engineering
scripting
Soñador Sable
fuente
fuente
Respuestas:
¿Ha examinado los sistemas de componentes de la entidad y las estrategias de mensajería de eventos?
Los efectos de estado deben ser componentes de algún tipo que puedan aplicar sus efectos persistentes en un método OnCreate (), expirar sus efectos en OnRemoved () y suscribirse a los mensajes de eventos del juego para aplicar efectos que ocurren como reacción a algo que sucede.
Si el efecto es persistente condicional (dura X turnos, pero se aplica solo bajo ciertas circunstancias), es posible que deba verificar esas condiciones en varias fases.
Luego, solo asegúrate de que tu juego no tenga números mágicos predeterminados también. Asegúrese de que todo lo que se pueda cambiar sea una variable basada en datos en lugar de valores predeterminados codificados con variables utilizadas para cualquier excepción.
De esta manera, nunca asumes cuál será la longitud del giro. Siempre es una variable constantemente verificada que puede modificarse por cualquier efecto y posiblemente deshacerse más tarde por el efecto cuando caduca. Nunca verifica las excepciones antes de omitir su número mágico.
fuente
RobStone está en el camino correcto, pero quería dar más detalles, ya que esto es exactamente lo que hice cuando escribí Dungeon Ho !, un Roguelike que tenía un sistema de efectos muy complejo para armas y hechizos.
Cada carta debe tener un conjunto de efectos adjuntos, definidos de tal manera que pueda indicar cuál es el efecto, a qué se dirige, cómo y por cuánto tiempo. Por ejemplo, un efecto de "dañar al oponente" podría verse más o menos así;
Luego, cuando se active el efecto, haga que una rutina genérica se encargue del procesamiento del efecto. Como un idiota, utilicé una gran declaración de caso / cambio:
Pero una forma mucho mejor y más modular de hacerlo es a través del polimorfismo. Cree una clase de efecto que envuelva todos estos datos, cree una subclase para cada tipo de efecto y luego haga que esa clase anule un método onExecute () específico de la clase.
Entonces tendríamos una clase de efecto básica, luego una clase DamageEffect con un método onExecute (), así que en nuestro código de procesamiento simplemente iríamos;
La forma de lidiar con saber lo que está en juego es crear un Vector / Array / lista vinculada / etc. de efectos activos (de tipo Efecto, la clase base) adjuntos a cualquier objeto (incluido el campo de juego / "juego"), por lo que en lugar de tener que verificar si un efecto particular está en juego, simplemente recorre todos los efectos adjuntos a el objeto (s) y dejarlos ejecutar. Si un efecto no está asociado a un objeto, no está en juego.
fuente
Ofreceré un puñado de sugerencias. Algunos de ellos se contradicen entre sí. Pero tal vez algunos sean útiles.
Considere listas versus banderas
Puede recorrer el mundo y marcar una bandera en cada elemento para decidir si se debe hacer la bandera. O puede mantener una lista de solo aquellos elementos que deberían hacer la bandera.
Considere listas y enumeraciones
Puede seguir agregando campos booleanos a su clase de elemento, isAThis y isAThat. O puede tener una lista de cadenas o elementos de enumeración, como {"isAThis", "isAThat"} o {IS_A_THIS, IS_A_THAT}. De esa manera, puede agregar nuevos en la enumeración (o consts de cadena) sin agregar campos. No es que haya nada realmente malo al agregar campos ...
Considerar punteros de función
En lugar de una lista de indicadores o enumeraciones, podría tener una lista de acciones para ejecutar ese elemento en diferentes contextos. (Entidad-ish ...)
Considerar objetos
Algunas personas prefieren enfoques basados en datos, guiones o entidades componentes. Pero también vale la pena considerar las jerarquías de objetos anticuados. La clase base necesita aceptar las acciones, como "jugar esta carta para la fase de turno B" o lo que sea. Luego, cada tipo de tarjeta puede anular y responder según corresponda. Probablemente también haya un objeto de jugador y un objeto de juego, por lo que el juego puede hacer cosas como, si (player-> isAllowedToPlay ()) {hace el juego ...}.
Considere la capacidad de depuración
Una vez que lo bueno de una pila de campos de banderas es que puede examinar e imprimir el estado de cada elemento de la misma manera. Si el estado está representado por diferentes tipos, o bolsas de componentes, o punteros de función, o está en diferentes listas, puede que no sea suficiente solo mirar los campos del elemento. Todo son compensaciones.
Eventualmente, refactorización: considere las pruebas unitarias
No importa cuánto generalice su arquitectura, podrá imaginar cosas que no cubre. Entonces tendrás que refactorizar. Quizás un poco, quizás mucho.
Una forma de hacer esto más seguro es con un cuerpo de pruebas unitarias. De esa manera, puede estar seguro de que, aunque haya reorganizado las cosas debajo (¡tal vez por mucho!), La funcionalidad existente aún funciona. Cada prueba unitaria generalmente se ve así:
Como puede ver, mantener estables las llamadas de API de nivel superior en el juego (o jugador, tarjeta, etc.) es clave para la estrategia de prueba de la unidad.
fuente
En lugar de pensar en cada carta individualmente, comience a pensar en términos de categorías de efectos, y las cartas contienen una o más de estas categorías. Por ejemplo, para calcular la cantidad de tiempo en un turno, puede recorrer todas las cartas en juego y verificar la categoría "manipular duración del turno" de cada carta que contiene esa categoría. Cada carta incrementa o sobrescribe la duración del turno según las reglas que hayas decidido.
Este es esencialmente un sistema de mini componentes, donde cada objeto "tarjeta" es simplemente un contenedor para un grupo de componentes de efectos.
fuente