Es muy habitual recurrir a Tilemaps para dar forma a juegos 2D. La sencillez que ofrecen los hacen ideales a la hora de darle forma a un escenario de estética retro.
Sin embargo, a primera vista, toda la implementación que hace Unity de los Tilemap parece limitarse a su aspecto estético. Por eso, es bastante frecuente encontrarse a gente en los foros preguntando cómo se puede asociar datos a los diferentes Tiles utilizados en un Tilemap.
¿Para que podemos necesitar asociarle datos a un tile? Por múltiples razones. Por ejemplo, supongamos que estemos usando un Tilemap para conformar el mapa de un juego en perspectiva cenital. En ese caso, es probable que queramos añadirle un valor de "resistencia al avance" a los diferentes tiles, de manera que nuestro personaje se mueva más lento en los tiles que representen una ciénaga, más rápido en los tiles que representen un camino, y que no pueda travesar los tiles que muestren piedras infranqueables.
Para nuestros ejemplos, vamos a suponer un escenario como el de la captura:
Escenario de nuestros ejemplos |
Representa un recinto cerrado que incluye tres obstáculos en su interior, uno a la izquierda (con una sola baldosa), otro en el centro (con cuatro baldosas) y otro a la derecha (con tres). El origen de coordenadas del escenario está en su centro; lo he señalado con la cruceta de un GameObject vacío.
La problemática que queremos resolver en nuestro ejemplo es cómo hacer para que un script pueda analizar el escenario, identificar las losetas negras y tomar nota de sus posiciones.
Como en muchos otros casos, no hay una solución única a esta problemática. Tenemos una opción rápida de implementar y que ofrece más posibilidades, pero que puede suponer una sobrecarga excesiva para el juego. Por otro lado, tenemos otra opción que es más ardua de implementar y está más limitada, pero que sobrecargará menos el juego. Vamos a analizar ambas.
Asociar un GameObject a un tile
Generalmente, cuando queremos identificar de una tacada los GameObject que pertenecen a una misma categoría, lo más sencillo sería marcarlos con una etiqueta y buscarlos por el escenario con el método estático GameObject.FindGameObjectsWithTag(). El problema es que los tiles son ScriptableObjects, así que no pueden marcarse con etiquetas.
Los ScriptableObjects de los tiles se crean cuando arrastramos sprites sobre la pestaña del Tile Palette. En ese momento, el editor nos deja elegir el nombre y la ubicación del asset con el ScriptableObject que queramos crear, asociado al tile. A partir de ese momento, si pinchamos en el asset de ese ScriptableObject podremos editar sus parámetros a través del inspector. Por ejemplo, para el tile que he utilizado para los muros perimetrales, los parámetros son:
![]() |
Configuración de un Tile |
- Sprite: Es el sprite con la apariencia visual del tile. Una vez fijado el sprite podemos pulsar el botón "Sprite Editor" de debajo para configurar tanto el pivot point como el collider asociado al sprite.
- Color: Permite colorear el sprite con el color que fijemos aquí. El color neutro es el blanco; si lo usamos, Unity entenderá que no queremos forzar el color del sprite.
- Collider Type: Define si queremos asociar un Collider al tile. Si elegimos "None" significará que no queremos que el Tile tenga un Collider asociado; si fijamos "Sprite", el collider será el que hayamos definido a través del Sprite Editor; por último si el valor elegido es "Grid", el colider tendrá la forma de las celdas del Tilemap.
- GameObject to Instantiate: Es el parámetro que nos interesa. Lo explicaremos en un momento.
- Flags: Sirven para modificar cómo se comporta un tile cuando se coloca en un Tilemap. Para nuestra explicación, basta con que lo dejes en su valor por defecto.
![]() |
Configuración del Tile de los obstáculos |
![]() |
ObstacleTileData con la etiqueta InnerObstacle |
![]() |
Código para localizar los tiles que hayamos marcado con la etiqueta InnerObstacle |
Basta que coloquemos el script anterior en cualquier GameObject situado junto al Tilemap del escenario. Yo, por ejemplo, lo tengo colgando del mismo transform que el componente Grid de los Tilemaps de mi escenario.
Cuando arranque el nivel, el Tilemap creará una instancia del prefab ObstacleTileData en cada una de las posiciones del escenario donde aparezca una loseta negra de obstáculo. Dado que el prefab ObstacleTileData no tiene componente visual, sus instancias serán invisibles para el jugador, pero no para nuestros scripts. Como esas instancias están marcadas con el tag "InnerObstacle" nuestro script puede localizarlas llamando a FindGameObjectsWithTag(), en la línea 16 del código.
Como demostración de que el código localiza correctamente las ubicaciones de los tiles de obstáculos, he fijado un punto de interrupción en la línea 17, para que podamos analizar el contenido de la variable "obstacles" tras llamar a FindGameObjectsWithTag(). Al ejecutar el juego en modo debug, el contenido de esa variable es el siguiente:
![]() |
Posiciones de los tiles de obstáculos |
De todos modos, tirar de etiquetas no es la única manera de localizar las instancias de los GameObject asociados a cada Tile. Los objetos Tilemap ofrecen el método GetInstantiatedObject(), al que se le pasa una posición dentro del Tilemap y, a cambio, devuelve el GameObject instanciado para el tile de esa pasición. Usar este método es menos directo que localizar los objetos por etiqueta, ya que te obliga a ir examinando una a una las posiciones del Tilemap, pero habrá situaciones en las que no te quede otro remedio.
Por último, antes de abandonar esta sección del artículo, es necesario que seas consciente de que puede haber situaciones en las que instanciar un GameObject por tile puede pesar sobre el rendimiento del juego. En el caso del ejemplo se trata unos pocos tiles, pero en escenarios mucho más grandes puede que hablemos de cientos de tiles, por lo que instanciar cientos de GameObject puede ser algo que debamos pensar dos veces.
Extender la clase Tile
Por defecto, yo usaría la estrategia anterior; pero puede ser que haya situaciones en las que no te convenga instanciar un número elevado de GameObjects. En ese caso, puede convenirte el enfoque que voy a explicar ahora.
Los Tiles son una clase que hereda de ScriptableObject. Podemos extender la clase Tile para añadir los parámetros que queramos. Por ejemplo, podríamos crear un Tile especializado con un booleano para definir si el tile es un obstáculo o no.
![]() |
Tile con un parámetro especializado |
Ese tile se puede instanciar como cualquier ScriptableObject para crear un asset. Cuando lo hagamos veremos que aparecerá el parámetro especializado y lo podremos configurar a través del inspector.
![]() |
Configuración del tile con el parámetro especializado |
La clave está en que los assets que creemos así se pueden arrastrar hacia el Tile Palette para poder ser dibujados en el escenario.
Hecho eso, podríamos utilizar el método Tilemap.GetTile() para ir recuperando los tiles de cada una de la posición, hacerles cast a nuestro tipo de tile personalizado (en nuestro caso CourtyardTile) y, acto seguido, analizar el valor del parámetro personalizado.
La pega de este método es que no podemos usar ni etiquetas ni capas para buscar los datos asociados a los tiles, lo que nos condena a recorrer los tilemap celda a celda para encontrarlos, pero tiene la ventaja de librar a nuestro juego de la carga de crear un GameObject por tile.
Conclusión
Ya sea creando un GameObject por tile o extendiendo la clase Tile, ya tienes los recursos necesarios para asociar datos a cada uno de los tiles. Esto te permitirá dotar a los tiles de una semántica imprescindible para multitud de algoritmos, como por ejemplo los de pathfinding.