Michael Christensen-Calvin /
October 2023
(4488 Words,
25 Minutes)
unity3dgamedev
Working with the Unity Tilemap
Working with Tiles in the Unity system can be a little frustrating. Unity is not a 2D engine, no matter what they try to tell new developers. Going into “2D” mode in the scene view is essentially just flattening the camera, it does nothing to your gameplay. The only 2D thing they really did was implement a seperate physics system for 2D objects, but everything still lives in 3D whenspace you do that, they just ignore the z-value. I am pretty sure you could get the same behaviour if you just locked the z-axis on all your 3D rigid bodies. Rant aside, this can get a little confusing when you are trying to work in 2d. When working with Tilemaps, they create a “Grid” that your tiles can live on for the Tilemap object, and this is a 2D space that lives on a 3D gameObject. When adjusting layering it can be tempting to adjust the position of the object, rather than setting the “Order in Layer” attribute on the Tilemap Renderer, and when you put in a character that probably doesn’t live on the tilemap, you need to figure out how to have them sort properly in the layer itself.
Solving the concept of “prefabs”
Often when working in a multi-layered tilemap scene, you need to create complex, repeatable objects that live across multiple layers. The Tile Pallette lets you select multiple tiles to place at once, and you can edit the tile palette as a tilemap, so you could just paint copies of your desired tiles into another part of the palette and just select and paint those together if you wanted just a simple shape on a single layer.
However, this doesn’t work if your object lives on multiple layers. For example what if you want a carpet beneath your table, and that carpet lives on the floor tile layer. You could select the floor layer, draw your saved carpet, then select the collidable layer and draw your table if you are doing everything manually, but this is still more steps than should be neccessary if that table appears on top of that carpet in many places. Then we get to my actualy use case: programatically setting tiles.
I wanted to create objects that could be placed programatically in the scene via script, and those objects would be dynamic. Maybe I put together a couple chairs and a table in a specific manner ,and I wanted to have multiple possible layouts of those tables around my bar. Essentially, I wanted a prefab instance of my object that lived in the Tilemap, and as far as I can tell Unity doesn’t provide this funcitonality so I would have to build it myself.
Defining the probem.
I can break down my problem into three different needs:
I need a data structure that holds all the relevant data
I need a way to edit/save these “prefabs”
I need a way to easily input these “prefabs” into my scene at runtime.
Data Structures
My new Data strucutre needs to store the following information:
The Tile that should be placed.
The location of where to place this tile test.
The Tilemap layer on which to place this tile.
After using just the above for a while, I found that I actually needed a bit more info:
The color tint of the tile.
The transform matrix of the tile. (Controls offset/rotation/scale).
The Tile type we can store as a Tile or TileBase, which works fine as is.
The Location is a Vector3Int, again easy to store.
The Color is simple Color type.
The transform matrix in unity is a Matrix4x4 type which is easy to store,
That just leaves us with the Tilemap layer. This is going to have to depend on your implmentation of layers, and have some specfic code written to get the layer you want. I am storing it as a int, and for me it is just the numerical representation of its order in the hierarchy, you could also use a string if you wanted. You just need to later have a way to turn this into an actual Tilemap object in your scene.
This gives us a small class that holds the neccessary information to recreate a tile:
Now we need something to store a collection of these TileInfos, and the standard Unity way to do this is with a ScriptableObject:
This stores the TileInfo objects, a unique Guid (not stricly neccessary, but can be useful when trying to save/load this during serialization for a larger object.) and a Texture2D preview (which we’ll get into later).
Editing/Saving Prefabs
Okay so we have the data structure for storing the data we need, but how do we actually create these? There were a couple options I thought of:
Edit these prefabs in the main scene where you are making your game.
This would have the lowest barrier to entry prefab wise, as you wouldn’t need to open up a different context in order to edit the prefab. However, the only way I could think of seperating out just the tiles you want would be to painstaking select them and then hit a button to collect them all.
Edit the prefabs in a seperate scene devoted to prefab creation/editing. While this is less convenient in terms of flow, it does allow you to always have control over how the scene will be setup when you are editing these prefabs, allowing you to make assumptions about the layout.
I chose to create a seperate scene for this, one that I create on the fly so that I can control it all in code and link it to my Tilemap manager class.
Setting the scene
So lets first create an EditorWindow script to help us manage this process. First, we want to create the class itself, and have it setup our scene:
This is a basic script that will get us into a state where we have a scene setup for editing tiles, with all of our desired Tilemap layers setup.
In this case. I am using an enum in MyTilemap.cs called Layer, and using that to recreate all of the layers that I have in my Tilemap manager class. This class Sits on the parent Grid.cs object to my Tilemaps, and handles grabbing and returning the Tilemap objects I ask for:
Saving the prefab
Now we have a scene that we can easily swap to when we want to edit our prefabs, we can make our prefab in the new scene. Once its setup, we need to be able tosave it, so lets add some functions.
First we want to add the ability create a new TilePrefabObject.
Now lets add the ability to select one from our current Assets:
And finally lets setup a function to clone the existing object, making it a bit easier to create different versions of something:
Finally, lets add a function that saves the prefab in the scene to our current _objectToEdit.
The ForEachTile function normally lives in a static helper class I wrote and published here
And huzzah! Now we have a scene and editor window that lets us save/edit our tile prefabs into ScriptableObjects.
Load Prefabs
Okay so now that we have these ScriptableObjects that store our tile prefab data, we need to somehow integrate it into our workflow.
Creating our Tile
I created a new kind of Tile that I called a PrefabTile that inhereits from TileBase.
Now we can just right client in whatever folder we want our tile to be saved in, and create that new Tile asset. Then we just assign the TilePrefabObject we want into the PrefabObject field, and select a sprite to represent it.
Populating our prefab tiles.
We now will need to get our prefab actually populating in the scene, and to do that we are going to override the StartUp function.
This looks a bit complicated, but that was because of some issues I was running into with timing and things populated before everything else in the scene was setup and ready. I then discovered a different bug, and I just haven’t found the time to test removing my delay code to see if it works without it or not.
The core of this Startup is this trimmed down block here:
We go through each of the TileInfo instances stored in our tile and :
1) get the associate tilemap from our index with MyTilemap.GetTilemap(int index)
2) Get a TileChangeData object from the TileInfo
3) Use this TileChangeData to set the tile on the desired tilemap.
4) Remove the prefab tile from the map.
The TileChangeData isn’t something we talked about before, but its functionally just a struct contianing the position, color, transform and Tile (basically all the information our TileInfo stores). I just put an easy getter into the TileInfo class to get this:
And there we have it! We now have TilePrefabs. Stay tuned for an update where I add some extra things like generating a preview of prefab when we save it, as well as creating a simple texture with some text on it for us to use as our Tile sprite so we know whats going in. Also, using gizmos so that we know the shape that the prefab tiles will expand out to so we aren’t working completely blind.