Articles

Unidad: Cómo crear 2D Tilemap mediante programación

Para el tutorial de vídeo haga clic aquí

Si usted necesita para crear diferentes Tilemaps de diversos tamaños, echa un vistazo a la segunda parte de este tutorial

Tilemap componente fue introducido en la Unidad 2017.2 y facilitó de manera significativa el juego en 2D proceso de desarrollo. Con la versión 2018.3 se introdujo el mapa de bits isométrico que proporciona un gran soporte para juegos 2.5 D. Recientemente tuve la oportunidad de trabajar estrechamente con este componente y tuve el reto de crear mosaicos de forma programática. En mi último juego tengo muchos niveles y todos ellos tienen el mismo tablero basado en mapas de bits. Pero la placa en sí tiene una configuración única de nivel. Obviamente, al ser un desarrollador profesional, no quería crear 60 escenas de juego y pintar todos los niveles a mano, sino tener un mecanismo para llenar el tablero con los elementos adecuados dependiendo de la entrada dada. Si tienes curiosidad por saber cómo se ve el resultado final, aquí está el enlace al juego.

El código fuente está disponible en GitHub, por favor, encuentre el enlace al final de este tutorial.

Utilizo la última versión disponible de Unity 2019.2.0.1 f. Para trabajar con este tutorial, debe tener al menos la versión 2018.3. Utilizaremos cuadrícula isométrica, pero la técnica descrita es aplicable a cualquier tipo.

Antes de comenzar, sugiero leer esta brillante entrada de blog de Entornos Isométricos 2D con Mapas de elementos para obtener una comprensión básica del mapa de elementos isométricos.

Como estamos trabajando en un entorno 2D, debe configurar un nuevo proyecto 2D (consulte 2D y 3dmodesettings)

Hay 3 componentes principales necesarios para lograr nuestro objetivo: un tablero de juego donde se llevará a cabo el juego, un lugar donde guardar una descripción de nivel y algún código que se conecte entre sí.

Parte 1. Creación de la placa

Comencemos importando los activos de imagen necesarios. Usaré las mismas imágenes que usé para mi juego:

Used to create a base tilemap level

Used as a path

Used to mark the start and end points of a path

Mantendremos las imágenes en la carpeta Tiles, simplemente arrástrelas y suéltelas allí.

Baldosas de instalación

tenga en cuenta! Debe tener un tamaño correcto de «Píxel por Unidad» para cada imagen de mosaico (para más detalles, consulte Entornos Isométricos 2D con mapa de mosaico). Para esas imágenes el valor es 1096.

configuración de la Imagen

Es una buena práctica para separar las diferentes capas en una escena dedicados juego de objetos con nombres descriptivos. Imagine la pantalla de juego típica que podría contener una zona de juego, una interfaz de usuario, ubicaciones de anuncios, etc.

Siguiendo este enfoque, vamos a crear un nuevo objeto de juego vacío llamado GameZone donde se pueden colocar el tablero y todos los elementos del juego.

Ahora es el momento de crear teselas reales de las imágenes que hemos importado anterior

Presione la Ventana -> 2D -> Mosaico Paleta

Mosaico Paleta

de Baldosas de la Paleta de vista se abrirá. Haga clic en «Crear nueva paleta» y cree una paleta:

New Palette

Drag and drop tile images one by one to create an actual tiles.

Tiles setup

With those tiles we can finally create the game board which will be filled with elements programmatically on a level startup later.

FindGameZone En la vista de jerarquía, haga clic con el botón derecho del ratón → Objeto 2D → Mapa de elementos isométricos. Se creará un nuevo objeto de juego Grid.

El tamaño del tablero será de 11×11 azulejos y podemos comenzar a pintar. Seleccione la herramienta Pincel de cuadro (4to elemento desde la izquierda) en la vista «Paleta de azulejos» y, a continuación, seleccione el azulejo «limpiar». Píntalo en la vista de escena.

Consejo

Después de que haya terminado con la pintura debe comprimir manualmente tilemap límites. Para esto usted necesita para seleccionar Tilemap en la vista de Jerarquía, pulse en configuración (engranaje) botón en el Tilemap componente y seleccione «Comprimir Tilemap Límites»

Comprimir Tilemap Límites

Bien hecho, el tablero de juego está listo para ser utilizado! Volveremos a ello en la parte 3.

Parte 2. Datos de nivel

Dado que queremos reutilizar la misma escena y el mismo tablero de juego, los datos de nivel deben guardarse en algún lugar. Una solución sencilla para eso es un simple archivo json que describirá cómo se debe construir cada nivel.

Es importante entender lo que estamos tratando de lograr, por eso tengo que decir unas palabras sobre la mecánica. En el juego hay objetos que se mueven a lo largo del camino desde el principio hasta el final (casi como en Zuma) y el objetivo del jugador es destruirlos a todos. En este tutorial crearemos esta ruta que será única para cada nivel.

Bien, de vuelta al proyecto.

Hay varias formas de acceder a datos externos en un tiempo de ejecución desde un script. Aquí usaremos Carpetas de recursos

Vamos a crear una nueva carpeta — Archivos-y una subcarpeta Recursos. Ese es el lugar donde queremos guardar los datos, así que cree un nuevo nivel de archivo.json y colócalo ahí.

Para los fines del tutorial, solo tendremos dos campos para describir cada nivel:

  • number — un int para identificar una ruta de nivel
  • — la ruta basada en mosaicos que queremos crear programáticamente. Matriz de valores donde el primer valor es el punto de inicio y el último valor es el punto final.

Este es el archivo que usaré. No se preocupe por esos valores en path, lo abordaremos más adelante.

{
"levels":
},
{
"number": 2,
"path":
},
{
"number": 3,
"path":
}
]
}

Vamos a crear otra carpeta-Scripts – y ahora la codificación finalmente comienza.

Queremos acceder a los datos del archivo en el código, por lo que necesitamos una clase modelo para ello. Es el momento de crear nuestro primer script — LevelsData. No está destinado a ser creado por Unity, por lo que los métodos MonoBehaviour y StartUpdate deben eliminarse. Desde el archivo anterior podemos ver que el elemento raíz es unarray de niveles donde cada nivel debe tener unint número de campo y unint array ruta de campo. Además, no olvide poner una anotación .


public class LevelsData
{
public LevelData levels;
public class LevelData
{
public int number;
public int path;
}
}

bueno, ahora que tenemos el archivo y el modelo. El siguiente paso es transformar uno en otro. Vamos a crear otro script – GameZone div — – y adjuntarlo al objeto GameZone en la escena. Este script se utilizará más tarde para configurar todo el tablero de juego.

Siga el principio de responsabilidad única vamos a crear otro script – LevelsDataLoader div — – que hará toda la transformación. Adjúntelo al objeto GameZone también.

public class LevelsDataLoader : MonoBehaviour
{
private const string LevelsPath = "Levels";
public Dictionary<int, LevelsData.LevelData> ReadLevelsData()
{
var jsonFile = Resources.Load(LevelsPath, typeof(TextAsset)) as TextAsset;
if (jsonFile == null)
{
throw new ApplicationException("Levels file is not accessible");
}
var loadedData = JsonUtility.FromJson<LevelsData>(jsonFile.text);
return loadedData.levels.ToDictionary(level => level.number, level => level);
}
}

Esta clase cargará los datos y los devolverá como un diccionario donde la clave es el número de nivel y los datos son los datos de nivel en sí.

Ahora deberíamos poder acceder a los datos en el script GameZone.

public class GameZone : MonoBehaviour
{
private Dictionary<int, LevelsData.LevelData> _levelsData;
private LevelsDataLoader _dataLoader;private void Awake()
{
_dataLoader = GetComponent<LevelsDataLoader>();
}private void Start()
{
_levelsData = _dataLoader.ReadLevelsData();Debug.Log(_levelsData.Count + " levels have been stored in the dictionary!");
}
}

Vuelva a Unity, presione el botón de reproducción y verifique la consola; debería ver el mensaje » ¡Se han almacenado 3 niveles en el diccionario!»

Parte 3. Conectando el Tablero y los Datos

Felicitaciones, has llegado a la última y más interesante parte de este tutorial. ¿Cómo conectaremos realmente la placa y los datos? ¡Sigue leyendo para averiguarlo!

En primer lugar, vamos a colocar horizontaly start_stop teselas creadas en la primera parte del tutorial en la carpeta Recursos, debajo de la carpeta Teselas. A continuación, agregue un nuevo script — TilesResourcesLoader — una clase auxiliar estática para cargar mosaicos de esa carpeta en tiempo de ejecución.

public static class TilesResourcesLoader
{
private const string PathHorizontal = "horizontal";
private const string StartStop = "start_stop"; public static Tile GetPathHorizontalTile()
{
return GetTileByName(PathHorizontal);
} public static Tile GetStartStopTile()
{
return GetTileByName(StartStop);
} private static Tile GetTileByName(string name)
{
return (Tile) Resources.Load(name, typeof(Tile));
}
}

Como último paso, debemos colocar esos mosaicos en el tablero en el inicio de la escena. Volvamos al script GameZone. En primer lugar, tenemos que simular la selección de niveles, en el juego real, generalmente sucede cuando un usuario presiona un botón de nivel. En aras de la simplicidad, agreguemos un nivel de campo público a GameZone y cambiemos su valor a 1 para comenzar. Primero te mostraré el guión final:

public class GameZone : MonoBehaviour
{
public int Level; private const int FieldLineSize = 11;
private const int FieldTotalTiles = FieldLineSize * FieldLineSize;
private Dictionary<int, LevelsData.LevelData> _levelsData; private void Start()
{
_levelsData = GetComponent<LevelsDataLoader>().ReadLevelsData();
SetupTiles();
} private void SetupTiles()
{
var baseLevel = GetComponentsInChildren<Tilemap>(); var localTilesPositions = new List<Vector3Int>(FieldTotalTiles);
foreach (var pos in baseLevel.cellBounds.allPositionsWithin)
{
Vector3Int localPlace = new Vector3Int(pos.x, pos.y, pos.z);
localTilesPositions.Add(localPlace);
} SetupPath(localTilesPositions, baseLevel);
} private void SetupPath(List<Vector3Int> localTilesPositions, Tilemap baseLevel)
{
var path = _levelsData.path;
var pathHorizontalTile = TilesResourcesLoader.GetPathHorizontalTile();
var first = path.First();
var last = path.Last();
foreach (var localPosition in localTilesPositions.GetRange(first, Math.Abs(first - last)))
{
baseLevel.SetTile(localPosition, pathHorizontalTile);
} var startStopTile = TilesResourcesLoader.GetStartStopTile();
baseLevel.SetTile(localTilesPositions, startStopTile);
baseLevel.SetTile(localTilesPositions, startStopTile);
}
}

¡Vaya, eso es mucha acción! Déjame explicártelo.

En el método SetupTiles primero debemos obtener el mapa de mosaico en sí porque necesitamos conocer las posiciones de los mosaicos para cambiarlo. Para lograr esto, estamos utilizando el método tilemap.cellBounds.allPositionsWithin que devuelve todas las posiciones de los mosaicos a partir de la primera, en la configuración dada es un mosaico más abajo.

Consulte la siguiente imagen, donde cada número representa el índice en el localTilesPositions lista.

Numeradas tilemap

¿Te acuerdas de los valores que utilizamos en la ruta de acceso en el Levels.json? Como ya habrás adivinado, esos valores son los índices de los mosaicos de la matriz. Todo lo que necesitas hacer ahora es tener una imagen de cheatsheet para ayudarte a construir niveles. Ese es el que he estado usando durante el desarrollo:

Mi feo numeradas tilemap

En este ejemplo, estamos estableciendo una línea horizontal en la ruta de acceso, por favor refiérase a la etiqueta SetupPath método. La parte clave es el siguiente bucle:

foreach (var localPosition in localTilesPositions.GetRange(first, Math.Abs(first - last)))
{
baseLevel.SetTile(localPosition, pathHorizontalTile);
}

Aquí podemos iterar sobre localTilesPositions para encontrar los para fijar el azulejo — horizontal en este caso.

¡Nota! GetRange el método tiene dos parámetros: índice y recuento.

Para marcar las posiciones de inicio y fin de la ruta, se utiliza el mosaico start_stop.

Aquí está el resultado de nuestro trabajo:

resultado Final

Ahora pruebe a cambiar el nivel del campo de número de la etiqueta GameZone script de 1 a 2 o 3 y verás que el camino está cargado correctamente para el nivel dado.

Después