Tilemaps are very often used to create 2D games. Their simplicity makes them ideal for creating a retro-style setting.
However, at first glance, Unity's entire implementation of Tilemaps seems to be limited to its aesthetic aspect. That's why it's quite common to find people on the forums asking how to associate data with the different Tiles used in a Tilemap.
Why might we need to associate data with a tile? For a variety of reasons. For example, let's say we're using a Tilemap to map a top-down game. In that case, we'll probably want to add a "drag" value to the different tiles, so that our character moves slower on tiles that represent a swamp, faster on tiles that represent a path, and can't cross tiles that show impassable stones.
For our examples, let's assume a scenario like the one in the capture:
![]() |
Scenario of our examples |
It represents an enclosed area that includes three obstacles inside, one on the left (with a single tile), one in the center (with four tiles) and one on the right (with three). The origin of the stage's coordinates is at its center; I have indicated it with the crosshair of an empty GameObject.
The problem we want to solve in our example is how to make a script analyze the scenario, identify the black tiles and take note of their positions.
As with many other cases, there is no single solution to this problem. We have an option that is quick to implement and offers more possibilities, but can put too much overhead on the game. On the other hand, we have another option that is more difficult to implement and is more limited, but will put less overhead on the game. Let's analyze both.
Associate a GameObject to a tile
Generally, when we want to identify at once the GameObjects that belong to the same category, the easiest way would be to mark them with a tag and search for them in the scenario with the static method GameObject.FindGameObjectsWithTag() . The problem is that tiles are ScriptableObjects, so they cannot be marked with tags.
ScriptableObjects for tiles are created when we drag sprites onto the Tile Palette tab. At that point, the editor lets us choose the name and location of the asset with the ScriptableObject we want to create, associated with the tile. From that point on, if we click on the asset of that ScriptableObject we can edit its parameters through the inspector. For example, for the tile I used for the perimeter walls, the parameters are:
![]() |
Setting up a Tile |
The fields that can be configured are:
- Sprite: This is the sprite with the visual appearance of the tile. Once the sprite is set, we can press the "Sprite Editor" button below to configure both the pivot point and the collider associated with the sprite.
- Color: Allows you to color the sprite with the color you set here. The neutral color is white; if you use it, Unity will understand that you do not want to force the sprite's color.
- Collider Type: Defines whether we want to associate a Collider to the tile. If we choose "None" it will mean that we do not want the Tile to have an associated Collider; if we set "Sprite", the collider will be the one we have defined through the Sprite Editor; finally, if the chosen value is "Grid", the collider will have the shape of the Tilemap cells.
- GameObject to Instantiate: This is the parameter we are interested in. We will explain this in a moment.
- Flags: These are used to modify how a tile behaves when placed on a Tilemap. For our purposes, you can simply leave it at its default value.
As I was saying, the parameter that interests us for our purpose is "GameObject to instantiate" if we drag a prefab to this field, the Tilemap will be in charge of creating an instance of that prefab in each location where that Tile appears.
For example, to be able to easily locate the black tiles, those of the obstacles, I have associated a prefab to that parameter of their Tile that I have called ObstacleTileData.
![]() |
Setting up the Obstacle Tile |
Since all I want is to be able to associate a tag with the tiles, in order to locate them with FindGameObjectsWithTag() , it was enough for me to make ObstacleTileData a simple transform with the tag I was interested in. In the screenshot you can see that I used the InnerObstacle tag .
![]() |
ObstacleTileData with label InnerObstacle |
Once this is done, and once the tiles we want to locate are deployed on the stage, we only need the following code to make an inventory of the tiles with the InnerObstacle tag .
![]() |
Code to locate the tiles that we have marked with the InnerObstacle tag |
We just need to place the above script on any GameObject located next to the stage's Tilemap. For example, I have it hanging from the same transform as the Grid component of the Stage's Tilemaps.
When the level starts, the Tilemap will create an instance of the ObstacleTileData prefab at each position on the stage where a black obstacle tile appears. Since the ObstacleTileData prefab has no visual component, its instances will be invisible to the player, but not to our scripts. Since these instances are marked with the "InnerObstacle" tag, our script can locate them by calling FindGameObjectsWithTag() , on line 16 of the code.
To demonstrate that the code correctly locates the obstacle tile locations, I've set a breakpoint on line 17, so that we can analyze the contents of the "obstacles" variable after calling FindGameObjectsWithTag() . When running the game in debug mode, the contents of that variable are as follows:
![]() |
Obstacle tile positions |
If we compare the positions of the GameObjects with those of the tiles, we can see that obstacles[7] is the obstacle on the left, with a single tile. The GameObjects obstacle[2], [3], [5] and [6] correspond to the four tiles of the central obstacle. The three remaining GameObjects ([0], [1] and [4]) are the tiles of the obstacle on the right, the elbow-shaped one.
In this way, we have achieved a quick and easy inventory of all the tiles of a certain type.
However, pulling labels isn't the only way to locate instances of the GameObjects associated with each Tile. Tilemap objects offer the GetInstantiatedObject() method , which is passed a position within the Tilemap and in return returns the instantiated GameObject for that tile's tile. Using this method is less direct than locating objects by label, since it forces you to examine the Tilemap positions one by one, but there will be situations where you have no other choice.
Finally, before we leave this section of the article, you should be aware that there may be situations where instantiating a GameObject per tile can weigh down the performance of the game. In the example case, we are talking about a few tiles, but in much larger scenarios we may be talking about hundreds of tiles, so instantiating hundreds of GameObjects may be something to think twice about.
Extending the Tile class
By default, I would use the above strategy; but there may be situations where you don't want to instantiate a large number of GameObjects. In that case, you may want to use the approach I'm going to explain now.
Tiles are a class that inherits from ScriptableObject. We can extend the Tile class to add any parameters we want. For example, we could create a specialized Tile with a boolean to define whether the tile is an obstacle or not.
![]() |
Tile with a specialized parameter |
This tile can be instantiated like any ScriptableObject to create an asset. When we do this, we will see that the specialized parameter will appear and we can configure it through the inspector.
![]() |
Setting the tile with the specialized parameter |
The key is that the assets we create this way can be dragged to the Tile Palette so they can be drawn on the stage.
Once that is done, we could use the Tilemap.GetTile() method to retrieve the tiles for each position, cast them to our custom tile type (in our case CourtyardTile) and then analyze the value of the custom parameter.
The drawback of this method is that we cannot use labels or layers to search for data associated with tiles, which forces us to go through the tilemap cell by cell to find them, but it has the advantage of freeing our game from the burden of creating a GameObject per tile.
Conclusion
Whether by creating a GameObject per tile or by extending the Tile class, you now have the resources necessary to associate data with each of the tiles. This will allow you to provide the tiles with essential semantics for a multitude of algorithms, such as pathfinding algorithms.