Sometimes you need to perform an action with all the tiles in a tilemap. For example, to save all the tile data information in a serialized class to save and load a tilemap from disk.
You can do this by using the helper methods IterateTilemapWithAction in TilemapUtils.
[System.Serializable]
public class TilemapSerializedData
{
[System.Serializable]
public class TileData
{
public int gridX;
public int gridY;
public uint tileData;
}
public List tileDataList = new List();
}
public static TilemapSerializedData SerializeTilemap(STETilemap tilemap)
{
TilemapSerializedData data = new TilemapSerializedData();
System.Action action = (tmap, gridX, gridY, tileData) =>
{
data.tileDataList.Add( new TilemapSerializedData.TileData() { gridX = gridX, gridY = gridY, tileData = tileData } );
};
TilemapUtils.IterateTilemapWithAction(tilemap, action);
return data;
}
This is going to be the first part of a tutorial to create a simple game using Super Tilemap Editor (STME). This tutorial will be focused on using the tools provided by STME more than just how to make a simple game using Unity, so think in this game tutorial like an excuse to comment some of the features included in STME and how to use them.
So let’s start with a brief summary about the content for the tutorial part 1:
To make it more fun, we are going to make a game based on Packman and Oh Mummy and to make it more original it will take some ideas from the movie Temple of Doom.
1. Game Introduction
The Plot
You are an explorer looking for adventures and you hear about a Temple of Doom in the middle of the Amazonas with wonderful treasures and a mystic idol made of gold that you want to include to your collection or weird artifacts. But you has been told the Temple is full of traps and a mummy is protecting the idol from undesirable thieves.
The game
You start the game in the entrance of the temple. Your objective is to find the Golden Idol and leave the temple alive. Optionally you can find also some treasures to finance future expeditions and items that will help you in this adventure.
Items
Golden Idol: once you get the idol, the mummy will came for you and the broken floor tiles will fall, so you better run to the exit.
Bag of Sand: if you have the bag of sand before taking the idol, it will give you some time before the events after stolen the idol start.
Golden Coins: give you 10, 20 and 30 golden coins each you collect, to pay for future expeditions (in a future DLC, for this tutorial consider them as your score).
Hammer: allows you to break broken walls until the hammer is broken.
Map: shows you the path to the Golden Idol and to the exit.
Torch: Illuminates the path and scares away the mummy.
Actors
Explorer: The adventurer protagonist of this game controlled by the player.
Mummy: The guardian of the Temple, will kill anyone who dares to steal their precious Golden Idol. He’ll try to possess you. So get away from him.
Credits
Thanks to oryxdesignlab for allowing me to use some of their graphics for this tutorial. All the graphics (except the Golden Idol), are part of the Oryx Sprite Packages:
They are intended to be used as examples for this tutorial and not for reuse.
I have used Aseprite to make modifications to the graphics and crate some of them, like the Golden Idol. It’s a really powerful tool for pixel art and very easy to use.
2. Creating a Tileset from an atlas texture
I guess you already created a new project and downloaded the last version of Super Tilemap Editor. Next thing you need to do is download the resources used for this tutorial:
Then import the resources into your project, in my case it was in the folder “Assets\_Game_\Tilesets”.
Right clic over the Atlas Image, then go to Create->SuperTilemapEditor->Tileset to create a tileset and assign the selected texture to the tileset. You can also create a tileset first and later select the atlas texture, but this way is faster.
I have added some space in the texture for more tiles in the future.
TIP: If you later need a bigger texture, you can extend it vertically (increasing the height) and press Slice button to add the new tiles making the previous tilemaps compatible with this new atlas texture. But it won’t be compatible if you change the width because the previous tile ids will change.
3. Creating some tileset brushes
As you can see, the Brush Palette is empty. A brush is an asset with special code to change the tile according to some rules. For example, it could select a tile depending on the neighbor tiles or change the tile after a while to animate it, or just select a random tile to reduce repetition.
We are now going to create a Road Brush. This brush will change the tile according to the neighbor tiles on top, right, left and bottom position. We are going to use this brush for the walls.
Create a Road BrushAssign the tileset. A brush can only be attached to a tileset.
You can see, creating a Wall brush was easy because the tile layout was the same as the Road brush layout. But what if the layout is different?
In this case, you need to select the tiles one by one for the first brush, but after that, you can create a new brush duplicating a previous brush (ex: selecting the brush and pressing Ctrl+D) then change only one of the tiles, and press Autocomplete. The rest of tiles will be changed taking the modified tile as reference. You can also change the RoadBrushEditor code to fit your custom layout.
Now lets create a Random Brush for the floor. We can use the two different versions of the floor tile for that.
You can also add tiles manually pressing ‘+’ to duplicate the selected tile or create a new one and then change a tile by pressing over the tile preview and then over the new tile in the Tile Palette.
Also, you can change the probability of the tile to appear.
Finally, we can import the new brushes in the tileset by pressing the Import Button:
Now that we have created a tileset and some brushes we can start creating our first level.
4. Creating a tilemap group and a simple level using the tileset and the brushes
A tilemap group is basically a parent object that manages a group of tilemaps. Its the same that an image made of different layers. In this case the image would be the tilemap group and the layers would be the tilemaps.
Creating a Tilemap Group is easy
Now we can add tilemaps to this tilemap group directly pressing the ‘+’ button in the tilemap list. We are going to create a tilemap first, then assign the tileset to the tilemap and after that, we are going to add 2 more tilemaps. This way, the new tilemaps will copy the tileset from the first one and we can skip that step.
After that, we are going to rename the tilemaps to Walls, Actors and Floor and we will change the sorting order so the walls are rendered over the Actors and the Actors over the Floor.
Drawing the level is easy. You just need to select the tilemap you want to paint to (pressing ‘+’ and ‘-‘ keys will cycle through the tilemap group tilemaps). Then select the Paint tab and select a tile to start paining. You can use the paint tools to draw basic forms.
TIP 1: You can also right click a tile to copy that tile or make a selection of tiles as well. If you only copy an empty tile you can erase the tiles (painting an empty tile).
TIP 2: If you forgot to change to the right tilemap while painting, you can use the Highlight Alpha property to change the alpha of unselected tilemaps.
5. Adding colliders to the wall tiles
To add colliders to the walls we need first to setup the collider data for the wall tiles. For this we need to open the Tile Property Window (right click on a tile in the tile palette or going to the menu->SuperTilemapEditor->Window->Tile Properties Window).
Then we need to select the wall tiles and go to the Collider tab and press Full (because the collider will cover the entire tile). To make it faster you can select more than a tile to make the modifications at once.
The tile collider properties only configure the shape of the collider, not the type of collider. A full collider is used for optimization, but its behaviour is the same as a Polygon collider. The only difference is, a polygon collider allow you to change the number of vertices and its position to create different shapes.
Once we have configured the collisions of the wall tiles, we need to set the collider type of the wall tilemap. This will make the tilemap to create a collider of the type we set. It can be a 2D collider using PolygonCollider2D or EdgeCollider2D or a 3D collider using a MeshCollider.
Configuring the Tilemap Walls to generate 2D edge colliders
TIP: You can configure the tile colliders also directly in the scene view while the Collider Tab is selected. Check the info box in the bottom left corner to see how.
6. Make the broken wall tiles autotile with the Wall Brush using autotiling groups
If you have tried to paint the broken wall in the middle of a wall, you will have noticed that the wall is not autotiling with this tile, like if it was a virus or a strange agent to avoid.
This is happening because, by default, the brush autotiles only with itself. The brushes check the neighbors and decide the tile based on the autotiling check with them.
Wall brush doesn’t like the broken tiles
In order to make the Wall Brush to consider another tile a friend tile to autotile with, you have different options:
Changing the autotiling mode
Brush Autotiling Properties (right click the tile to open this window)
By default, a brush will autotile only with a brush tile of the same type, here you can change the autotiling mode and combine them.
Autotiling Modes:
Everything: include all the other modes
Self: autotiles only with brush tiles of the same type
Group: autotiles with tiles or brushes of the same group (this will be explained next)
Empty Cells: the cell were no tile is painted is treated as an empty cell
Tilemap Bounds: the tiles that would be outside of the tilemap bounds even if there are not tiles at all
So a way to make the broken tiles to autotile with the Wall Brush would be making the Wall Brush autotile with Other tiles.
After changing the Autotiling Mode you need to refresh the tilemap to see the changes.
Using autotiling mode Group for the Wall tiles
There is other way to make the broken tiles autotile with the Wall Brush. If we group the broken tiles and the Wall Brush in a group you can make only the broken tiles and not any other tile to autotile with the Wall.
For example, if we want to paint a door tile in the middle of a wall and we don’t want the door to autotile with the wall tiles:
We don’t have a Door tile so I have used a Map instead
First we need to add the Wall group. For that we need to select the Tileset (in the project view or dobleclick on the tileset property in the Tilemap). Then select the BrushGroups tab and add the Wall name to the Brush Group 1.
This is working in a similar way the Unity Layers work. If the name of a group is not empty you will be able to select it in the Group drop menu.
Now we can select the Wall Brush and change the Autotiling Mode to (Self + Group) and then change the Group from Default to Wall. And do the last step with the broken wall tiles as well.
For the broken wall you can make a selection of both tiles to modify them at once
Now, Refresh the Wall tilemap to see the changes. The Wall is not autotiling with the broken wall but not with the Map tile.
With this ends the first part of this tutorial.
You can Download the example project used as example here:
When you modifying an STETilemap by script, you will use the methods SetTileData and GetTileData. They have a parameter called tileData that is an unsigned integer (uint).
This unsigned integer is a raw data representation of a tilemap cell.
This data is divided in different attributes, each of one using different bits of the unsigned integer of 32bits:
TileId [16bits, from 0 to 15]: this is the id of the tile used to draw this cell.
BrushId [12bits, from 16 to 27]: this is used to update the tileId when the tilemap mesh is updated
Flags [4bits, 28, 29, 30 and 31]:
Updated flag [bit 28]: used to inform the brush and other subsystem when the cell has been updated. For example, in a random brush, it will avoid changing the tileId again after this flag is set the first time.
Rotate 90º [bit 29]: the cell tile is rotated 90º
Vertical Flip [bit 30]: the cell tile is flipped vertically
Horizontal Flip [bit 31]: the cell tile is flipped horizontally
You can set or get tile data using the methods in STETilemap component: SetTileData, SetTile, GetTileData and GetTile.
The difference between Tile and TileData is, TileData will use a raw tile data stored in an unsigned int containing the tile flags (flip horizontal, flip vertical, rotate 90º), the brush Id and the tile Id. You can see more details in the tutorial Understanding TileData.
GetTile will return the Tile instance stored in the tileset used by the tilemap.
So, in order to call any of the previous methods, you need first to get an STETilemap reference.
Getting the STETilemap reference to work with
// Gets the gameObject with the name "tilemapName"
GameObject tilemapObj = GameObject.Find("tilemapName");
// Gets the STETilemap component from a gameObject
STETilemap tilemap = gameObject.GetComponent<STETilemap>();
Setting a tile at some position in the tilemap
The tilemaps are boundless and will increase the boundaries when necessary.
SetTile and SetTileData will set a tile at some position. There are two versions of this method:
One is using a Vector2 position. This will set a tile at the cell position under the local position relative to the tilemap.
You can use this, for example, to change a tile under the player position:
//playerTransform will be a reference to the player transform
tilemap.SetTileData(playerTransform.position, 45); //sets the tile with id 45 in the cell under the player position
//Use this if the tilemap transform has been changed its position, rotation or scale
tilemap.SetTileData( tilemap.transform.InverseTransformPoint(playerTransform.position), 45);
The other version is using two integers gridX and gridY to set or get the tile at an specific cell. GridX is also the column and GridY the row.
After modifying a tilemap, you need to call UpdateMesh (to mark it to be updated during the next update) or UpdateMeshImmediate, to update it immediately.
Here are some examples setting tiles:
//Set a tile at grid position (20, -5) with a tileId (-1) empty, with a brushId 5.
tilemap.SetTile(20, -5, -1, 5);
//The same using SetTileData, the brushId should be shifted 16bits to the left, because the
//first 16 bits are used to store the tileId. In this case, tileId will be 0, but doesn't matter
//because the brush will change the tile after updating the tilemap
tilemap.SetTileData(20, -5, (5 << 16));
//Set a tile at position (2f, 5f), not the grid position (2, 5), locally to the tilemap and apply the flags
tilemap.SetTile(new Vector2(2f, 5f), 8, -1, eTileFlags.FlipH | eTileFlags.FlipV);
//And using SetTileData...
tilemap.SetTileData(new Vector2(2f, 5f), (8<<16) | (uint)(eTileFlags.FlipH | eTileFlags.FlipV)<<28);
// Erase the tile at grid position (-4, 9), the tileData will be -1 or 0xFFFFFFFF
tilemap.Erase(-4, 9);
//Always remember to call this to update the tilemap mesh and see the changes
tilemap.UpdateMesh();
Getting a tile at some position in the tilemap
Now you know how to set a tile, get a tile works almost the same but returning the tile data at some (local or grid) position.
In this case, SetTile will return directly a reference to the Tile instance in the Tileset used by the tilemap. To take the brush you need to use GetTileData and the use the method tileset.FindBrush(brushId).
Let see some examples:
//Get the tileData at grid position -4, -8
uint tileData = tilemap.GetTileData(-4, -8);
// Get the tileId, the brushId and the flags from tileData
int tileId = Tileset.GetTileIdFromTileData(tileData);
int brushId = Tileset.GetBrushIdFromTileData(tileData);
bool flipHorizontal = (tileData & Tileset.k_TileFlag_FlipH) != 0;
bool flipVertical = (tileData & Tileset.k_TileFlag_FlipV) != 0;
bool rot90 = (tileData & Tileset.k_TileFlag_Rot90) != 0;
eTileFlags tileFlags = (eTileFlags)(Tileset.GetTileFlagsFromTileData(tileData) << 28);
// Get the object Tile or TilesetBrush
Tile tile = tilemap.GetTile(-4, -8); // directly from the tilemap
tile = tilemap.Tileset.GetTile(tileId); // from the tileset with the tileId
TilesetBrush brush = tilemap.Tileset.FindBrush(brushId);
Some uses of getting the Tile object would be to check if it has colliders (for path finding for example) or to get a custom parameter like typeOfMaterial or isWall.
// will be true if the tile is not null and has any collider set on it
bool hasCollider = tile != null && tile.collData.type != eTileCollider.None;
// will be -1 if the tile is null, or it will get the value in the parameter typeOfMaterial or -1 if the parameter is not set
int typeOfMaterial = tile == null ? -1 : tile.paramContainer.GetIntParam("typeOfMaterial", -1);
// will be false if the tile is null, or it will get the value in the parameter isWall or false if the parameter is not set
bool isWall = tile == null ? false : tile.paramContainer.GetBoolParam("isWall", false);
// When will the tile be null?
// It will be null if the tileId is -1 because the tile was not set or it was set to empty ex: tilemap.Erase(-4, -8)
A new section has been added to the menu bar to access to important information about each tool. Including special notifications, manual, and video tutorials.
And using the returned list of sorting layer names, create a popup in the editor.
But the editor popup is not showing the special option “Add Sorting Layer…”, like the one in SpriteRenderer:
When you press on “Add Sorting Layer…”, Tags & Layers setting are displayed on the Inspector View, and “Sorting Layers” foldout is open to easily add a new sorting layer to the project.
I have created two methods, similar to the ones used internally by Unity, to create this popup:
I did this script for moving platforms to move along a path, but it can be used also for patrol behaviors.
What this is script is doing is basically move the object this script is attached along a path of nodes, and waits in each node during the specified Waiting Time.
Moving Time is the time to go from a node to the next one.
There is 3 modes for patrol:
None: the object stops when reaching the target position
Loop: the object goes through all the nodes in a loop
Ping-Pong: the object is going forward through the path, then in reverse, once it reach the last node
LoopPing-Pong
Editing the path is simple. The first node will be always at object position, and there will be a minimum of 2 nodes.
You can add a node by selecting one of them and Shift + Dragging the node.
Shift + Click + Dragging
You can remove the nodes by Ctrl + Clicking on any of them
Control + Click
And finally, you can drag any of the nodes by dragging any node or move all of them in block by moving the parent object
Dragging nodes
Holding Ctrl while dragging a node will snap its position
These are some of the features of the SmartRectCollider2D included in the Smart 2D Colliders asset
I have used it to create a platform controller and I have made a gif showing it in action solving different problems you can found when making a platform game.