Advertisement
  1. Game Development
  2. Game Development
Gamedevelopment

Creando un Buscaminas Hexagonal

by
Difficulty:BeginnerLength:LongLanguages:

Spanish (Español) translation by Ricardo Mayén (you can also view the original English article)

Final product image
What You'll Be Creating

En este tutorial, trataré de presentarte el interesante mundo de juegos basados en baldosas hexagonales usando el más fácil de los métodos. Aprenderás a cómo convertir una matriz de datos bi-dimensionales a un diseño hexagonal respectivo en la pantalla y viceversa. Usando la información obtenida, crearemos un juego de buscaminas hexagonal en dos diseños hexagonales diferentes.

Esto te iniciará explorando juegos de mesa hexagonales simples y juegos de rompecabezas y será un buen punto de arranque para aprender métodos más complicados como el sistema de coordenadas axial o cúbico hexagonal.

1. Baldosas Hexagonales y Diseños

En la actual generación de videojuegos casuales, no vemos muchos juegos que usen un método basado en baldosas hexagonales. Aquellos que encontramos son usualmente juegos de rompecabezas, juegos de mesa, o juegos de estrategia. También la mayoria de nuestros requerimientos los cumplen el método de la red cuadrada o el método isométrico. Esto nos lleva a la pregunta natural: ¿Por qué necesitamos un método diferente y obviamente complicado? Averiguémoslo.

Ventajas del Método Hexagonal

Entonces, ¿qué hace al método basados en baldosas hexagonales relevantes, puesto que ya tenemos otros métodos aprendidos y perfeccionados? Permíteme enumerar algunas de las razones.

  • Número más pequeño de baldosas adyacentes: Cuando se compara con una red cuadrada, que tendrá ocho baldosas adyacentes, una baldosa hexagonal tendrá solamente seis adyacentes. Esto reduce computaciones para algoritmos complicados.
  • Todas las baldosas adyacentes están a la misma distancia. Para una red cuadrada, los cuatro vecinos diagonales están lejos cuando se comparan con los vecinos horizontales o verticales. El estar las baldosas vecinas equidistantes es un gran alivio cuando calculamos las heurísticas y reduce la sobrecarga de usar dos métodos diferentes para calcular algo dependiendo del vecino.
  • Singularidad: Hoy en día, millones de juegos casuales están emergiendo y compitiendo por el tiempo de los jugadores. Los grandes juegos están fallando en obtener una audiencia, y una cosa que puede ser garantizada para atraer la atención de un jugador es la singularidad. Un juego que usa un método hexagonal resaltará visualmente del resto, y el juego parecerá más interesante a un público que está aburrido con toda la mecánica de los juegos convencionales.

Diría que la última razón debería ser suficiente para que domines este nuevo método.Agregar ese elemento de juego único en tu lógica de juego podría hacer toda la diferencia y hacer posible que hagas un juego genial.

Las otras razones son puramente técnicas y solamente entrarían en efecto una vez que estés lidiando con algoritmos complicados o sets de baldosas más grandes. También hay muchos otros aspectos que pueden enumerarse como ventajas del método hexagonal, pero la mayoría dependerá del interés personal del jugador.

Diseños 

Un hexágono es un polígono con seis lados, y un hexágono cuyos lados tienen la misma longitud se llama un hexágono regular. Por propósitos de teoría, consideraremos que nuestras baldosas hexagonales son hexágonos regulares, pero ellos podrían ser aplastados o alargados en la práctica.

Lo interesante es que un hexágono se puede colocar en dos maneras diferentes: las esquinas puntiagudas podrían ser alineadas vertical u horizontalmente. Cuando las partes puntiagudas superiores se alinean verticalmente, se le llama un diseño horizontal y cuando se alinean horizontalmente se le llama un diseño vertical . Puedes pensar que los nombres son poco apropiados según la explicación dada. Este no es el caso ya que los nombres no se basan según las esquinas puntiagudas sino en la manera en que una cuadrícula de baldosas se disponen.

The vertical and horizontal hexagonal tile grid layout

La elección del diseño depende enteramente de los visuales y la jugabilidad de tu juego. Pero tu elección no termina aquí ya que cada uno de estos diseños pueden ser implementados en dos maneras diferentes. 

Consideremos un diseño de cuadrícula hexagonal horizontal. Las filas alternativas de la cuadrícula necesitarían ser compensadas por hexTileWidth/2 Esto significa que podríamos elegir compensar ya sea las filas impares o las pares. Si también mostramos los valores correspondientes de fila columna, estas variantes se verían como la imagen abajo.

Horizontal hexagonal layout with even odd offsets

Similarmente, el diseño vertical puede ser implementado en dos variaciones mientras que se compensan las columnas alternativas por hexTileHeight/2 como se muestra abajo.

Vertical hexagonal layout showing even odd variations

Implementando Diseños Hexagonales 

De aquí en adelante, por favor comienza a consultar el código fuente que se provee junto con este tutorial para entender mejor.

Las imágenes arriba, con las filas y columnas mostradas, facilitan visualizar una correlación directa con una gama bi-dimensional que guarda los datos del nivel. Digamos que tenemos una gama bi-dimensional levelData como se muestra abajo.

Para facilitar la visualización, te mostraré el resultado esperado aquí tanto en las variaciones verticales como las horizontales.

simple hexagonal grid based on level data array

Comencemos con el diseño horizontal, el cual es la imagen al lado izquierdo. En cada fila, si se toma individualmente, las baldosas vecinas se compensan horizontalmente por hexTileWidth. Las filas alternativas se compensan horizontalmente por un valor de hexTileWidth/2. La diferencia de altura vertical entre cada fila es hexTileHeight*3/4.

Para entender cómo llegamos a tal valor para la compensación de la altura, necesitamos considerar el hecho que las porciones triangulares superiores e inferiores de un hexágono diseñado horizontalmente son exactamente hexTileHeight/4.

Esto significa que el hexágono tiene una porción rectangular hexTileHeight/2 en el medio, una porción triangular hexTileHeight/4 en la parte superior, y una porción triangular invertida hexTileHeight/4 en la parte inferior. Esta información es suficiente para crear el código necesario para diseñar la cuadricula hexagonal en la pantalla.

Con el prototipo HexTile, he agregado algunas funcionalidades adicionales al prototipo Phaser.Sprite lo que le permite mostrar los valores i y j. El código esencialmente coloco una nueva baldosa Sprite hexagonal en startXstartY. Este código puede cambiarse para mostrar la variante de compensación par simplemente al remover un operador en la condición if de esta manera: if(i%2===0)

Para un diseño vertical (la imagen a la mitad derecha), las baldosas adyacentes en cada columna son compensadas verticalmente por hexTileHeight. Cada columna alterna se compensa verticalmente por hexTileHeight/2.  Aplicando la lógica que aplicamos para la compensación vertical en el diseño horizontal, podemos ver que la compensación horizontal para el diseño vertical entre baldosas vecinas en una fila es hexTileWidth*3/4. El código correspondiente está abajo.

De la misma manera que con el diseño horizontal, podemos cambiar a la variante de compensación par simplemente al remover el operador ! en la condición superior if. Estoy usando un Group Phaser para recolectar todos los hexTiles llamados hexGrid. Por simplicidad, estoy usando el punto central de la imagen de la baldosa hexagonal como ancla, de lo contrario necesitaríamos considerar las compensaciones de imagen también.

Una cosa a la cual se le debe prestar atención es que los valores de ancho y alto de baldosa en el diseño horizontal no son iguales a los valores de ancho y alto de baldosa en el diseño vertical. Pero cuando se usa la misma imagen para ambos diseños, podemos simplemente rotar la imagen de la baldosa 90 grados y cambiar los valores de ancho y alto de la baldosa.

3. Encontrando el Índice de Matriz de una Baldosa Hexagonal

La matriz para la ubicación lógica en pantalla fue interesantemente directa, pero lo opuesto no es tan fácil. Considera que necesitamos encontrar el índice de matriz de la baldosa hexagonal en la cual hemos pulsado. El código para hacer eso no es bonito, y usualmente se llega a él por medio de ensayo y error.

Si consideramos el diseño horizontal, puede parecer que la porción regular media de la baldosa hexagonal puede facilmente ayudarnos a descifrar el valor j ya que sólo es una cuestión de dividir el valor x por hexTileWidth y tomar el valor integrado. Pero a menos que conozcamos el valor i, no sabemos si estamos en una fila impar o par. Un valor aproximado de i se puede encontrar el valor y por hexTileHeight*3/4.

Ahora vienen las partes complicadas de la baldosa hexagonal: las porciones triangulares superiores e inferiores. La imagen abajo nos ayudará a entender el problema en cuestión.

a horizontally laid hexagonal tile split into regions

Las regiones 2, 3, 5, 6, 8, y 9 juntas forman una baldosa. la parte más complicada es encontrar si la posición seleccionada esta en 1/2 o 3/4 pr 7/8 o 9/10. Para esto, necesitamos considerar todas las regiones triangulares individuales y cotejarlas usando la inclinación de la orilla sesgada.

Esta inclinación se puede encontrar por la altura y el ancho de cada región triangular, que respectivamente son hexTileHeight/4 y hexTileWidth/2. Déjame mostrarte la función que hace esto.

Primero, encontramos xVal y yVal en la misma manera que haríamos para la cuadrícula cuadrada. Luego encontramos los valores faltantes horizontales (dX) y verticales (dY) después de remover la compensación del multiplicador de baldosa. Usando estos valores, tratamos de averiguar si el punto está dentro de cualquiera de las regiones triangulares complicadas.

Si se encuentra, hacemos los cambios correspondientes a los valores iniciales de xVal y yVal. Como dije antes, el código no es bonito y no es directo. La manera más fácil de entender esto sería llamar al findHexTile con un movimiento del ratón, y luego colocar console.log dentro de cada una de esas condiciones y mover el ratón sobre varias regiones dentro de una baldosa hexagonal. De esta manera, puedes ver cómo se maneja cada región intra-hexagonal.

Los cambios de código para el diseño vertical se muestran abajo.

4. Encontrando vecinos

Ahora que hemos encontrado la baldosa en la cual hemos seleccionado, encontremos todas las seis baldosas adyacentes. Este es un problema muy fácil de resolver una vez que analizamos la cuadrícula visualmente. Consideremos el diseño horizontal.

odd and even rows of horizontal layout when a middle tile has ij set to 0

La imagen arriba muestra las filas pares e impares de una cuadrícula hexagonal orientada horizontalmente cuando una baldosa del medio tiene el valor de 0 tanto para i como para j. De la imagen, se vuelve claro que si la fila es impar, entonces para una baldosa en i, j los vecinos son i, j-1, i-1,j-1, i-1,j, i,j+1, i+1,j, y i+1,j-1. CUando la fila es par, entonces para una baldosa en i,j los vecino son i, j-1, i-1,j, i-1,j+1, i,j+1, i+1,j+1, y i+1,j. Esto se puede calcular manualmente con facilidad.

Analicemos una imagen similar para las columnas pares e impares de una cuadrícula hexagonal alineada verticalmente.

odd and even columns of vertical layout when a middle tile has ij set to 0

Cuando tenemos una columna impar, una baldosa en i, j tendrá a i, j-1, i-1,j-1, i-1,j, i-1,j+1, i,j+1, y i+1,j como vecinos. Similarmente, para una columna par, los vecinos son i+1,j-1, i,j-1, i-1,j, i,j+1, i+1,j+1, and i+1,j.

5. Buscaminas Hexagonal

Con el conocimiento de arriba, podemos intentar hacer un juego de buscaminas hexagonal en los dos diferentes diseños. Descompongamos las características de un juego de buscaminas.

  1. Habrá un número N de minas ocultas dentro de la cuadrícula.
  2. Sí tocamos una baldosa con una mina, el juego se acaba.
  3. Si tocamos una baldosa que tiene una mina adyacente, esta mostrará el numero de minas immediatamente alrededor de ella.
  4. Si tocamos una mina sin ninguna mina adyacente, esta nos revelaría todas las baldosas conectadas que no tienen minas.
  5. Podemos pulsar y mantener presionado para marcar una baldosa como mina.
  6. El juego termina cuando revelamos todas las baldosas sin minas.

Fácilmente podemos guardar un valor en la matriz levelData para indicar una mina. el mismo método se puede usar para poblar el valor de minas cercanas en el índice de matriz en las baldosas adyacentes.

Al iniciar el juego, poblaremos aleatoriamente la matriz levelData con un número N de minas. Después de esto, actualizaremos los valores para todas las baldosas adyacentes. Usaremos un método recursivo para revelar en cadena todas las baldosas vacías cuando el jugador toque una baldosa que no tenga una mina adyacente.

Datos de Nivel

Necesitamos crear una cuadrícula atractiva, como se muestra en la imagen abajo.

horizontal hexagonal minesweeper grid

Esto se puede hacer al simplemente mostrar una porción de la matriz levelData. Si usamos -1 como el valor para una baldosa no utilizable  y 0 como el valor para una valdosa utilizable, entonces nuestro levelData para alcanzar el resultado de arriba lucirá así.

Mientras se hacen bucles a través de la matriz, sólo agregaríamos baldosas hexagonales cuando el levelData tenga un valor de 0. Para la alineación vertical, la misma levelData se puede usar, pero necesitaríamos trasladar la matriz. Aquí hay un método ingenioso que puede hacer esto para ti.

Agregando Minas y Actualizando Adyacentes

Por defecto, nuestro levelData sólo tiene dos valores -1 y 0, de los cuales sólo usaríamos el área con 0. Para indicar que una baldosa contiene una mina, podemos usar el valor de 10.

Una baldosa hexagonal en blanco puede tener un máximo de seis minas cerca puesto que tiene seis baldosas adyacentes. Podemos guardar esta información en el levelData cuando hemos agregado todas las minas. Esencialmente un índice de levelData que tenga un valor de 10 tiene una mina, y si contiene cualquier valor entre 0 y 6, eso indica el número de minas adyacentes. Después de poblar minas y actualizar adyacentes, si un elemento de la matriz sigue siendo 0, indica que es una baldosa en blanco sin minas adyacentes.

Para nuestros propósitos podemos usar los siguientes métodos.

Por cada mina agregada en addMines, incrementaremos el valor de matriz guardado en todas sus adyacentes. el método getNeighbors no restituirá una baldosa que esté fuera de nuestra área efectiva o si contiene una mina.

Lógica de Toque

Cuando el jugador toca una baldosa, necesitamos encontrar el elemento de matriz correspondiente usando el método findHexTile explicado anteriormente. Si el índice de la baldosa está dentro de nuestra área efectiva, entonces sólo comparamos el valor en el índice de matriz para averiguar si es una mina o una baldosa vacía.

Damos seguimiento del número total de baldosas vacías usando la variable blankTiles y el número de baldosas reveladas usando revealedTiles. Una vez que sean iguales, hemos ganado el juego.

Cuando tocamos una baldosa con un valor de matriz de 0, necesitamos de revelar la región recursivamente con todas las baldosas vacías. Esto se hace con la función recursiveReveal, la cual recibe los índices de la baldosa de la baldosa tocada.

En esta función, encontramos las vecinas de cada baldosa y revelamos el valor de la baldosa, mientras tanto se agregan baldosas adyacentes a una matriz. Seguimos repitiendo esto con el próximo elemento en la matriz hasta que la matriz se encuentra vacía. La recursión se detiene cuando alcanzamos elementos de matriz que contienen una mina, lo cual se garantiza por el hecho que getNeighbors no restituirá una baldosa con una mina.

Marcando y Revelando Baldosas

Te debes haber dado cuenta que estoy usando hexTile.reveal() lo cual se hace posible al crear un prototipo hexTile que mantiene la mayoría de los atributos relacionados a nuestra baldosa hexagonal. Uso la función reveal para mostrar el texto de valor de la baldosa y establece el color de la baldosa. Similarmente, la función toggleMark se usa para marcar la baldosa como una mina cuando la tocamos y mantenemos presionada. HexTile también tiene un atributo revealed el cual le da seguimiento ya sea que se toque y se revele o no.

Revisa el buscaminas hexagonal con orientación horizontal abajo. Toca para revelar baldosas y toca y mantén presionado para marcar minas. Hasta ahora el juego no ha terminado, pero si revelas un valor de 10, entonces es hasta la vista baby!

Cambios para la Versión Vertical

Como estoy usando la misma imagen de baldosa hexagonal para ambas orientaciones, yo roto el Sprite para la alineación vertical. el código abajo en el prototipo HexTiles hace esto.

La lógica del buscaminas se mantiene la misma para la cuadrícula hexagonal verticalmente alineada con la diferencia para lógica findHextile y getNeighbors que ahora necesitan acomodar la diferencia de alineamiento. Como se mencionó antes, también necesitamos usar el traslado de la matriz del nivel con el diseño de bucle.

Revisa la versión vertical abajo.

El resto del código en la fuente es simple y directa. Me gustaría que intentes agregar la funcionalidad de reinicio, juego ganado, y fin de juego que faltan.

Conclusión

Este método de un juego basado en baldosas hexagonal usando una matriz bi-dimensional tiene un enfoque sencillo. Métodos más interesantes y funcionales involucran alterar el sistema de coordenadas a diferentes tipos usando ecuaciones.

Los más importantes son las coordinadas axiales y cúbicas. Habrá una serie de tutoriales de seguimiento que discutirá estos métodos. Mientras tanto, te recomendaría que leas el increiblemente exhaustivo artículo de Amit sobre cuadrículas hexagonales.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.