I have been trying a little bit the new TileMap
system in Unity and I was looking for a way to access the tiles as a List or 2D array but I can't find anything other than GetTile(Vector3Int vector)
which returns only one tile ... Is there any way to do this ?
To get an array with all tiles from a rectangular area of your tilemap, use tilemap.GetTilesBlock(BoundsInt bounds)
. You will get a one-dimensional array of tiles, so you need to know by yourself when the next row of tiles starts. Any empty cells will be represented with a null
value.
If you want all tiles, use tilemap.cellBounds
. This gets you a BoundsInt
object which covers the complete used area of the tilemap. Here is an example script which gets all tiles from the Tilemap on the same game object and lists the tiles with their coordinates:
using UnityEngine;
using UnityEngine.Tilemaps;
public class TileTest : MonoBehaviour {
void Start () {
Tilemap tilemap = GetComponent<Tilemap>();
BoundsInt bounds = tilemap.cellBounds;
TileBase[] allTiles = tilemap.GetTilesBlock(bounds);
for (int x = 0; x < bounds.size.x; x++) {
for (int y = 0; y < bounds.size.y; y++) {
TileBase tile = allTiles[x + y * bounds.size.x];
if (tile != null) {
Debug.Log("x:" + x + " y:" + y + " tile:" + tile.name);
} else {
Debug.Log("x:" + x + " y:" + y + " tile: (null)");
}
}
}
}
}
Regarding the bounds and why you might get more tiles than you expect: Conceptually, Unity Tilemaps have an unlimited size. The cellBounds
grow as needed when you paint tiles, but they don't shrink again if you erase them. So when your game has a well-defined map size, you might get some surprises if you ever slip while editing maps. There are three ways to work around this issue:
- Call
tilemap.CompressBounds()
to restore the bounds to the outmost tiles (hoping you remembered to erase them) - create the
bounds
object yourself withnew BoundsInt(origin, size)
instead of relying oncellBounds
. - set
tilemap.origin
andtilemap.size
to the desired values and then calltilemap.ResizeBounds()
.
Here is another way to do it with .cellBounds.allPositionsWithin
public Tilemap tilemap;
public List<Vector3> tileWorldLocations;
// Use this for initialization
void Start () {
tileWorldLocations = new List<Vector3>();
foreach (var pos in tilemap.cellBounds.allPositionsWithin)
{
Vector3Int localPlace = new Vector3Int(pos.x, pos.y, pos.z);
Vector3 place = tilemap.CellToWorld(localPlace);
if (tilemap.HasTile(localPlace))
{
tileWorldLocations.Add(place);
}
}
print(tileWorldLocations);
}
During figuring out how to get all custom tiles from Tilemap and due to ITilemap
hasn't GetTilesBlock
method which mentioned in answers I suggest to add an extension method like this (2D only):
public static class TilemapExtensions
{
public static T[] GetTiles<T>(this Tilemap tilemap) where T : TileBase
{
List<T> tiles = new List<T>();
for (int y = tilemap.origin.y; y < (tilemap.origin.y + tilemap.size.y); y++)
{
for (int x = tilemap.origin.x; x < (tilemap.origin.x + tilemap.size.x); x++)
{
T tile = tilemap.GetTile<T>(new Vector3Int(x, y, 0));
if (tile != null)
{
tiles.Add(tile);
}
}
}
return tiles.ToArray();
}
}
In this case if you have, suppose, custom tile TileRoad, inherited from Tile or TileBase, then you can get all TileRoad tiles with call:
TileBase[] = tilemap.GetTiles<RoadTile>();
For ITilemap we can change parameter (this Tilemap tilemap)
to (this ITilemap tilemap)
(pay attention that second is Interface) but I didn't check that.
-
\$\begingroup\$ In what situation do you need to deal with the ITilemap interface? I didn't encounter it at all during my experiments with the Tilemap system. I always acquired the Tilemap component with
GetComponent<Tilemap>()
. \$\endgroup\$ – Philipp Dec 1 '17 at 14:21 -
\$\begingroup\$ ~Philipp, can't tell anything about interface, because I worked with Component only too. \$\endgroup\$ – Dmitry Tabakov Dec 19 '17 at 9:33