Isometric 2D Environments with Tilemap
With the release of Unity 2018.3, we introduced Isometric Tilemap support — closely following Hexagonal Tilemap support which was added in the 2018.2 release. The new Tilemap features provide a fast and performant way to create 2D environments based on isometric and hexagonal grid layouts, the likes of which are seen in many game classics, including the first entries of the Diablo and Fallout franchises, Civilization, Age of Empires, and many more.
Both features build on top of the existing Tilemap system introduced back in Unity 2017.2, and working with them today is just as easy! They are also natively integrated into the Editor. In further Unity releases, they might be moved to the package manager.
If you’re interested in following along and experimenting with the techniques shown, we’ve created a pre-configured Isometric Starter Kit project with an animated character and multiple environment tilesets, which you can download for free.
Project Settings for Isometric Tilemap
Before we start working with Tilemap, it is important to set up our project correctly. Isometric Tilemap works with 2-dimensional sprites, and it relies on correct renderer sorting in order to create the illusion of a top-down isometric view. We need to make sure that the tiles that are further away from the viewer get painted first; and those that are closer painted on top of them.
To customize the order in which 2D objects are painted on the screen, we can use Unity’s Custom Axis Sort feature. You can define this setting either per-camera (currently, this is the default way to do it in the Scriptable Render Pipelines, including LWRP and HDRP) — or globally at the project level.
To define a Custom Axis Sort at the Project level, go to Edit > Project Settings > Graphics. In the Camera Settings section, you will see a Transparency Sort Mode dropdown, as well as the X, Y, and Z value settings for the Transparency Sort Axis.
By default, the Transparency Sort Axis in Unity is set to (0, 0, 1) for XYZ respectively. However, all of our 2D tiles are actually on the same Z plane. Instead, we can determine which tiles are behind or in front by using their height on screen, rather than their depth. Tiles which are positioned higher on the screen will be sorted behind those which are placed lower. To sort the tiles based on height, change the Transparency Sort Mode to Custom; and set the Transparency Sort Axis values to (0, 1, 0).
You can read the relevant Unity Documentation page for 2D sorting if you want to learn more about how it works.
In some cases, you may also want to adjust the Z value of your Transparency Sort Axis. We will cover this in more depth later on in this blog post.
The Tilemap feature consists of several components working together. The first two are the Grid and Tilemap Game Objects. To create a Grid, simply right-click anywhere in the Hierarchy, go to 2D Object, and select the type of Tilemap you wish to use. By default, each new Grid is created with one child Tilemap Game Object of the corresponding type. The currently available Tilemap types are as follows:
Tilemap — creates a rectangular Grid and Tilemap. An example of using this Tilemap can be seen in Unity’s 2D Game Kit.
Hexagonal Point Top Tilemap — creates a Hexagonal Grid and Tilemap, where one of the vertices of each hexagon is pointing upwards.
Hexagonal Flat Top Tilemap — another Hexagonal Grid type, where the top of the hexagon is an edge which is parallel to the top of the screen.
The last two types, Isometric and Isometric Z as Y, create two different implementations of the isometric grid. The difference between them arises when simulating different tile elevation levels, such as when we have a raised platform in our Isometric level.
A regular Isometric Tilemap is best used when you wish to create separate Tilemap Game Objects for each individual elevation level of the tiles. This will simplify the process of creating automatic collision shapes — but you will have less flexibility when it comes to height variation between the tiles since all the tiles on one layer will have to be on the same ‘plane’.
In the case of an Isometric Z as Y Tilemap, the Z position value of each tile works in combination with the custom Transparency Axis Sort setting to make the tiles appear as stacked on top of one another. When painting on a Z as Y Tilemap, we dynamically adjust the Z setting on the brush to switch between different heights. The Z as Y Tilemap requires an additional Z value in the Custom Transparency Sort Axis to render correctly.
Note: The assets shown here are from the Temple tileset in our Isometric Starter Kit project. Feel free to grab it — completely free — and have some fun creating your own environments!
Think of the Grid as the ‘easel’ that holds your Tilemap Game Objects – which are essentially canvases that you will be painting your tiles onto. To start painting on a Tilemap, you also need a brush and a palette. A Tile Palette is what holds your tile assets, after which you can pick them with the brush tool and start painting.
To create a Tile Palette, choose Window > 2D > Tile Palette. In the newly opened window, in the top left dropdown choose “Create New Palette”. Make sure to set the grid type that corresponds to your use case. For this example, I will be using a regular Isometric Tilemap; as well as the assets from our Isometric Starter Kit project. Set the palette cell size to Manual to be able to customize the dimensions of your isometric tiles. In this case, I know that the dimensions of my tiles correspond to a grid of 1 in X and 0.5 in Y; however, for your use case, it will depend on the resolution, pixels per unit values selected at import and dimensions of the assets — essentially, on the isometric angle at which the tiles are rotated.
A note on importing assets
You might be unsure about the correct import settings and tilemap size that will work for your assets. There is a general rule that you can follow here based on your initial asset dimensions. First, take a look at the resolution of your tiles. Typically, isometric tiles that are represented as a block are taller than they are wide; ‘flat’ tiles (ones that appear as a plane rather than a cube) are wider than they are tall. However, the width will always be the same between them. Therefore, if you want your tiles to take up exactly one Unity unit, set the Pixels Per Unit value in the tile import settings equal to their width in pixels. You may want to adjust this value in some cases — usually by decreasing it (or increasing the actual resolution of your assets); this could be useful if you are trying to produce an effect where some tiles appear to take up more than one grid cell and overlay the neighboring tiles.
In order to decide on the correct Y grid value for the tiles, take the height of the base (or cap) of a single tile, and divide it by the width. This will give you a Y value relative to the X, provided that X is 1. Let’s look at some examples:
For the pixel art that we are using in this project, all tiles have a base height of 32 pixels, and are 64 pixels wide. Therefore, the grid size that we will be using is exactly 0.5 in Y.
The second block in the example image comes from an asset pack from Golden Skull Studios — you can check it out here. The example tile has been scaled down for reference, but the original assets are 128 pixels wide. The tile base is about 66 pixels tall, giving us a Y grid size of 66/128 — approximately 0.515 units.
Basic Tilemap Workflow
Once we have settled on the correct Grid dimensions, let’s go ahead and add some tiles to our palette. Simply grab one of your tile sprites and drag it over into the Tile Palette window. This will create a Tile Asset. It contains some information about the tile itself, such as the sprite(s) that it is using, a tint color, and the type of collider that it will generate. If you want to see the detailed information about a tile on the palette, choose the Select (S) tool at the top of the Tile Palette window and click on that tile. Now, in the Inspector, you should be able to see which Tile asset it is referencing.
To paint the new Tile onto our Tilemap, select the Brush (B) tool, and click the Tile in the Palette. You will now be able to paint with the selected Tile in the scene view. Some other painting tools include the Eraser (D), Box Fill (U), Flood Fill (G), and the Tile Picker (I).
Sometimes, you might also wish to edit the arrangement of the tiles in the palette itself. Just below the toolbar, you will see an Edit button. If you click it, you will go into the palette editing mode, during which the tools will affect the Tile Palette itself. Don’t forget to exit out of this mode once you’ve made the desired changes.
Tilemap Renderer Modes
In some cases, you might see a situation where tiles of different types are not sorting correctly, despite being on the same Tilemap, like in the example below:
This is determined by the Mode setting on the Tilemap Renderer component. By default, the Mode is set to Chunk.
Chunk mode is effective at reducing the performance cost of Tilemap. Instead of rendering each tile individually, it batch renders them in one go, as a large block. However, there are two main drawbacks to using it. The first one is the fact that it does not support dynamic sorting with other 2D objects in the scene. This means that if your Tilemap is in Chunk mode, it will not be able to dynamically sort behind and in front of other objects, such as characters — only one or the other will be possible at a time, based on the Order in Layer setting. However, it is still extremely effective when you want to optimize your game, and can be used to batch render large areas of the ground.
However, this does not get around the issue of different tiles not sorting with each other. In order to batch-render tiles that come from two or more different sprites (i.e. textures), the sprites have to be unified under a single Sprite Atlas asset.
To create a Sprite Atlas, choose Assets > Create > Sprite Atlas. In the Sprite Atlas settings, you will find the list of Objects for Packing. Simply drag all of the tiles that you wish to be batch rendered into this list, and set the correct import settings — usually equivalent to those on your individual sprites.
Once you have done that, the tiles will sort correctly; but they will only be visible in this way when in Play mode or at runtime.
As such, it is better to set your Tilemap Renderer Mode to Individual while editing. It will sort each tile separately; which means that you will see them correctly rendered even outside of Play mode — which is extremely useful when you are still making changes to your level. Once you have your level structure in place, you can always set the Tilemap Renderer Mode back to Chunk.
Individual Render Mode is also useful when you want to add objects — such as trees, props, and elevated ground that you wish to sort dynamically with characters, or with each other. During this blog post, we will stick to using Individual Mode for all of our Tilemaps.
Using Multiple Tilemaps
Sometimes, you might want to use more than one Tilemap on the same Grid. In the case of Isometric and Hexagonal Tilemaps, it will be useful if you want to add prop objects to the level that also align with the grid; or if you want to add tiles that appear to be higher than the first layer.
To attach another Tilemap to the grid, right-click on the Grid Game Object, and create a new Tilemap of the corresponding type.
In order to switch to painting on the new Tilemap, go back to the Tile Palette window, and change the Active Tilemap just below the main toolbar.
Adding Elevated Areas
There are generally two ways to go about adding elevated ground to your levels. The one that you will most likely use depends on the type of tilemap you choose to go with. We’ll go over each of the possible cases.
Additionally, we have prepared a short video on the topic, which demonstrates one of these approaches with a regular Isometric Tilemap; as well as adding collision areas to the tiles. Check it out if you want to have a quick video reference for both of these things:
Using a Regular Isometric Tilemap
For normal Isometric Tilemaps, you can simply create a new Tilemap under the same Grid; and give it a higher Order in Layer value. You can then change the Tile Anchor setting to make the new layer anchor to a higher point on the grid.
My ground-level Tilemap had a Tile Anchor of (0, 0) for X and Y respectively. I want my new layer to paint one unit higher; so I will change the new Tilemap’s anchor point to (1, 1). Additionally, I will give it an Order in Layer of 1 — just one unit higher than my base level.
I can now change my active Tilemap to the one with the second height level and paint away.
Using a Z as Y Isometric Tilemap
Sometimes it can be useful to simulate different heights using the same Tilemap. For this case, you can use a Z as Y Isometric Tilemap and Grid.
With a Z as Y Tilemap, the Z value of each tile has an additional influence on tile rendering order. We can adjust the Z value that tiles have while we are painting them, using the Z Position setting on our brush in the bottom part of the Tile Palette (which can also be changed using the ‘+’ and ‘-’ hotkeys):
However, in order for our Z value to contribute properly and for the tiles to sort correctly, we need to go back to our Custom Axis Sort value and add a Z influence. The number that we use here is directly connected to the way in which Unity converts cell positions on an isometric grid to world space values.
For example, a grid with XYZ dimensions of (1, 0.5, 1) — the default for isometric — will have a Z-axis sort value of -0.26. If you are curious as to how this number is calculated, or you are using a grid with a different cell size — read on to learn how to find the right Z value for your case.
Once you have set the correct Custom Axis Sort value, you can start painting tiles that have different Z values. You can also adjust the increments in which the Z value moves the elevated tiles up or down by changing the Grid’s Z dimension — set to 1 by default.
Calculating the Z value
There is a general formula you can use to work out the Z value of your axis sort. First, take the Y dimension of your grid. If you haven’t worked out your Y dimension yet, take a look at the note on importing assets at the top of this blog post. Multiply this value by negative 0.5, and subtract an additional 0.01 from it.
Following this formula, a grid that has the dimensions (1, 0.5, 1) will give us a Z sorting value of -0.26 (negative point twenty-six). At this axis sort value, any (1, 0.5, 1) grid will have its tiles sorting correctly.
If you want to find out more about where this value and calculation comes from, take a look at the documentation here. It explains in great depth how 2D renderers work, and what method is used when converting isometric cells into world space values.
Now that we have some tiles placed higher than others, we can control the areas that the player can go to and transition between them using collision.
There are many approaches to adding collision; but in our case, we want the player to ascend and descend along the level using a ramp, and as such, it is not obvious which objects should or shouldn’t have colliders on them. Instead, we can define collision by hand using an additional Tilemap.
In this project, we have created some sprites that correspond to the different shapes that we will use to define our collision areas. We can paint these shapes onto our third Tilemap, in the areas that we do not want the player to pass over. For example, we want the player to be able to ascend to the cliff only using the ramp, rather than walking onto it directly.
We can also add a custom Material in our Tilemap Renderer component in order to tint the tiles a different color that is distinct from the rest of our level.
Once we have placed our collision tiles, we can add a Tilemap Collider component to the collision Tilemap. This will auto-generate colliders for each individual tile based on the shape of the sprite.
For better performance, we can also add a Composite Collider 2D component, and make sure to tick Used by Composite on our Tilemap Collider. This unifies all of our individual colliders into one big shape.
Adding props to the level is quite simple. You can either manually place the prop sprites at any desired point in the scene, or you can attach the props to the Tilemap Grid by making them into individual tiles. You can decide which approach works best for your case.
In this project, we’ve manually placed some trees around the level. The trees and the character have the same Order in Layer, allowing our character to sort behind and in front of them dynamically.
We can define the point at which the player can pass the tree by using a collider. There are several ways in which we can do this.
The first one, as demonstrated in the videos, is to attach a child collider to the object, and change its shape as needed.
The other method is to define a Custom Physics Shape for the object within the Sprite Editor.
To open the Sprite Editor, select the object’s sprite and find the Sprite Editor button in the Inspector. In the top-left dropdown, switch to the Custom Physics Shape editor. Here, you are able to create a polygonal shape to define the bounds of your custom collider.
Once you have defined a physics shape, you can attach a Polygon Collider component to your object, and it will correspond to that shape.
If you are using your props as tiles on a Tilemap, you could also use a Grid collider. Select the Tile Asset that corresponds to a prop tile (if you need a refresher on where to find it, take a look at the Basic Tilemap Workflow section). You will be able to see a dropdown setting for the Collider Type. By default, it is set to sprite — meaning the auto-generated collider will use the Physics Shape we talked about earlier. If you set it to grid, however, it will always exactly match with the shape of the grid cell that the prop is attached to. It may not be the most accurate way of implementing colliders but could be useful for a specific type of game.
To use the grid colliders for these tiles, select the Tilemap with your props and add a Tilemap Collider component.
Using Rule Tiles
Rule Tiles are extremely useful when it comes to automating the tile painting workflow. A Rule Tile acts as a normal tile, with an additional list of tiling parameters. Using these parameters — or rules — the tile can automatically choose which sprite should be painted based on its neighboring tiles.
Rule Tiles are useful when you want to avoid hand-picking differently rotated tiles — for example, when creating a cliff or platform. They can also be used to randomize between different variations of the same tile to avoid obvious patterns, and even to create animated tiles.
Isometric and Hexagonal Rule Tiles are available from Unity’s 2D Extras repository on GitHub. They also contain many other handy assets for the Tilemap feature that you may want to explore.
We have also included pre-configured Rule Tiles for each of the different tilesets in our Isometric Starter Kit project. Here are some examples of the tiles included in the project for you to experiment with:
Taking it further
Now that you’ve learned the ins and outs of Isometric Tilemaps in Unity, download the Isometric Starter kit project here and try it out yourself! It’s also possible to interact with Tilemaps via script if you are a programmer, so that might be something you want to try as well.
For example, you can find out how you can implement a simple character controller which works with Isometric Tilemap by taking a look at this video:
The artwork in this project was created for Unity by @castpixel and you can see more of her work here. If you are interested in learning more about creating 2D games with Unity we have some great courses available over at the Learn website. If you are looking for additional 2D assets to experiment with using Tilemaps, you can check out the Unity Asset Store as well.
First time designing levels with Tilemap? Explore worldbuilding in 2D in this beginner tutorial on Unity Learn.
15 КомментарииПодписаться на комментарии