Desarrollando un minijuego

Aunque actualmente me gustan los videojuegos con buenas tramas, personajes complejos, giros inesperados y todos esos detalles que hacen interesante a una historia, cuando era más jóven solía jugar a un juego llamado “Puyo Pop Fever” que también me enganchaba bastante, a pesar de ser uno de esos juegos simplones tipo “Tetris”.

Mi primer contacto con este tipo de juegos tipo Puyo fue el “Kirby’s Ghost Trap” de Super Nintendo. Tardé tan solo uno o dos días en completarlo en la máxima dificultad.
La mecánica de estos juegos consiste en una matriz de 6×12 sobre la que van cayendo pares de bolitas de colores. Al juntar cuatro del mismo color desaparecen y vas sumando puntos. Puedes moverlas hacia la izquierda, derecha o rotarlas en sentido horario o antihorario. Luego tiene algunas complicaciones extra como aumentos de velocidad o piezas especiales, pero eso ya es otro tema.

El caso es que, dado que a lo largo de la vida he empezado a programar muchos videojuegos y rara vez los completo (principalmente por falta de un diseñador 3D, un músico, un -insertar cualquier cosa que no sea un programador-). Así que decidí ponerme a hacer algo sencillito que pudiese terminar, y seguramente por nostalgia ese algo resultó ser un juego tipo “Puyo”.

Quería ambientarlo en el universo de Parámetro Zero, así que pensé que las bolitas que caen serían partículas y el juego en sí representaría una simulación del interior del núcleo de un generador de energía. También tengo intención de dotarlo de una historia simple más adelante, pero bueno, todo a su tiempo.

El juego lo estoy haciendo en el Unreal Engine 4 a base de blueprints. Tengo experiencia con este motor y la verdad es que me parece que en el aspecto técnico está a años luz del resto de motores.
Los blueprints son el lenguaje de programación visual de alto nivel del UE4. Se puede utilizar también C++ o combinar ambos programando tus propios bloques de Blueprints en C++.

Hay gente que dice que con el UE4 puedes desarrollar un juego sin saber programar, yo digo que no. Desde mi punto de vista con el UE4 puedes desarrollar un juego sin saber C++, pero el lenguaje de Blueprints no deja de ser un lenguaje de programación más. Como se puede ver en la siguiente imagen, no hay mucha diferencia:

Comparación de una función simple en UE4BP y C++.
Comparación de una función simple en UE4BP y C++.

Primeros Pasos

Comencé creando el escenario con el Unreal Engine 4. En este caso es muy simple, un prisma que haría de fondo y una cámara. La cámara tuve que alejarla bastante del fondo, ya que si estaba demasiado cerca, los objetos cercanos a los bordes de la pantalla aparecían distorsionados. Lo que hice fue alejarla mucho y cerrar su campo de visión.

Una vez creado el escenario, decidí crear el objeto partícula, inicialmente con un comportamiento muy simple. La partícula tenía un tipo (de 0 a 5) indicando su color (el 0 sería una especial) y a parte su comportamiento consistía en desplazarse hacia la parte inferior de la pantalla con una determinada velocidad.

La lógica del juego se controlaría desde el GameMode, que almacenaría la matriz con el estado del tablero.
Así mismo, la partícula era capaz de traducir su posición en el espacio tridimensional a filas y columnas de la matriz de juego. El primer problema con el que me encontré es que el UE4 no soporta en sus blueprints arrays bidimensionales, por lo cual tuve que crear varios arrays unidimensionales (uno por cada una de las 6 columnas) y escribir unas funciones que me permitiesen acceder a posiciones concretas de la matriz de forma transparente a su estructura.

Las partículas se añadirán a dicha matriz, en la posición que les corresponda, en el momento en que se detecte una superposición. Es decir, en cada iteración durante la caída del par de partículas, se debe comprobar si alguna de las partículas en caída ocupan la misma posición que alguna de las partículas que se encuentran anteriormente en la matriz (o una posición de fila 0, lo cual las situaría en el “suelo”).

Finalmente doté a las partículas de cuatro tipos de movimientos: moverse hacia la derecha, moverse hacia la izquierda, rotar hacia una posición concreta y aumentar su velocidad de descenso.

Movimientos de las partículas

Los movimientos de las partículas son más complicados de lo que parecía en un principio porque hay que tener una serie de elementos en cuenta. Es decir, no se puede desplazar una partícula a una posición ocupada ni tampoco fuera de los límites de la pantalla.
Como es el GameMode quien tiene la matriz de juego, será él quién se encargue de realizar las comprobaciones previas a los movimientos, y una vez garantizado que el movimiento es válido, ordenar a la partícula en cuestión que lo realice.

Los desplazamientos hacia la izquierda o derecha son sencillos, simplemente se comprueba que la posición izquierda o derecha esté libre y dentro de los límites y se desplaza la partícula hacia ese lugar.

Las rotaciones son un poco más complejas. Como he dicho anteriormente, siempre cae un par de partículas, de las cuales la primera que cae la llamé principal y la segunda la llamé orbital. Al realizar una rotación, siempre la orbital rota en sentido horario o antihorario en torno a la principal, por tanto siempre será la orbital la que reciba las órdenes de rotación.

Para conocer la posición hacia la que tiene que rotar al recibir una orden de rotación horaria o antihoraria, debe conocer su posición relativa a la partícula principal, por ese motivo, también tuve que escribir esa función para las partículas.

Las rotaciones, al igual que los desplazamientos, también deben tener en cuenta que el movimiento sea válido, y además, en caso de que no lo sea, tratar de desplazar las partículas hacia la posición válida más cercana, ya que las rotaciones tienen prioridad.

Uno de los problemas que me encontré con el tema del movimiento es que las partículas simplemente se teletransportaban a la posición que les correspondía, en lugar de desplazarse poco a poco.
Para solucionarlo cambié el sistema de movimiento de tal forma que añadí a las partículas un nuevo atributo indicando la posición a la que se debería mover a continuación, y en su tick de eventos irá recorriendo mediante interpolación el vector desde su posición actual hacia su posición futura. Esto dio lugar al tipo de movimiento suave que buscaba.

Comprobación de enlaces

Bien, llegados a este punto ya tenía un sistema funcional de partículas que caían, se quedaban en su sitio al chocar contra algo por debajo y que se movían y rotaban adecuadamente, pero aún me faltaba la parte más complicada, hacer que se destruyesen al juntarse cuatro del mismo color.

Para dar solución a este problema, debía diseñar un algoritmo que analizase la matriz de estado del tablero y determinase qué partículas estaban enlazadas con qué otras.
Tras barajar diferentes alternativas decidí añadir un nuevo atributo a las partículas llamado “código de enlace”, de forma que todas las partículas que tuviesen el mismo código de enlace implicaría que estaban enlazadas entre sí.

Para asignar este código de enlace, el algoritmo que diseñé fue el siguiente:
Se recorrerá la matriz pasando una vez por cada uno de sus elementos. Si ese elemento no tiene un código de enlace asignado (código 0), se le asignará el siguiente código de enlace disponible y se comprobarán sus alrededores, es decir, las cuatro posiciones adyacentes (arriba, abajo, derecha e izquierda). Si alguna de las partículas en alguna de estas posiciones es del mismo tipo que el elemento actual, se le asignará el mismo código de enlace y se comprobarán sus alrededores (de manera recursiva). Una vez terminada la recursión de comprobación de alrededores, el código de enlace se incrementa en una unidad y se continúa recorriendo la matriz.

Una vez finalizado el algoritmo, tenemos la matriz debidamente enlazada. Tras eso, haciendo uso de un array a modo de histograma contamos el número de partículas que tiene cada código de enlace. Este array tendrá 72 posiciones en el caso peor (6*12), en el caso en que no haya ninguna partícula enlazada con ninguna otra y cada una tenga un código de enlace único.

Finalmente, los índices de este último array cuyo contenido sea mayor o igual a cuatro, nos indican los códigos de enlace de las partículas a destruir.

Recorremos la matriz enviando una orden de explosión a todas las partículas cuyo código de enlace se corresponda con alguno de los códigos indicados por el array anterior.

Bucle principal

Ya íbamos teniendo todas las piezas del puzzle, ahora solo faltaba juntarlas.
El bucle principal del programa funcionaría de la siguiente manera:

En primer lugar se generan las primeras piezas que comienzan a caer. Cuando se produce una colisión, las piezas se fijan en la matriz y se comprueban los enlaces.
En caso de que haya enlaces que requieran una explosión, se añadirán a una lista las piezas que tengan dicho código de enlace y se llevará a cabo la explosión de las mismas. Se eliminarán de la matriz tablero y se volverán a comprobar los enlaces (ya que otras partículas por encima de las explotadas caerán a rellenar sus huecos y podrían generar nuevas explosiones).

En caso de que ya no haya más enlaces que requieran explosión, se generará otro nuevo par de partículas en una nueva iteración del bucle principal.

Detalles

Bien, el juego ya estaba funcionando, pero ahora había que añadirle algunos detalles como animaciones para las explosiones, partículas más vistosas y elementos de jugabilidad como un indicador de la próxima pieza o un sistema de puntuación.

El indicador de la próxima pieza fue sencillo, en lugar de generar los nuevos pares de partículas en la parte superior del mapa, los generé en pequeño en una esquina de la pantalla, y cuando era hora de que entrasen en juego simplemente se escalaban y teletransportaban a la posición de salida, y un nuevo par de fichas se generaban en la esquina indicando las siguientes en caer.

Para el sistema de puntuación decidí otorgar 10 puntos por cada partícula que explotase y a su vez un multiplicador por posibles reacciones en cadena. Es decir, si fusionamos 4 piezas del mismo color obtenemos 40 puntos, pero si tras esa explosión las piezas de arriba caen y quedan colocadas de tal forma que se junten otras cuatro del mismo color, obtendríamos otros 40×2, con lo cual, con un único movimiento habríamos obtenido 120 puntos. El multiplicador aumenta con cada reacción consecutiva x2, x4, x6, x8, x10… (Es posible que cambie el sistema de puntuación en caso de que no me convenza).

Finalmente añadí sonidos indicando estas reacciones en cadena, hasta un multiplicador de x6. (Aunque no limité el multiplicador máximo, no se oirán nuevos sonidos a partir del 6), un texto anunciador y una musiquilla de fondo.

El texto anunciador es otro objeto, que simplemente hace spawn en una posición tridimensional que se le indique, con un texto que se le indique y él mismo comienza a ascender por la pantalla durante dos segundos hasta desintegrarse.

Para la música de fondo decidí hacer un remix utilizando la canción de un antiguo juego de 1986 para Amstrad CPC llamado “Army Moves”. Añadí una serie de distorsiones, ritmos, y un poco de dubstep. Al final quedó bastante animada y pegadiza.

He de decir que los gráficos todavía no me acaban de convencer, pero bueno, dejaré eso para el final.

Próximos pasos

La jugabilidad del modo “supervivencia” se podría decir que ya está lista. La velocidad de caída de las piezas aumenta cada minuto, hasta un máximo de 10 aumentos, y el objetivo sería alcanzar la máxima puntuación posible antes de perder. (Se me olvidó decir que la condición para perder es llenar hasta arriba la columna por la que salen las nuevas partículas).

Ahora quería añadir también piezas de tipo 0 que no reaccionen entre si, sino que haya que realizar una explosión adyacente para que desaparezcan. Estas piezas no caerían en el modo supervivencia, pero serían interesantes para un posible modo de juego por niveles en el que te vayan mandando determinados objetivos.

Según lo tengo pensado, tendría que modificar el algoritmo de detección de enlaces para que asigne un código de enlace 0 a las piezas de tipo 0, y posteriormente hacer que el array histograma de enlaces para ignore los códigos de enlace 0. De esa manera, las partículas 0 no explotarían al juntarse 4 o más, y posteriormente hacer que cada vez que se añada una pieza a la lista de piezas para explotar, propague la explosión a sus piezas adyacentes de tipo 0 de manera única (no vaya a ser que se añada dos veces la misma pieza a la lista y la segunda vez intentemos explotar una pieza que ya no existe).

Para el tema de los diferentes niveles tengo en mente añadir un objeto especial a cada mapa que indique las condiciones de victoria, derrota o elementos de juego especiales propios de ese nivel, de forma que estas se carguen o se comprueben desde el GameMode al cargar dicho mapa. Es solo una idea, tengo que darle una re-pensada a todo esto.

En fin, seguiré trabajando en estas cosillas durante los próximos días y a ver qué sale de aquí.

Written by: