Building a 2D game in Unity3D, have you found yourself in a situation where you want to get all of the painted tiles on a TileMap? Here's what you need to do.
If you’re building a 2D game in Unity3D, odds are you’ve come across the TileMap
component. The TileMap
is a powerful tool that allows you to create a grid of tiles that you can render your tiles with instead of hand-placing individual game objects with sprites. It has a host of built in functionality that you might otherwise find yourself manually writing, like mapping coordinates to particular cells on a map. And what’s even cooler about using a TileMap
? You don’t need to handroll your own editor to paint tiles! I think I’d pass on having to do that. But have you found yourself in a situation where you want to get all of the painted tiles on a TileMap
? You may have found its not quite as obvious as you’d have hoped!
What We Have to Work With on a TileMap
As I mentioned, a TileMap
can do a lot for you. In particular, I found myself wanting to get all of the cells that have a Sprite associated with them and what that Sprite actually is. We can use the built in method GetSprite
for this:
var cellPosition = new Vector3Int(x, y, 0);
var sprite = tilemap.GetSprite(cellPosition);
Simple enough. But how do we know which coordinates to use for x
and y
? Is it good enough to just go from 0
to a really high number on the x
and y
axes with two loops and hope it’s good enough? We can do better than that! The TileMap
class has a handy property on it called cellBounds
. This gives us an integer rectangle that defines the bounds of all of the TileMap
cells that have been modified:
tilemap.cellBounds
If you have found you’re doing a lot of editing on a TileMap
, you may be erasing sections to focus on another area. If that’s common, you may benefit from calling CompressBounds()
to shrink this back down to the currently assigned cells:
tilemap.CompressBounds()
And with all of these, we can approach how we might tie them all together to get what we need!
Be Careful with TileMap.cellBounds and Starting from Zero!
If you have a keen eye, you’ve probably realized that we want to use two loops to go through the cell bounds on the x
and y
axes to get the cells we’re interested in on the TileMap
. You’re spot on! But there’s something we should be careful about here!
It’s an easy mistake to make because it’s how we commonly write loops:
for (int x = 0; x < bounds.max.x; x++)
{
for (int y = 0; y < bounds.max.y; y++)
{
}
}
But what’s wrong with this? The starting value! You need to be careful when working in 2D space like in Unity. There’s nothing that actually guarantees your Tiles have been drawn starting from position zero on the TileMap
and only going in a positive direction. In fact, in my game, I had no tiles on the positive y
axis! So a simple change to make sure you don’t mess up is use the MINIMUM as your starting point:
for (int x = bounds.min.x; x < bounds.max.x; x++)
{
for (int y = bounds.min.y; y < bounds.max.y; y++)
{
}
}
Simple as that!
Wrapping It All Up
You’re probably looking for a full-blown code snippet by now. Fair enough. Here’s what I whipped up:
public static class TileMapExtensionMethods
{
public static IEnumerable GetAllTiles(this Tilemap tilemap)
{
var bounds = tilemap.cellBounds;
for (int x = bounds.min.x; x < bounds.max.x; x++)
{
for (int y = bounds.min.y; y < bounds.max.y; y++)
{
var cellPosition = new Vector3Int(x, y, 0);
var sprite = tilemap.GetSprite(cellPosition);
var tile = tilemap.GetTile(cellPosition);
if (tile == null && sprite == null)
{
continue;
}
var tileData = new TileData(x, y, sprite, tile);
yield return tileData;
}
}
}
}
public sealed class TileData
{
public TileData(
int x,
int y,
Sprite sprite,
TileBase tile)
{
X = x;
Y = y;
Sprite = sprite;
Tile = tile;
}
public int X { get; }
public int Y { get; }
public Sprite Sprite { get; }
public TileBase Tile { get; }
}
The above example provides a nice extension method to get you back a TileData
instance for all the populated cells on your TileMap
!