Create a 2D Minimap with Fog of War in Godot

Want to add a minimap to your Godot game, with simple fog of war reveal?  Then this is the perfect tutorial for you! =)

simple minimap with fog

Getting Started

To follow along and see how this works you can download the project files from the minimap folder in my github repo.  The project is for Godot 3.3.3.

This project continues our prior tutorial on creating caves using cellular automata in Godot, and we will be using the generated caves as our map to work with.

To begin you want to create your Main scene in Godot with the following node setup:

  • The top level node is our Navigation2D, to allow the player to navigate through the caves.  Note that your NavigationPolygons (which will be part of our Caves TileMap) must be nested under this node for it to work.
  • Caves is a TileMap that generates our caves (from our prior tutorial)
  • Camera2D will follow the player around as it moves
  • Player is just a sprite that we will move around
  • UI is a CanvasLayer to go above our game
  • And finally, the HUD scene is where we will place our minimap

Making the Player Move

The first thing we want to do is make our player able to move around the caves with the camera following them, so make sure the camera is checked as Current, then lets add some code to the Camera2D node:

extends Camera2D

# camera movement lerp
var rate   = 4
var cutoff = 4

onready var player = get_node("../Player")
onready var caves  = get_node("../Caves")

func _ready():
	var rect      = caves.get_used_rect()
	var tile_size = caves.tile_size

	# set the camera limits to be the size of the caves
	limit_left   = rect.position.x * tile_size
	limit_right  = rect.end.x * tile_size
	limit_top    = rect.position.y * tile_size
	limit_bottom = rect.end.y * tile_size

	# start the camera at the center of the caves
	position.x = rect.position.x + (rect.size.x/2 * tile_size)
	position.y = rect.position.y + (rect.size.y/2 * tile_size)

func _process(delta):
	var dest = player.position
	var x    = lerp(position.x, dest.x, rate * delta)
	var y    = lerp(position.y, dest.y, rate * delta)
	var pos  = Vector2(x,y)
	if(pos.distance_to(dest) > cutoff):
		position = pos

Next lets add some code to our Player Sprite so it can move around the caves by left clicking:

extends Sprite

var path  = PoolVector2Array()
var speed = 400

onready var main = get_parent()

func _process(delta):
	if path.size() == 0: return

	var distance  = speed * delta
	var start_pos = self.position

	for i in range(path.size()):
		var dist_next = start_pos.distance_to(path[0])
		if distance < dist_next and distance > 0.0:
			self.position = start_pos.linear_interpolate(path[0], distance/dist_next)

		elif path.size() == 1:
			self.position = path[0]

		distance -= dist_next
		start_pos = path[0]

func _unhandled_input(event):
	if event is InputEventMouseButton and event.pressed:
		if(event.button_index == 1):
			var mouse_pos = get_global_mouse_position()
			path = main.get_simple_path(self.position, mouse_pos)

What this code is doing, is when you left click on the map, it uses Navigation2D to create a path with a number of points, and every frame we check if the path has any points; if it does we walk there and delete those points as we arrive at them.

* if you want a full tutorial on Pathfinding a 2D TileMap, check out the video by GDQuest covering this, which is where we got this code snippet from!

That is all the code we need, but this wont work just yet!  We need to add some NavigationPolygons to our TileMap.  To do that edit the Caves TileSet and select your ground tile that is walkable, go to the Navigation tab, and draw a square that covers it.  By doing this, we are saying which portions of the map we can walk on.

And now your player should be able to click to move around the caves!

Creating the Minimap

Now lets create the Minimap!  For this we want to create a new scene with the following nodes:

  • Map is a TileMap for the minimap
  • Fog is also a TileMap for the fog of war
  • Minicam is a Camera2D to follow the player on the minimap
  • Dot is a sprite to represent the player on the minimap
  • FogTimer is a timer for revealing the fog of war

Lets start with the simplest code first here, we need to add a script to the MiniCam to make it follow the player around the minimap.  This is very similar to the camera script we used above:

extends Camera2D

# camera movement lerp
var rate   = 8
var cutoff = 4

onready var dot = get_node("../Dot")

func _process(delta):
	var dest = dot.global_position
	var x   = lerp(position.x, dest.x, rate * delta)
	var y   = lerp(position.y, dest.y, rate * delta)
	var pos = Vector2(x,y)
	if pos.distance_to(dest) > cutoff:
		position = pos

Now, the last bit of code goes on the Minimap root node to to make the actual minimap work:

extends Node2D

onready var dot    = $Dot
onready var caves  = get_node("/root/Main/Caves")
onready var player = get_node("/root/Main/Player")

enum Tiles { GROUND, ROOF, FOG }

func _ready():
	$FogTimer.connect("timeout", self, "update_fog")

func generate():
	# add starting tiles to minimap
	for cell in caves.get_used_cells():
		var tile = caves.get_cellv(cell)
		$Map.set_cellv(cell, tile)

	# fill outer borders
	var rect = $Map.get_used_rect()
	var buffer = 15  # how far tiles extends beyond edge of map

	for x in range(rect.position.x - buffer, rect.end.x + buffer):
		for y in range(rect.position.y - buffer, rect.end.y + buffer):
			var cell = Vector2(x,y)
			if $Map.get_cellv(cell) == -1:
				$Map.set_cellv(cell, Tiles.ROOF)

	dot.position = player.position/8

# move the player in the minimap
# minimap is 8x smaller, 32/4 = 8
func _process(delta):
	dot.position = player.position/8

# update the fog of war
func update_fog():
	var player_pos = caves.world_to_map(player.position)

	# how far players can see in fog
	var seex = 8
	var seey = 6

	for x in range(-seex, seex):
		for y in range(-seey, seey):
			var cell = player_pos + Vector2(x,y)
			var tile = $Map.get_cellv(cell)
			$Fog.set_cellv(cell, tile)

So first we generate the minimap based off the cave cells.  To do this, we loop through all the get_used_cells() in the caves tilemap, and then set those same cells on our minimap.

We also set a buffer of 15 cells in every direction, where we set these as the Roof tiles on the minimap if they are empty.  This is more a design decision, but if u do not do this they will be transparent, and because the minimap may be able to see further then the game world, we do this to make it look like the roof extends into the beyond.

When creating a minimap you'll want to make note of the size difference between it and your main map; here our main tileset is 32x32 and our minimap is 4x4, so its 8x smaller, which we can then use in calculations to determine where the player is, like in _process() above.

Adding Fog of War

To make Fog of War work on the minimap is fairly easy.  All we want to do is hide the Map tileset in our minimap, and show the Fog tileset.  The Fog tileset is empty to start, but we tell it to copy the tiles from the Map minimap for all the squares near the player that they can see, which is done in update_fog() above.  As the player moves, we continue to update the Fog tileset, revealing all the tiles they can see.  This is then done on a timer for performance reasons (rather then every frame).

Showing the Minimap in the HUD

As the final step, we now just want to show our Minimap on top of our game world.  To do that you want a HUD scene made of Control nodes setup like this:

The most important part here is our Minimap goes inside a Viewport, and the Viewport needs to go inside a ViewportContainer.

And once you've setup your HUD node, you're done and have a working minimap!

That's a Wrap!

We hope this tutorial has been helpful for creating a minimap in your own game.  If you enjoy our tutorials and are up for offering us a bit of support, we have a kickstarter for Helms of Fury starting in 5 days that could use a few more followers!

Hope you have a great weekend, and if you have any questions about the tutorial feel free to chat with us as @abitawake on Twitter =)