Minimap
A zoomed out Camera2D on a SubViewport that shares the World2D of the game. The HUD sits outside that world, which is why it stays out of the map.
The editor above is running this project. Change a line and it reloads.
Godot classes used
SubViewportCamera2DWorld2D
The code
scripts/main.gd
extends Node
const MAP: Vector2 = Vector2(2600, 1600)
const SPEED: float = 400.0
const MINIMAP_SIZE: Vector2i = Vector2i(320, 240)
var stage: SubViewport
var player: Node2D
var minimap_camera: Camera2D
var route: PackedVector2Array = PackedVector2Array()
var target: int = 1
var manual: bool = false
func _ready() -> void:
_add_background()
var view: Vector2 = get_viewport().get_visible_rect().size
var container: SubViewportContainer = SubViewportContainer.new()
container.stretch = true
add_child(container)
container.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
stage = SubViewport.new()
stage.size = Vector2i(view)
stage.render_target_update_mode = SubViewport.UPDATE_ALWAYS
container.add_child(stage)
_add_world()
route = PackedVector2Array([Vector2(420, 400), Vector2(2180, 420), Vector2(2160, 1200), Vector2(1300, 800), Vector2(440, 1180)])
player = _add_player()
player.position = route[0]
var camera: Camera2D = Camera2D.new()
camera.position_smoothing_enabled = true
camera.position_smoothing_speed = 5.0
camera.limit_left = 0
camera.limit_top = 0
camera.limit_right = int(MAP.x)
camera.limit_bottom = int(MAP.y)
player.add_child(camera)
camera.make_current()
var minimap: SubViewport = SubViewport.new()
minimap.size = MINIMAP_SIZE
minimap.render_target_update_mode = SubViewport.UPDATE_ALWAYS
add_child(minimap)
# a Viewport is born with its own World2D, so sharing one has to be explicit
minimap.world_2d = stage.world_2d
minimap_camera = Camera2D.new()
minimap_camera.zoom = Vector2(0.26, 0.26)
minimap_camera.position = player.position
minimap.add_child(minimap_camera)
minimap_camera.make_current()
_add_hud(minimap)
print('the corner panel is a second Camera2D on the same World2D, wasd drives the square')
func _process(delta: float) -> void:
var drive: Vector2 = Vector2(
float(Input.is_key_pressed(KEY_D)) - float(Input.is_key_pressed(KEY_A)),
float(Input.is_key_pressed(KEY_S)) - float(Input.is_key_pressed(KEY_W))
)
if drive != Vector2.ZERO:
manual = true
if manual:
player.position += drive.normalized() * SPEED * delta
player.position = player.position.clamp(Vector2(80, 80), MAP - Vector2(80, 80))
else:
var goal: Vector2 = route[target]
player.position = player.position.move_toward(goal, SPEED * delta)
if player.position.distance_to(goal) < 4.0:
target = (target + 1) % route.size()
minimap_camera.position = player.position
func _add_world() -> void:
var outside: Polygon2D = _make_rect(MAP + Vector2(2400, 2400), Color('#12141a'))
outside.position = MAP * 0.5
stage.add_child(outside)
var ground: Polygon2D = _make_rect(MAP, Color('#12141a').lerp(Color('#2a2f3d'), 0.5))
ground.position = MAP * 0.5
stage.add_child(ground)
for x in range(200, int(MAP.x), 200):
_add_grid_line(Vector2(x, 0), Vector2(x, MAP.y))
for y in range(200, int(MAP.y), 200):
_add_grid_line(Vector2(0, y), Vector2(MAP.x, y))
for wall in [
{ at = Vector2(MAP.x * 0.5, 45), size = Vector2(MAP.x, 90) },
{ at = Vector2(MAP.x * 0.5, MAP.y - 45), size = Vector2(MAP.x, 90) },
{ at = Vector2(45, MAP.y * 0.5), size = Vector2(90, MAP.y) },
{ at = Vector2(MAP.x - 45, MAP.y * 0.5), size = Vector2(90, MAP.y) },
{ at = Vector2(900, 760), size = Vector2(560, 90) },
{ at = Vector2(1700, 480), size = Vector2(90, 620) },
{ at = Vector2(1820, 1160), size = Vector2(620, 90) },
{ at = Vector2(700, 1300), size = Vector2(90, 520) },
{ at = Vector2(1180, 1420), size = Vector2(700, 90) },
{ at = Vector2(2280, 800), size = Vector2(90, 480) },
{ at = Vector2(420, 700), size = Vector2(90, 340) },
]:
var block: Polygon2D = _make_rect(wall.size, Color('#8da5f3').lerp(Color('#12141a'), 0.45))
block.position = wall.at
stage.add_child(block)
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
rng.seed = 7
for i in 26:
var crate: Polygon2D = _make_rect(Vector2(90, 90), Color('#f3d48d').lerp(Color('#12141a'), 0.35))
crate.position = Vector2(rng.randf_range(200, MAP.x - 200), rng.randf_range(200, MAP.y - 200))
crate.rotation = rng.randf_range(-0.4, 0.4)
stage.add_child(crate)
func _add_player() -> Node2D:
var body: Node2D = Node2D.new()
body.add_child(_make_rect(Vector2(80, 80), Color('#8eef97')))
body.add_child(_make_rect(Vector2(30, 30), Color('#fc7f7f')))
stage.add_child(body)
return body
func _add_grid_line(from: Vector2, to: Vector2) -> void:
var line: Line2D = Line2D.new()
line.points = PackedVector2Array([from, to])
line.width = 3.0
line.default_color = Color('#2a2f3d')
stage.add_child(line)
func _make_rect(size: Vector2, color: Color) -> Polygon2D:
var rect: Polygon2D = Polygon2D.new()
rect.polygon = PackedVector2Array([-size * 0.5, Vector2(size.x, -size.y) * 0.5, size * 0.5, Vector2(-size.x, size.y) * 0.5])
rect.color = color
return rect
func _add_hud(minimap: SubViewport) -> void:
var hud: CanvasLayer = CanvasLayer.new()
add_child(hud)
var view: Vector2 = get_viewport().get_visible_rect().size
var origin: Vector2 = Vector2(view.x - MINIMAP_SIZE.x - 34, 34)
var frame: ColorRect = ColorRect.new()
frame.color = Color('#8da5f3')
frame.position = origin - Vector2(4, 4)
frame.size = Vector2(MINIMAP_SIZE) + Vector2(8, 8)
hud.add_child(frame)
var picture: TextureRect = TextureRect.new()
picture.texture = minimap.get_texture()
picture.position = origin
picture.size = Vector2(MINIMAP_SIZE)
hud.add_child(picture)
var label: Label = Label.new()
label.text = 'minimap zoom 0.26'
label.add_theme_font_size_override('font_size', 20)
label.add_theme_color_override('font_color', Color('#8da5f3'))
label.position = origin + Vector2(0, MINIMAP_SIZE.y + 10)
hud.add_child(label)
var title: Label = Label.new()
title.text = 'the HUD is outside the shared world, so it stays out of the minimap'
title.add_theme_font_size_override('font_size', 20)
title.add_theme_color_override('font_color', Color('#8eef97'))
title.position = Vector2(40, 40)
hud.add_child(title)
func _add_background() -> void:
var layer: CanvasLayer = CanvasLayer.new()
layer.layer = -1
add_child(layer)
var background: ColorRect = ColorRect.new()
background.color = Color('#12141a')
layer.add_child(background)
background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)