07 May 2024

How to implement a MiniMap in Godot

A few days ago, I published an article about a Godot course I had just finished on Udemy. It was about a 2.5D action game, so even though the course itself didn't cover it, I thought it was the perfect foundation for learning how to make MiniMaps in Godot.

As you may know, a MiniMap is a small window included in many games to show where the character is within the level. You have an example in the image that opens this article, corresponding to the MiniMap of World of Warcraft.

It turns out that implementing a basic MiniMap in Godot is very easy and, in its most basic version, doesn't even require code. Let's start by explaining how to implement a stationary MiniMap and from there we'll progressively sophisticate it by adding the following features:

  1. We'll make the MiniMap move following the main character.
  2. We'll represent the character and its surroundings with icons inside the MiniMap.
  3. We'll see how to make the MiniMap round.
  4. We'll adorn the MiniMap with a border.
  5. Basic stationary MiniMap

Basic stationary MiniMap

This MiniMap doesn't move with the character but shows a section of the scenario. It only makes sense if our level is very small and can be entirely shown within the MiniMap. Still, despite its limited usefulness, it's the basis from which all MiniMaps start.

To implement it, we'll need to add the following nodes to the hierarchy of our scene:

The SubViewportContainer node will allow us to anchor the MiniMap to the area of the screen where we want it to be located by simply selecting that node and setting its anchoring in the main view tab:

Once the MiniMap is placed, you'll probably want to define its size. To do that, you need to first set the size of the SubViewport node, responsible for showing the camera view. To do this, select that node and configure the Size property.

It's possible that after resizing the SubViewport node, you'll need to re-set the anchoring of the SubViewportContainer node.

Once that's done, we'll have a window, overlaid on the main game window, that will show what the third node focuses on: the camera view. What we'll need to do is to place that camera over the scenario, focusing vertically. I usually set up this camera in orthogonal projection because it makes the MiniMap clearer. With its Size parameter, we can encompass more or less of the scenario within the focus. Finally, it's important to uncheck the Current parameter so as not to override the main game's main camera function. Ultimately, those camera parameters can look like this:

At this point, we'll have the most basic version of the MiniMap.

However, this MiniMap has many shortcomings that we'll address in the following sections.

Moving MiniMap

The first problem we have at this point is that since the camera doesn't move, we have to give it a gigantic Size so that the entire scenario fits in the MiniMap frame. This reduces its value because the larger the scenario, the more its details will be reduced in the MiniMap. What is normally done in these cases is to reduce the Size of the camera, causing it to focus only on a part of the scenario around the player character and make it follow the character. This way, although the camera doesn't cover the entire scenario, it will provide enough detail of the player's environment, which is what really matters.

To make the MiniMap camera follow the player, you need to add the following script to it:

As you can see, it simply locates the camera in the same position as the player, retaining the initial height at which we would have placed it in the editor. Since we've implemented the _Process() method, the camera's position is updated every frame. The effect will be that the camera will move with the player, focusing on them from above.

The _player field is just a reference that will be passed throug inspector, dragging the scene player object over that field.

Representation with icons of characters and map elements

The next problem will be that, no matter how much we've brought the camera closer, it's very possible that many of the important elements are too small or not clearly visible. To solve this, most MiniMaps represent those elements with icons, as can be seen in the example MiniMap that starts this article.

In my particular case, since my game was 2.5D, the characters were sprites, so being flat, they were invisible when focused from above with the MiniMap camera.

The solution is to add a sprite just above the elements we want to show on the MiniMap. These sprites will display the icon we want to represent those elements.

For example, in my case, I added the sprite of a helmet above my enemies.

Logically, this icon must be on a plane perpendicular to the focus axis of the MiniMap camera so that it can capture it properly. If the MiniMap camera focuses from the Y-axis, the icon will have to rest on the XZ plane.

At this point, the issue will be that these icons are invisible to the main game's main camera and only show up on the MiniMap camera. To solve this, we can use rendering layers.

In my game, I've created the following rendering layers, within Project > Project Settings... > General > Layer Names > 3D Render:

I placed the pieces of the scenario in layer 1, the player character in layer 2, and the enemies in layer 3. This is configured in the VisualInstance3D section of the main sprite of each of the elements. For example, in the case of the sprite representing the player, its configuration is as follows:

For 3D elements, the rendering layer is configured in the MeshInstance section.

As for the markers' icons, both for the player and the enemies, I've left them on layer 4.

To ensure that the main game's main camera doesn't show the MiniMap icons, set its Cull Mask so that it doesn't display the layer where you've placed those icons.

In the case of our example, the main game's main camera has the following Cull Mask:

Considering the layers I had created, the above means that the main game's main camera only shows the scenario (layer 1), the player (layer 2), the enemies (layer 3), but not the MiniMap markers (layer 4).

In contrast, the MiniMap camera's Cull Mask should include the layer where the icons are.

In the example's case, the MiniMap camera has the following configuration:

Note, therefore, that the MiniMap will show the scenario (layer 1) and the marker icons (layer 4). I don't show the player or the enemies because these are already represented by their respective markers.

The result will be that the player and enemies will be represented as follows on the MiniMap:

Round MiniMap

At this point, we'll have a functionally complete MiniMap, albeit aesthetically modest.

It may be that in our game, a square MiniMap fits, but how would we do it if we wanted a round MiniMap?

There are several ways to do it. Some prefer to use a shader, but I think it's much simpler to use a mask. This is a sprite that overlays an image to define which parts of the image can be seen and which cannot. The sprite that acts as a mask will define the parts that can be seen with an alpha of 1 (regardless of color) and those that cannot with an alpha of 0 (transparent).

A graphic editor like Gimp may be sufficient to create a mask like the following:

Import the mask image into Godot and associate it with a Sprite2D node that you should place as the parent of the MiniMask:

For the sprite to act as a mask, you must set the Clip Children parameter of its CanvasItem section to Clip Only. This will mask all the nodes that it has as children.

When you reposition the MiniMap nodes under the mask sprite, you may need to ensure that the sizes (Transform-Sizes) of the SubviewPortContainer and Subviewport nodes match. If they don't match, you may only see a portion of the MiniMap.

Also, make sure that the Anchor Preset parameter of the SubViewportContainer is set to FullRect so that it fits the size of the mask sprite.

The problem with this scheme is that the MiniMap will lose automatic anchoring to the screen because the parent of the MiniMap (the mask sprite) will no longer automatically reposition when the game window is resized. In these cases, it's usually a good idea to place the mask sprite (and all its children) under a PanelContainer node so that we can anchor it to the screen position we want.

At this point, the problem will be that the MiniMap will be round, but for some reason, the panel on which it is displayed will not be entirely transparent (zoom in on the photo and look at the edges of the MiniMap):

The cause is that the default style of Godot panels includes making their background not transparent. So, we have to change that. Go to the properties of the PanelContainer and, in the ThemeOverrides > Styles section, create an empty StyleBox resource. This will reset the default values of the Panel Container style, and it will become completely transparent.

MiniMap Border

It's common to give the MiniMap a border to highlight it against the background and to match the general aesthetics of the game. Let's see how to do it.

If we're not artists, we can search the Internet for borders for our MiniMaps. However, I advise you to check the rights of what you find to make sure you can use it in your game. A search for "circular Minimap texture download" terms can offer many examples. One of them is the one I used in this tutorial:

Note that the image must contain an Alpha channel, setting with value 0 all those areas, such as the inside of the ring, through which we want our MiniMap to be visible.

The idea is to place that image over the MiniMap, covering the edges. To do this, we have to add a TextureRect node as a child of the MiniMap's PanelContainer. On this new node, we should drag the border image, which will show it in the editor.

The image may have a size that doesn't fit our MiniMap, so we'll have to resize it. To do this, we must set the Expand Mode parameter of the TextureRect to Ignore Size. From there, we can change the size of the border image by modifying its Custom Minimum Size parameter. We'll probably also have to change the Position and Scale parameters of the MiniMapMask node to resize and center the MiniMap image within the border.

The resulting node scheme will be as follows:

And the result:

Note that, in Godot, the painting order of nodes is from top to bottom of the scene hierarchy. That means that nodes closer to the root are painted first, and then their children. So it's important that the TextureRect is below the MiniMapMask so that the MiniMapMask is painted first, and then the TextureRect. The consequence of this is that the TextureRect will be painted on top of the MiniMapMask node, covering its edges.

Try reversing the order and see the effect.