Create a 2D Dungeon with Tilemaps

In this tutorial, you will create the dungeon setting for the game using the Tilemap system.

Paint the Dungeon



A technique for easily creating 2D worlds in video games in by using Tilemaps. Tilemaps are grids that are made up of rectangular tiles where you can place 2D sprites.

Unity has a built in Tilemap system where you can create Tiles in a Tile Palette, and essentially paint them onto the tilemap grid. Therefore we can create background tiles, wall tiles, floor tiles etc. and paint them into the scene to create a 2D world.

To get started, open the Tile Palette Window by going to Window > 2D > Tile Palette. Then dock the window to the center by dragging the tab next to the other tabs in the center.

If you don't have the Tile Palette installed, go to Window > Package Manager, search for Tilemap Editor, and click Install in the lower right corner.



Now we can create a new Tile Palette where we can add Tile Assets that will be used to paint sprites onto the scene.

First go into the Assets folder in the Project Window and create a new folder named Environment. This is where we will save the Tile Palette, Sprites and Tiles to create the 2D Dungeon environment.

Then back in the Tile Palette Window, click on Create New Palette, change the name to Dungeon and click Create. This will open a window for you to choose where to save the new Palette.

Locate the Environment folder that you created and save the Palette there.



Let's add some Tiles to the Palette that we can use to create the Dungeon.

First go into the Environment folder and create a new square sprite. You can do this by right clicking, then selecting Create > Sprites > Square.



Now you can drag that square sprite into the Palette Window to create a new Tile Asset. We will start by creating a Tile that will be used to paint the background.

Drag the square sprite into the Palette Window for the Dungeon Palette and a window will pop up asking you where to save the new asset. Name the asset Background and save it in the Environment folder.



Now let's edit the color of the Background Tile.

In the Environment folder you will see the new Background Tile asset. Click on this to view it in the Inspector Window.

Here you can change the color of the Tile to be the color that you want for the dungeon background.



Now drag the square sprite into the Palette Window again to create a new Tile asset, name it Wall and save it in the Environment folder.

Then change the color of that Tile to be the color that you want for the dungeon walls.



Before we can use the Tiles to create the 2D dungeon, we have to create a Grid with a Tilemap that the Tiles can be painted onto.

To do this, right click in the Hierarchy Window and select 2D Object > Tilemap. This will create a Grid Object with a Tilemap Object inside.

Rename the Tilemap to Background.



In the Scene Window you will now see a grid layout which is the tilemap that can now be painted on.

If you move your mouse over the grid in the Scene Window you may see the Tile you created move with the mouse. This is the Brush Tool that allows you to paint tiles onto the Tilemap grid.

If you do not currently have the Brush tool selected you can activate it by clicking on the brush icon in the top-left toolbar, or by clicking brush icon on the toolbar in the Tile Palette Window.



Before we start painting the background, we should change the rendering layer for the Background Tilemap so that the background always appears behind the player and the other sprites.

To do this, click on the Background Tilemap to view its components in the Inspector Window. Then under Additional Settings, change the Order in Layer field to have a value of -5.

This will make sure that the Background sprites are always behind other sprites.



Now you can finally paint your Background Tiles onto the Tilemap.

First make sure you have the Background Tile selected by clicking on it in the Tile Palette Window. Also make sure you have the Brush tool selected.

In the Scene Window, click on a square in the grid to paint a sprite there. You can then hold and drag the mouse to quickly paint multiple tiles in the Scene.

Do this to create a basic layout for your dungeon.



If you make a mistake when painting the Tiles you can select the Eraser Tool from the toolbar in the Tile Palette Window.

Then you can click and drag over the Tiles you want to erase from the Tilemap. Just make sure to reselect the Brush tool when you need to paint again.



The next step is to paint the Wall Tiles around the edge of the dungeon.

First create a new Tilemap within the grid by right clicking on the Grid object and selecting 2D Object > Tilemap. Then rename the Tilemap to Walls.



Now you can select the Wall Tile from the Dungeon Tile Palette, and paint the wall for the dungeon.

Make sure that the Walls Tilemap is selected in the Hierarchy Window before you start painting the walls. This will ensure that the Tiles are actually painted onto the correct Tilemap.



Great! You have now created the visuals for the 2D dungeon. However, if you play the game now you will notice that the player can walk through the walls.

To stop this, we have to add a Tilemap Collider 2D component to the Walls Tilemap. This will create a collider for each Tile in the tilemap.



We do not want an individual collider around each Tile as this can cause problems like the player clipping into the wall. Instead we want one seamless collider around the wall tiles that are connected together.

To do this, we can add another component to the Walls Tilemap called a Composite Collider 2D. Then in the Tilemap Collider 2D component, we can check the Used By Composite setting so that the Composite Collider is used.

Adding the Composite Collider will also automatically add a Rigidbody 2D component. You should set the Body Type for this to Static since we do not want the walls to move.



Now if you play the game you will be able to walk around your dungeon and the player will be stopped by the walls.



You will notice that when the player walks through the dungeon, the camera does not follow, and so the player will walk off screen.

We can easily fix this by dragging the Camera object onto the Player object so that the camera becomes a child of the player. This will cause the camera position to change based on the player position.



Collect Gems



At the moment the player can explore the dungeon, but they should have more to do. Let's give place gems throughout the level that the player can collect.

First let's create a gem sprite and object. In the Assets folder, create a new folder named Sprites. Then within that folder, create a new Diamond sprite by right clicking and selecting Create > Sprites > Diamond.



Now drag the sprite into the Hierarchy Window and change the color to something like blue so it looks more like a diamond.

Also resize the Diamond so it is smaller and add a Circle Collider 2D with the Is Trigger field checked so that the player can collide with the object.



Now we need a way to permanently store the number of gems that we have.

To do this we will create a Game Manager script that will hold all the main variables of the game that we want to store permanently. This way, even if we load a new scene, the values of the variables will still be saved.

First create an Empty Game Object named Game Manager. Then go into the Scripts folder, create a new script named GameManager and drag this script onto the Game Manager object.



Now open the script and we will start by creating a Singleton Pattern.

A singleton pattern is just a way of ensuring that only one instance of a class/object exists. Since the Game Manager is going to be persistent throughout all scenes, we want to make sure that only one exists so that we have one consistent object to hold persistent data.

Start by deleting the Start() and Update() methods and instead create an Awake() method which will always run first.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{

    void Awake()
    {

    }
}
          

Now create a public static GameManager variable named Instance.

This is static because it is not a member variable of the actual instance object but instead is a global variable for the Game Manager class itself.

This will allow us to reference the Instance variable in any script by typing GameManager.Instance as opposed to needing to reference an actual object.

public class GameManager : MonoBehaviour
{
  public static GameManager Instance;

    void Awake()
    {

    }
}
          

In the Awake() method we can create the singeleton pattern.

First we use an if statement to check if the Instance variable is not null (meaning that it already exists) and is not this script's instance. If so, then we can just destroy this game object because we already have another Game Manager.

Otherwise, if the instance does not exist then we can just set the Instance variable to this script. We can then use the DontDestroyOnLoad() method so that this game object is not destroyed when a new scene is loaded.

public class GameManager : MonoBehaviour
{
  public static GameManager Instance;

    void Awake()
    {
        if(Instance != null && Instance != this )
          {
              Destroy(this.gameObject);
          }
          else
          {
              Instance = this;
              DontDestroyOnLoad(this.gameObject);
          }
    }
}
          

Now when we create more levels and change between them, the Game Manager will be persistent and so will all the variables that it holds.

Let's create private int variable named gems. It is private so that other scripts cannot access the variable directly but instead will get and update the variable through the functions we create.

public class GameManager : MonoBehaviour
{
  public static GameManager Instance;
  private int gems = 0;

    void Awake()
    {
        if(Instance != null && Instance != this )
          {
              Destroy(this.gameObject);
          }
          else
          {
              Instance = this;
              DontDestroyOnLoad(this.gameObject);
          }
    }
}
          

Now create two new public methods:

1. public int GetGems() which returns the number of gems.

2. public void AddGems(int amount) which takes in a number of gems to add to the total. We can use gems += amount to add the amount to the total and update the variable at the same time.

public class GameManager : MonoBehaviour
{
  public static GameManager Instance;
  private int gems = 0;

    void Awake()
    {
        if(Instance != null && Instance != this )
          {
              Destroy(this.gameObject);
          }
          else
          {
              Instance = this;
              DontDestroyOnLoad(this.gameObject);
          }
    }

    public int GetGems()
    {
        return gems;
    }

    public void AddGems(int amount)
    {
        gems += amount;
    }
}
          

Now we have these methods, open the Player Controller where we will add the ability to collect gems using these methods.

Since the gems will have a Collider with Is Trigger enabled, we can use the OnTriggerEnter2D() method to check for collisions between the player and the gems.

After the Update() method, create a private void OnTriggerEnter2D() method that takes in a Collider2D named collision. This method will run when the player collides with a trigger collider and information of the collision gets stored in the method's collision variable.

        animator.SetFloat("Horizontal", rb.velocity.x);
        animator.SetFloat("Vertical", rb.velocity.y);
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        
    }
}
          

Inside the method, we can create an if statement to check if the object we collided with has the Gem tag (we will add this tag to all of the Gem objects).

We can check the tag of the collision object using the CompareTag() method and pass in the string name of the tag as an argument.

Then if the object does have the Gem tag, we can use the GameManager.Instance.AddGems() method and pass in 1 as an argument to add 1 gem to the total. We will also destroy the Gem that we collided with so it can only be collected once.

      private void OnTriggerEnter2D(Collider2D collision)
      {
          if (collision.CompareTag("Gem"))
          {
              GameManager.Instance.AddGems(1);
              Destroy(collision.gameObject);
          }
      }
}
  

Save the script and go back into Unity Editor.

Select the Diamond object in the Hierarchy Window, then at the top of the Inspector Window, click the Tag dropdown where it says Untagged. Then click Add Tag...

This will open up the Tags & Layers settings where we can create the Gem Tag. Under Tags, click the + button to create a new Tag. Then rename the Tag to Gem and click Save.

Finally, click on the Diamond object again and now when you click the Tag dropdown you will see the new Gem Tag. Select this to tag the Diamond object as a Gem.



Now if you play the game and walk into the gem, the gem will be collected and disappear.



Create the HUD



At the moment there is no way to see how many gems the player has collected. Therefore we will create a HUD (Heads-Up Display) to display the number of Gems collected. Later we will also use this HUD to display the player's health.

Right click in the Hierarchy Window and select UI > Panel to create a Panel object that will hold the HUD UI. This will also create a Canvas as a parent object and an Event System.

Double click the Panel object to see it in full in the Scene Window, then rename the Panel to HUD.



The Panel is currently colored but we want it to be completely transparent.

Click on the HUD Panel object, then in the Inspector Window, under the Image component, change the Color so that the Alpha value (A) is set to 0 (completely transparent).



Inside the HUD Panel, create a new Text object by right clicking and selecting UI > Text.

Rename the Text object to Gems Text, change the Text in the Text Component to GEMS: 0 and edit the text so it is larger and bold.

Then position and anchor the Gems Text in the top right of the Panel.



Now we just have to script the HUD so it updates with the actual gems value stored in the Game Manager.

In the scripts folder, create a new script named HUDController and attach it to the HUD Panel object.



Open the script and delete the Start() method as this is not needed.

Then import the UnityEngine.UI namespace so that we can access the UI classes.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class HUDController : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        
    }
}
  

Create a Text variable named gemsText and use the [SerializeField] attribute so that it appears in the Inspector.

public class HUDController : MonoBehaviour
{
    [SerializeField] Text gemsText;
    // Update is called once per frame
    void Update()
    {
        
    }
}
  

Now in the Update() method, we can set the text value of gemsText to GEMS: + GameManager.Instance.GetGems().

public class HUDController : MonoBehaviour
{
    [SerializeField] Text gemsText;
    // Update is called once per frame
    void Update()
    {
        gemsText.text = "GEMS: " + GameManager.Instance.GetGems(); 
    }
}
  

Save the script and go back into Unity Editor.

On the HUD Panel object, you will now see a Gems Text field under the HUD Controller component in the Inspector.

Drag the Gems Text object from the Hierarchy into the Gems Text field in the Inspector. Then play the game and collect the gem to test that the UI updates.



Create More Dungeons



In this game, the player should start in one dungeon/level, explore it to collect gems, fight enemies, and then reach the exit to the dungeon. When the player reaches the dungeon exit, they should then go to the next dungeon/level which may be harder, with more items and enemies.

First let's create an Exit object to place into the dungeon.

Go into the Sprites folder, create a new Square sprite then rename it to Exit. Then drag the sprite into the Scene and place it over one of the walls.



In the Inspector Window, change the Order in Layer for the Exit to 5 so that it always appears over other sprites. Then resize it to be wider than the wall so that the player can walk into it.

Also change the color to green so that it is clearer that it is an exit.



Finally, add a Box Collider 2D component to the Exit and check the Is Trigger field so that the player can walk into it.



We need to script the Exit so that when the player walks into it, the next Scene (Level) is loaded.

Go into the Scripts folder and create a new Script named ExitController.



Open the script and delete both the Start() and Update() as these are not needed.

Then import the UnityEngine.SceneManagement namespace so that we can use the classes that it has.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class ExitController : MonoBehaviour
{
   
}
  

Now create a OnTriggerEnter2D() method that takes in a Collider2D named collision.

Similar to the gem, we can check the tag of the collision object. If the object has the Player tag then we want then we want to load the next scene.

We can load the next scene using the SceneManager.LoadScene() method. This method takes in the index (number) of the scene to load.

In this case we want to load the exact next scene, so we can pass in SceneManager.GetActiveScene().buildIndex + 1, which gets the index of the current scene and adds 1 to it to get the index of the next scene.

public class ExitController : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Player"))
        {
            SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
        }
    }
}
  

Now save the script and go back into Unity Editor.

Currently we don't have a second level to go to so if we walk into the exit, we will get an error. Let's create a second dungeon that the player can go to when they reach the exit.

The easiest way to create the second dungeon is to duplicate the current scene (Level1) by going into the Scenes folder, clicking on the Level1 Scene, then pressing CTRL + D to duplicate it. Make sure to save the current Scene before doing this.

If you duplicate a Scene with a number, unity should automatically increment the number e.g. duplicating Level1 should create Level2. If this does not happen you can just rename the scene to Level2 manually.



Now open the Level 2 Scene and edit the Tilemap to create a new dungeon layout.



You should also move the Exit to an appropriate place and duplicate the Diamond, placing them around the dungeon.



Now we have two levels, the last thing to do is to add both to the Build so that the SceneManager can find them.

To do this, go to File > Build Settings and drag both scenes into the section that says Scenes in Build.

Make sure the order is correct so that Level1 is first in the list, and Level2 is second.



Now save the Level2 Scene and go back into Level1.

Play the game and walk into the Exit. The next scene will be loaded and you will be able to walk around Level2.

Also notice that the Gem count is the same because the GameManager persists between scenes.