Juego 'Sokoban' isométrico y hexagonal basado en azulejos de Unity 2D

Lo que vas a crear

En este tutorial, convertiremos un juego de Sokoban basado en mosaico 2D convencional en vistas isométricas y hexagonales. Si eres nuevo en juegos isométricos o hexagonales, puede ser abrumador al principio intentar seguirlos a través de ambos al mismo tiempo. En ese caso, recomiendo elegir isométrica primero y luego regresar en una etapa posterior para la versión hexagonal.

Estaremos construyendo sobre el tutorial anterior de Unity: Unity 2D Tile-Based Sokoban Game. Siga el tutorial primero, ya que la mayoría del código permanece sin cambios y todos los conceptos básicos siguen siendo los mismos. También enlazaré con otros tutoriales que explican algunos de los conceptos subyacentes..

El aspecto más importante en la creación de versiones isométricas o hexagonales a partir de una versión 2D es determinar el posicionamiento de los elementos. Utilizaremos métodos de conversión basados ​​en ecuaciones para convertir entre los distintos sistemas de coordenadas..

Este tutorial tiene dos secciones, una para la versión isométrica y otra para la versión hexagonal..

1. Juego isométrico de Sokoban

Vayamos a la versión isométrica una vez que haya completado el tutorial original. La imagen a continuación muestra cómo se vería la versión isométrica, siempre que usemos la misma información de nivel utilizada en el tutorial original..

Vista isométrica

La teoría isométrica, la ecuación de conversión y la implementación se explican en varios tutoriales en Envato Tuts +. Una explicación antigua basada en Flash se puede encontrar en este tutorial detallado. Recomendaría este tutorial basado en Phaser ya que es una prueba más reciente y futura.

Aunque los lenguajes de script utilizados en esos tutoriales son ActionScript 3 y JavaScript respectivamente, la teoría es aplicable en todas partes, independientemente de los lenguajes de programación. Esencialmente se reduce a estas ecuaciones de conversión que se utilizan para convertir las coordenadas cartesianas 2D en coordenadas isométricas o viceversa.

// Cartesiano a isométrico: isoX = cartX - cartY; isoY = (cartX + cartY) / 2; // Isométrico a cartesiano: cartX = (2 * isoY + isoX) / 2; cartY = (2 * isoY - isoX) / 2;

Utilizaremos la siguiente función Unity para la conversión a coordenadas isométricas.

Vector2 CartesianToIsometric (Vector2 cartPt) Vector2 tempPt = new Vector2 (); tempPt.x = cartPt.x-cartPt.y; tempPt.y = (cartPt.x + cartPt.y) / 2; retorno (tempPt); 

Cambios en el arte

Usaremos la misma información de nivel para crear nuestra matriz 2D, levelData, Lo que impulsará la representación isométrica. La mayor parte del código también seguirá siendo el mismo, aparte del específico de la vista isométrica..

El arte, sin embargo, necesita tener algunos cambios con respecto a los puntos de pivote. Por favor, consulte la imagen de abajo y la explicación que sigue..

los IsometricSokoban juego script utiliza sprites modificados como heroSprite, pelotaprecio, y blockSprite. La imagen muestra los nuevos puntos de pivote utilizados para estos sprites. Este cambio da el aspecto pseudo 3D que buscamos con la vista isométrica. El blockSprite es un nuevo sprite que agregamos cuando encontramos un invalidTile.

Me ayudará a explicar el aspecto más importante de los juegos isométricos, la clasificación en profundidad. Aunque el sprite es solo un hexágono, lo estamos considerando como un cubo 3D donde el pivote está situado en el centro de la cara inferior del cubo..

Cambios en el Código

Descargue el código compartido a través del repositorio de git vinculado antes de continuar. los CrearNivel El método tiene algunos cambios que se relacionan con la escala y el posicionamiento de las baldosas y la adición de blockTile. La escala del tilesSprite, que es solo una imagen en forma de diamante que representa nuestra baldosa de tierra, debe modificarse como se muestra a continuación.

tile.transform.localScale = new Vector2 (tileSize-1, (tileSize-1) / 2); // el tamaño es crítico para la forma isométrica

Esto refleja el hecho de que una baldosa isométrica tendrá una altura de la mitad de su ancho. los heroSprite y el pelotaprecio tener un tamaño de tileSize / 2.

hero.transform.localScale = Vector2.one * (tileSize / 2); // usamos la mitad del tamaño de tiles para los ocupantes

Dondequiera que encontremos un invalidTile, añadimos un blockTile usando el siguiente código.

tile = new GameObject ("block" + i.ToString () + "_" + j.ToString ()); // create new tile float rootThree = Mathf.Sqrt (3); float newDimension = 2 * tileSize / rootThree; tile.transform.localScale = new Vector2 (newDimension, tileSize); // necesitamos establecer cierta altura sr = tile.AddComponent(); // agregue un renderizador de sprite sr.sprite = blockSprite; // asigne el sprite de bloque sr.sortingOrder = 1; // esto también debe tener un orden de clasificación más alto Color c = Color.gray; c.a = 0.9f; sr.color = c; tile.transform.position = GetScreenPointFromLevelIndices (i, j); // colocar en escena según los índices de nivel ocupantes.Add (tile, new Vector2 (i, j)); // almacenar los índices de nivel de bloque en dict

El hexágono debe escalarse de manera diferente para obtener el aspecto isométrico. Esto no será un problema cuando el arte sea manejado por artistas. Estamos aplicando un valor alfa ligeramente inferior a la blockSprite para que podamos ver a través de él, lo que nos permite ver la clasificación de profundidad correctamente. Tenga en cuenta que estamos agregando estos azulejos a la ocupantes diccionario también, que se utilizará más adelante para la clasificación en profundidad.

El posicionamiento de las baldosas se realiza utilizando el GetScreenPointFromLevelIndices método, que a su vez utiliza el CartesianToIsometric Método de conversión explicado anteriormente. los Y el eje apunta en la dirección opuesta a Unity, que debe considerarse al agregar el middleOffset Posicionar el nivel en el centro de la pantalla..

Vector2 GetScreenPointFromLevelIndices (int row, int col) // convirtiendo los índices en valores de posición, col determina x & row determina y Vector2 tempPt = CartesianToIsometric (nuevo Vector2 (col * tileSize / 2, row * tileSize / 2)); eliminado el '-' en la parte y como la corrección del eje puede suceder después de coverion tempPt.x- = middleOffset.x; // aplicamos el desplazamiento fuera de la conversión de coordenadas para alinear el nivel en tempPt.y * = - medio de pantalla. unidad y corrección de eje tempPt.y + = middleOffset.y; // aplicamos el desplazamiento fuera de la conversión de coordenadas para alinear el nivel en la pantalla media return tempPt; 

Al final de CrearNivel método, así como al final de la TryMoveHero método, llamamos al DepthSort método. La clasificación en profundidad es el aspecto más importante de una implementación isométrica. Esencialmente, determinamos qué azulejos van detrás o delante de otros azulejos en el nivel. los DepthSort el método es como se muestra a continuación.

vacío privado DepthSort () int depth = 1; SpriteRenderer sr; Vector2 pos = nuevo Vector2 (); para (int i = 0; i < rows; i++)  for (int j = 0; j < cols; j++)  int val=levelData[i,j]; if(val!=groundTile && val!=destinationTile)//a tile which needs depth sorting pos.x=i; pos.y=j; GameObject occupant=GetOccupantAtPosition(pos);//find the occupant at this position if(occupant==null)Debug.Log("no occupant"); sr=occupant.GetComponent(); sr.sortingOrder = depth; // asignar nueva profundidad ++; // incrementar profundidad

La belleza de una implementación basada en matrices 2D es que para la clasificación isométrica adecuada de la profundidad, solo tenemos que asignar una profundidad secuencialmente mayor mientras analizamos el nivel en orden, utilizando la secuencia para los bucles. Esto funciona para nuestro nivel simple con una sola capa de terreno. Si tuviéramos varios niveles de terreno en varias alturas, entonces la clasificación en profundidad podría complicarse.

Todo lo demás sigue siendo el mismo que la implementación 2D explicada en el tutorial anterior.

Nivel completado

Puedes usar los mismos controles del teclado para jugar el juego. La única diferencia es que el héroe no se moverá vertical u horizontalmente sino isométricamente. El nivel final se vería como la imagen de abajo..

Echa un vistazo a cómo la clasificación en profundidad es claramente visible con nuestro nuevo bloques de bloques.

Eso no fue difícil, ¿verdad? Lo invito a cambiar los datos de nivel en el archivo de texto para probar nuevos niveles. La siguiente es la versión hexagonal, que es un poco más complicada, y te aconsejaría que tomes un descanso para jugar con la versión isométrica antes de continuar..

2. Juego de Sokoban Hexagonal

La versión hexagonal del nivel de Sokoban se vería como la imagen de abajo.

Vista hexagonal

Estamos utilizando la alineación horizontal para la cuadrícula hexagonal para este tutorial. La teoría detrás de la implementación hexagonal requiere mucha lectura adicional. Por favor, consulte esta serie de tutoriales para una comprensión básica. La teoría se implementa en la clase auxiliar. HexHelperHorizontal, que se puede encontrar en el utiles carpeta.

Conversión de coordenadas hexagonales

los HexagonalSokoban El script del juego usa métodos de conveniencia de la clase auxiliar para las conversiones de coordenadas y otras características hexagonales. La clase de ayuda HexHelperHorizontal Solo funcionará con una rejilla hexagonal alineada horizontalmente. Incluye métodos para convertir coordenadas entre sistemas offset, axial y cúbico..

La coordenada de desplazamiento es la misma coordenada cartesiana 2D. También incluye un getNeighbors Método, que toma en una coordenada axial y devuelve un Lista con todos los seis vecinos de esa celda coordinados. El orden de la lista es en el sentido de las agujas del reloj, comenzando con la coordenada de celda del vecino noreste.

Cambios en los controles

Con una cuadrícula hexagonal, tenemos seis direcciones de movimiento en lugar de cuatro, ya que el hexágono tiene seis lados, mientras que un cuadrado tiene cuatro. Así que tenemos seis teclas del teclado para controlar el movimiento de nuestro héroe, como se muestra en la imagen de abajo..

Las teclas están dispuestas en el mismo diseño que una cuadrícula hexagonal si considera la tecla del teclado S como la celda central, con todas las teclas de control como sus vecinos hexagonales. Ayuda a reducir la confusión con el control del movimiento. Los cambios correspondientes al código de entrada son los siguientes.

private void ApplyUserInput () // tenemos 6 direcciones de movimiento controladas por e, d, x, z, a, w en una secuencia cíclica que comienza con NE a NW si (Input.GetKeyUp (userInputKeys [0])) TryMoveHero (0); // noreste else if (Input.GetKeyUp (userInputKeys [1])) TryMoveHero (1); // east else if (Input.GetKeyUp (userInputKeys [2])) TryMoveHero (2) ; // sureste else if (Input.GetKeyUp (userInputKeys [3])) TryMoveHero (3); // south west else if (Input.GetKeyUp (userInputKeys [4])) TryMoveHero (4); / / west else if (Input.GetKeyUp (userInputKeys [5])) TryMoveHero (5); // noroeste

No hay cambio en el arte, y no hay cambios de pivote necesarios.

Otros cambios en el código

Explicaré los cambios en el código con respecto al tutorial 2D original de Sokoban y no a la versión isométrica anterior. Por favor, consulte el código fuente vinculado para este tutorial. El hecho más interesante es que casi todo el código sigue siendo el mismo. los CrearNivel método tiene un solo cambio, que es el middleOffset cálculo.

middleOffset.x = cols * tileWidth + tileWidth * 0.5f; // esto se cambia por middleOffset hexagonal.y = rows * tileSize * 3/4 ​​+ tileSize * 0.75f; // se cambia por isométrico 

Un cambio importante es obviamente la forma en que se encuentran las coordenadas de la pantalla en el GetScreenPointFromLevelIndices método.

Vector2 GetScreenPointFromLevelIndices (int row, int col) // convirtiendo los índices en valores de posición, col determina x & row determina y Vector2 tempPt = new Vector2 (row, col); tempPt = HexHelperHorizontal.offsetToAxial (tempPt); // convertir de offset a axial // convertir el punto axial en punto de pantalla tempPt = HexHelperHorizontal.axialToScreen (tempPt, sideLength); tempPt.x- = middleOffset.x-Screen.width / 2; // agregar desplazamientos para la alineación media tempPt.y * = - 1; // tempPt.y de corrección de unidad y eje.y + = middleOffset.y-Screen.height / 2; tempPt de retorno; 

Aquí usamos la clase auxiliar para primero convertir la coordenada a axial y luego encontrar la coordenada de pantalla correspondiente. Tenga en cuenta el uso de la largo de lado Variable para la segunda conversión. Es el valor de la longitud de un lado de la baldosa hexagonal, que nuevamente es igual a la mitad de la distancia entre los dos extremos puntiagudos del hexágono. Por lo tanto:

sideLength = tileSize * 0.5f;

El único otro cambio es el GetNextPositionAlong método, que es utilizado por el TryMoveHero Método para encontrar la siguiente celda en una dirección dada. Este método se cambia completamente para adaptarse al diseño completamente nuevo de nuestra cuadrícula.

private Vector2 GetNextPositionAlong (Vector2 objPos, int direction) // este método se modifica por completo para adaptarse a las diferentes formas en que se encuentran los vecinos en la lógica hexagonal objPos = HexHelperHorizontal.offsetToAxial (objPos); vecinos = HexHelperHorizontal.getNeighbors (objPos); objPos = vecinos [dirección]; // la lista de vecinos sigue la misma secuencia de orden objPos = HexHelperHorizontal.axialToOffset (objPos); // reconvertir de axial a offset de retorno objPos; 

Usando la clase auxiliar, podemos devolver fácilmente las coordenadas del vecino en la dirección dada.

Todo lo demás permanece igual que la implementación 2D original. Eso no fue difícil, ¿verdad? Dicho esto, no es fácil comprender cómo llegamos a las ecuaciones de conversión siguiendo el tutorial hexagonal, que es el quid de todo el proceso. Si juegas y completas el nivel, obtendrás el resultado como se muestra a continuación.

Conclusión

El elemento principal en ambas conversiones fue la conversión de coordenadas. La versión isométrica implica cambios adicionales en la técnica con su punto de pivote, así como la necesidad de clasificación en profundidad..

Creo que ha encontrado lo fácil que es crear juegos basados ​​en cuadrícula utilizando solo datos de nivel de matriz bidimensional y un enfoque basado en mosaico. Hay posibilidades y juegos ilimitados que puedes crear con esta nueva comprensión.

Si ha comprendido todos los conceptos que hemos discutido hasta ahora, lo invitaría a cambiar el método de control para tocar y agregar un poco de búsqueda de ruta. Buena suerte.