Estoy haciendo un juego 2D de arriba hacia abajo y quiero tener muchos tipos de ataque diferentes. Me gustaría hacer que los ataques sean muy flexibles y combinables como funciona The Binding of Isaac. Aquí hay una lista de todos los coleccionables del juego . Para encontrar un buen ejemplo, echemos un vistazo al artículo de Spoon Bender .
Spoon Bender le da a Isaac la capacidad de disparar lágrimas de referencia.
Si observa la sección "sinergias", verá que se puede combinar con otros objetos coleccionables para obtener efectos interesantes pero intuitivos. Por ejemplo, si se combina con The Inner Eye , "le permitirá a Isaac disparar múltiples tiros de referencia al mismo tiempo". Esto tiene sentido, porque el ojo interno
Le da a Isaac un triple disparo
¿Qué es una buena arquitectura para diseñar cosas como esta? Aquí hay una solución de fuerza bruta:
if not spoon bender and not the inner eye then ...
if spoon bender and not the inner eye then ...
if not spoon bender and the inner eye then ...
if spoon bender and the inner eye then ...
Pero eso se saldrá de control muy rápido. ¿Cuál es una mejor manera de diseñar un sistema como este?
fuente
Respuestas:
No es necesario codificar manualmente las combinaciones. En su lugar, puede centrarse en las propiedades que le da cada elemento. Por ejemplo, el artículo A establece
Projectile=Fireball,Targetting=Homing
. Artículo B estableceFireMode=ArcShot,Count=3
. LaArcShot
lógica es responsable de enviar elCount
número deProjectile
elementos en un arco.Estos dos elementos se pueden combinar con cualquier otro elemento que modifique estas (u otras) propiedades libremente. Si agrega un nuevo tipo de proyectil, simplemente funcionará automáticamente con
ArcShot
, y si agrega un nuevo modo de disparo, funcionará automáticamente conFireball
proyectiles. Del mismo modo,Targetting
es una propiedad que establece el controlador para los proyectiles mientrasFireMode
crea los proyectiles, por lo que pueden combinarse fácil y trivialmente en cualquier combinación, según sea conveniente.También puede establecer un conjunto de dependencias de propiedad y tal. Por ejemplo,
ArcShot
requiere que tenga un proveedor deProjectile
(que podría ser el predeterminado). Puede establecer prioridades para que si tiene dos elementos activos que proporcionanProjectile
el código sepa cuál usar. O puede proporcionar la interfaz de usuario para permitir que el usuario seleccione qué tipo de proyectil usar, o simplemente requerir que el jugador desequipee los elementos de alta prioridad que no quiere, o use el elemento más reciente, etc. También puede permitir un sistema de incompatibilidades , por ejemplo, de modo que dos elementos que ambos simplemente modificanProjectile
no puedan equiparse simultáneamente.En general, cuando sea posible, prefiera cualquier tipo de enfoque basado en datos (o declarativo ) sobre enfoques de procedimiento (el gran lío si no es así) cuando se trata de los objetos y demás en su juego. La lógica genérica de nivel superior que es configurable por datos simples es mucho más preferible que las listas codificadas de reglas especiales.
fuente
Si está utilizando un lenguaje OOP, este suena como un buen lugar para emplear el Patrón Decorador . Cuando desee modificar cómo ocurre un ataque, simplemente decórelo con el aumento apropiado.
Crudo c ++ Ejemplo:
Este método sería mejor si tiene una gran cantidad de ataques y necesita que todos se comporten de la misma manera. Si desea cambiar sustancialmente la forma en que ocurre el ataque con el modificador (por ejemplo, nueva animación con modificador), este método no es para usted.
fuente
Attack
método del objeto que agrega. LaTripleAttack
clase no debe saber sobre laTearAttack
clase. Si esto fuera cierto, provocaría tantos dolores de cabeza como elelse-if
bloqueo. Esto significa que cualquier animación de lágrima debe residir dentro delTearAttackBehaviour
objeto. Este objeto no (y no debería) saber que ha sido decorado por unTripleAttack
objeto. El resultado es que las 3 animaciones de lágrimas proceden de forma independiente, porque son independientes.Como fanático de Binding of Isaac, yo también me he preguntado cómo hacer algo como esto. El sistema en el juego es lo suficientemente robusto donde los comportamientos emergentes surgen de la combinación de efectos (el que me viene a la mente es obtener espejo, doblador de cuchara y algunos amplificadores de alcance dan como resultado una pared de lágrimas giratoria y giratoria alrededor de Isaac, estilo Magneto ) El gran número de ellos haría poco práctico un bloque "si".
Mi conclusión es que Isaac y sus lágrimas son dos entidades en el centro de un marco masivo de componentes y entidades. . Las entidades tienen algunas estadísticas básicas (velocidad de movimiento, vida, rango, daño, sprite, etc.) y cada componente traería consigo un modificador de estadísticas y un verbo.
En código, Isaac y sus lágrimas tendrían una lista que contendría cosas de una interfaz. Isaac tendría una lista de cosas que se suscriben a la interfaz IsaacMutator, y sus lágrimas tearMutator. IsaacMutator tendría funciones para modificar la salud, la velocidad, el alcance, el aspecto y algunos verbos especiales de Isaacs. TearMutator sería similar. Una vez por ciclo de juego, Isaac recorrería todos los IsaacMutators que tiene, y todas las lágrimas vivas también lo harían. Para seguir su ejemplo en inglés, se leería así:
y así. Debido a que los tipos son aditivos, puede apilar, agregar y eliminar el contenido de su corazón.
fuente
Creo que tu camino funciona mejor. Este tipo de elementos dan una condición, si se usan juntos producen una condición diferente, entonces necesitaría efectivamente las 3 condiciones posibles definidas.
También podría hacerlo creando un nuevo tipo de definición cuando ambos elementos están presentes, pero esto en realidad se suma a la convolución:
fuente