BLog

How to iterate a Tilemap

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;
}

 

Advertisements

Creating a simple game with Super Tilemap Editor: Temple of Doom Part 1

Hi there!

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:

  1. Game Introduction
  2. Creating a Tileset from an atlas texture
  3. Creating some tileset brushes
  4. Creating a tilemap group and a simple level using the tileset and the brushes
  5. Adding colliders to the wall tiles
  6. Make the broken wall tiles autotile with the Wall Brush using autotiling groups

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

goldenIdol2xGolden 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.

bagOfSand2xBag 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.

coin12xcoin22xcoin32xGolden 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).

hammer2xHammer: allows you to break broken walls until the hammer is broken.

map2xMap: shows you the path to the Golden Idol and to the exit.

torch2xTorch: Illuminates the path and scares away the mummy.

Actors

explorer2xExplorer: The adventurer protagonist of this game controlled by the player.

mummy2xMummy: 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:

Temple Of Doom Resources Part 1

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.

1-createTileset714

createdTileset1

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.

createRoadBrush
Create a Road Brush
addTilesetToBrush
Assign the tileset. A brush can only be attached to a tileset.

wallBrushSetup

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.

randomBrushSetup

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:

importBrushesButton

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.

createTilemapGroup
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.

createTilemapGroup

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.

drawingLevel

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.

tipHighlightAlpha


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).

openTilePropertyWindow

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.

setupTileColliders

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.

setupTilemapColliders
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.

setupTileColliderInSceneView


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.

wallBadAutotiling
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

autotilingModes
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.

autotilingOther

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:

doorWall
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.

creatingWallGroup
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.

wallBrushGroupWall

brokenWallGrouped
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.

wallGroupInAction


With this ends the first part of this tutorial.

You can Download the example project used as example here:

Temple of Doom Part 1

I hope you have enjoyed it. If you did, please share it with your friends and spread the word. That would help me to create new tutorials.

And if you have any suggestion to improve this tutorial, don’t hesitate to let me know leaving a reply in this post.

In the next tutorial we will focus more in gameplay, creating a player, the mummy AI working with pathfinding and more…

Here is an advance of what we will see in the next tutorial:

  1. Creating a player controller with a tile based movement and simple animation
  2. Creating a mumy actor with an AI to walk around and follow the player on sight using path finding
  3. Collectibles taken by the player on touch
  4. Simple HUD to show the treasure taken by the player and lifes.
  5. Touch controller for a touch device or to control the player by clicking with the mouse on the screen.

 

Understanding TileData

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

[HFlip:bit31|VFlip:bit30|Rot90:bit29|Updated:bit28|BrushId:bit27-16|TileId:bit15-0]

You can see some examples managing a tile data in the tutorial: Set and Get Tile data in an STETilemap

Set and Get Tile data in an STETilemap from script

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)