Voltar para o Blog
Quest Log

Como criar um minimapa no Godot 2D

Cena 2D de um jogo com um minimapa circular no canto superior mostrando o mapa e marcadores de inimigos

Tutorial de minimapa godot em 2D: SubViewport com Camera2D de zoom out, exibição no canto da tela e versão leve com marcadores por proporção.

Como criar um minimapa no Godot 2D

Minimapa é daqueles recursos que parecem exigir um sistema enorme e, no Godot, se resolvem com dois ou três nodes bem colocados. A ideia central de um minimapa godot é simples: renderizar o mesmo mundo uma segunda vez, com uma câmera bem mais afastada, e desenhar o resultado num canto da tela. O motor já tem a peça exata pra isso, o SubViewport, e o resto é configuração.

Nesse tutorial eu mostro o caminho completo: SubViewport compartilhando o mundo 2D do jogo, uma Camera2D secundária com zoom out seguindo o player, e a exibição no canto via SubViewportContainer ou TextureRect. No final tem uma alternativa bem mais leve, sem renderizar nada duas vezes, usando marcadores posicionados por proporção. Todo código é GDScript do Godot 4.x.

Como funciona um minimapa godot com SubViewport

No Godot, tudo que aparece na tela passa por um Viewport. A janela do jogo é um, e você pode criar outros com o node SubViewport. Cada viewport renderiza um mundo 2D, e o detalhe que destrava o minimapa é este: dois viewports podem compartilhar o mesmo World2D. Ou seja, o SubViewport do minimapa não precisa de uma cópia da fase, ele enxerga exatamente a mesma cena que o jogador está vendo, só que por outra câmera.

E cada viewport tem a sua câmera ativa, independente das demais. Então a câmera principal segue o player de perto, e a câmera do minimapa segue o mesmo player de longe, com zoom out. Se você ainda não tem a câmera principal funcionando, vale resolver isso antes: escrevi um guia de Camera2D seguindo o player no Godot que cobre essa parte.

A estrutura que vamos montar, dentro de um CanvasLayer pra UI:

HUD (CanvasLayer)
└── MinimapContainer (SubViewportContainer)
    └── MinimapViewport (SubViewport)
        └── MinimapCamera (Camera2D)

Montando o SubViewport e a câmera secundária

Crie o SubViewportContainer, posicione no canto que preferir (eu uso o topo direito, com as âncoras do Control) e defina um tamanho, algo como 200 por 200 pixels. Dentro dele, o SubViewport herda o tamanho do container automaticamente se a propriedade stretch do container estiver ligada, então ligue.

Agora o script do container, que faz as duas amarrações importantes: compartilhar o mundo e seguir o player.

extends SubViewportContainer

@onready var minimap_viewport = $MinimapViewport
@onready var minimap_camera = $MinimapViewport/MinimapCamera

@export var player_path: NodePath
@export var minimap_zoom := 0.08

var player: Node2D

func _ready():
    # O minimapa renderiza o MESMO mundo da cena principal.
    minimap_viewport.world_2d = get_viewport().world_2d
    minimap_camera.zoom = Vector2(minimap_zoom, minimap_zoom)
    player = get_node(player_path)

func _process(delta):
    if is_instance_valid(player):
        minimap_camera.global_position = player.global_position

Dois pontos que merecem atenção aqui. Primeiro, o world_2d = get_viewport().world_2d é a linha que faz tudo funcionar: sem ela, o SubViewport renderiza um mundo vazio e o minimapa fica preto. Segundo, no Godot 4 o zoom da Camera2D funciona ao contrário do que muita gente espera: valores menores que 1 afastam a câmera. Um zoom de 0.08 mostra uma área 12,5 vezes maior que a câmera normal, que é exatamente o que um minimapa quer. Ajuste esse número até o recorte do mapa fazer sentido pro seu jogo.

Com isso rodando, você já tem um minimapa funcional: o mundo inteiro em miniatura, centrado no player, atualizado em tempo real. Inimigos se movendo, projéteis, portas abrindo, tudo aparece, porque é literalmente a mesma cena renderizada de novo.

SubViewportContainer ou TextureRect?

O SubViewportContainer é o caminho direto: ele desenha o conteúdo do SubViewport filho sozinho, sem código extra. Pra maioria dos casos, fica nele e segue o jogo.

O TextureRect entra quando você quer mais controle visual. O SubViewport expõe a renderização como textura via get_texture(), e qualquer node que aceite textura pode exibir essa imagem. Isso abre duas portas: aplicar shader no minimapa (o clássico recorte circular) e posicionar o SubViewport em qualquer lugar da árvore, desacoplado da UI.

extends TextureRect

@onready var minimap_viewport = $"../MinimapViewport"

func _ready():
    texture = minimap_viewport.get_texture()

Nesse arranjo o SubViewport deixa de ser filho de um container, então defina o size dele manualmente no Inspector. Pro recorte circular, um shader de duas linhas no TextureRect resolve:

shader_type canvas_item;

void fragment() {
    float dist = distance(UV, vec2(0.5));
    COLOR.a *= step(dist, 0.5);
}

Minha regra de bolso: container pra minimapa retangular simples, TextureRect quando o visual pede shader ou formato. E independente da escolha, vale pensar o minimapa como parte do conjunto da interface, não como enfeite solto: tamanho, posição e contraste seguem os mesmos princípios que discuti no artigo sobre UI e UX em jogos.

Próximo nível
Quer aprender isso na prática?

No CursoGame.Dev você sai dos tutoriais soltos e constrói jogos publicáveis, com trilha progressiva, quests práticas e feedback real.

Conhecer a plataforma
+500 alunos4.9/5Garantia 7 dias

Escondendo o que não pertence ao minimapa

Renderizar tudo de novo tem um efeito colateral: partículas, texto de dano e detalhes pequenos viram ruído numa visão tão afastada. A solução do Godot é o sistema de camadas de cull do canvas, via visibility_layer dos nodes e canvas_cull_mask do viewport.

Funciona assim: todo CanvasItem tem uma visibility_layer (padrão: camada 1), e cada viewport só desenha as camadas marcadas na sua canvas_cull_mask. Então a receita é:

  1. Deixe o cenário e os elementos que importam na camada 1.
  2. Coloque o que não deve aparecer no minimapa (partículas, labels de dano) só na camada 2.
  3. No SubViewport do minimapa, desmarque a camada 2 da canvas_cull_mask.

Dá pra fazer o inverso também: criar ícones grandes e chapados que existem só pro minimapa, numa camada 3 que apenas o SubViewport desenha. Um círculo vermelho de 64 pixels em cima de cada inimigo fica ilegível no jogo, mas perfeito na miniatura. É assim que jogos comerciais fazem os pontinhos do radar sem precisar de sprite detalhado.

# No _ready() do ícone que só existe pro minimapa:
func _ready():
    visibility_layer = 0
    set_visibility_layer_bit(2, true)  # apenas camada 3 (índice 2)

A alternativa leve: marcadores por proporção

O SubViewport renderiza a cena uma segunda vez, e isso tem custo. Em mapa grande, em jogo pra mobile, ou quando o minimapa só precisa mostrar posições (player, inimigos, objetivo), existe um caminho muito mais barato: um Control com uma imagem estática do mapa e marcadores posicionados por regra de três.

A matemática é uma conversão de espaço: a posição do mundo, normalizada pelos limites da fase, vira uma posição dentro do retângulo do minimapa.

extends Control

@export var world_rect := Rect2(Vector2.ZERO, Vector2(4000, 3000))
@export var player_path: NodePath

@onready var player_marker = $PlayerMarker

var player: Node2D

func _ready():
    player = get_node(player_path)

func _process(delta):
    if is_instance_valid(player):
        player_marker.position = world_to_minimap(player.global_position)

func world_to_minimap(world_pos: Vector2) -> Vector2:
    var normalized = (world_pos - world_rect.position) / world_rect.size
    return normalized * size

O world_rect é o retângulo da sua fase em coordenadas do mundo: onde ela começa e que tamanho tem. O size é o tamanho do próprio Control. Com isso, um player em 25% da largura da fase aparece em 25% da largura do minimapa. Pra rastrear vários inimigos, agrupe todos num grupo do Godot e itere:

func _process(delta):
    for marker in enemy_markers:
        marker.queue_free()
    enemy_markers.clear()

    for enemy in get_tree().get_nodes_in_group("enemies"):
        var marker = ColorRect.new()
        marker.color = Color.RED
        marker.size = Vector2(4, 4)
        marker.position = world_to_minimap(enemy.global_position)
        add_child(marker)
        enemy_markers.append(marker)

Em produção eu não recriaria os marcadores todo frame, manteria um pool e só atualizaria posições, mas a versão acima deixa a lógica nua. O fundo pode ser uma captura de tela da fase tratada no editor de imagem, ou até um TextureRect cinza, se o jogo só precisa do radar.

Quando usar qual? SubViewport quando o minimapa precisa refletir o mundo de verdade, com geometria e movimento. Marcadores por proporção quando bastam pontos sobre um fundo fixo, ou quando cada milissegundo de render importa.

Fechando

Um minimapa no Godot 2D é, no fundo, uma escolha entre duas ideias: renderizar o mundo de novo (SubViewport compartilhando world_2d, Camera2D com zoom out, exibição via container ou TextureRect) ou projetar posições num retângulo (marcadores por proporção). A primeira dá fidelidade total quase de graça em código; a segunda dá performance quase de graça em render.

Meu conselho prático: comece pelo SubViewport, que fica de pé em dez minutos, e só migre pra versão de marcadores se o profiler reclamar. E teste o zoom da câmera secundária com calma, porque é ele que define se o minimapa informa ou confunde. O resto é polimento: recorte circular, ícones por camada de visibilidade e uma borda bonita por cima.