Voltar para o Blog
Quest Log

UI Responsiva no Godot 4: Stretch Mode e Multiplas Resolucoes

Editor do Godot 4 mostrando uma interface de menu se ajustando a diferentes tamanhos de tela

Aprenda a montar ui responsiva godot com stretch mode canvas_items, ancoras, Containers e safe area mobile para o jogo funcionar em qualquer tela.

Voce terminou seu primeiro jogo, abriu no celular de um amigo e a interface ficou cortada, com botoes fora da tela e texto minusculo. Esse e o momento em que quase todo mundo descobre que fazer ui responsiva godot nao acontece sozinho. O Godot tem ferramentas boas para isso, mas elas so funcionam quando voce entende a ordem das coisas: primeiro o projeto inteiro precisa saber como esticar a tela, depois cada elemento precisa saber a que ponto da tela ele se prende.

UI Responsiva no Godot 4: Stretch Mode e Multiplas Resolucoes

Neste post a gente vai do ajuste global (Project Settings) ate o detalhe de cada no Control, passando por ancoras, presets, Containers e a safe area dos celulares com notch. Nada de teoria solta: cada parte vem com o porque e com codigo GDScript que voce pode colar no seu projeto.

Por que sua UI quebra em outras telas

A tela do seu jogo tem uma resolucao base, definida em Project Settings, normalmente algo como 1280x720. O problema e que ninguem joga so nessa resolucao. Tem monitor ultrawide, tem celular 19.5:9, tem tablet quase quadrado. Se voce nao disser ao Godot como reagir, ele vai simplesmente mostrar a area base e o resto fica imprevisivel.

Existem dois passos independentes que costumam ser confundidos. O primeiro e o stretch mode, que decide como o viewport inteiro se comporta quando a janela muda de tamanho. O segundo sao as ancoras e os Containers, que decidem como cada botao ou label se posiciona dentro desse viewport. Voce precisa dos dois. Stretch mode sozinho deixa a proporcao certa mas nao reorganiza nada; ancoras sozinhas sem stretch fazem os elementos pularem de jeito estranho quando a resolucao muda.

Configurando o Stretch Mode em Project Settings

Va em Project, Project Settings, aba General, e procure a secao Display, Window. Os campos que importam estao em Stretch.

O campo Mode tem tres opcoes praticas:

  • disabled: a UI usa pixels reais da janela. Cresce a janela, sobra espaco vazio. Quase nunca e o que voce quer para UI de jogo.
  • canvas_items: o Godot escala o conteudo mantendo a resolucao base como referencia logica. As coordenadas continuam sendo as da resolucao base (ex: 1280x720), e o engine estica visualmente. Esse e o modo certo para a maioria dos jogos 2D e para HUDs em 3D.
  • viewport: renderiza tudo na resolucao base e depois faz upscale da imagem inteira. Bom para pixel art que precisa de pixels perfeitos, ruim para UI nitida porque o texto tambem borra no upscale.

O campo Aspect e o que evita distorcao:

  • ignore: estica para preencher tudo, deforma o jogo. Evite.
  • keep: mantem a proporcao da resolucao base e adiciona barras pretas onde sobra. Seguro e previsivel.
  • keep_width e keep_height: mantem uma dimensao fixa e deixa a outra crescer. Util quando voce quer aproveitar a largura extra de um celular sem distorcer.
  • expand: mantem a proporcao mas, em vez de barras, revela mais area do jogo. Esse e o mais usado em UI responsiva de verdade, porque voce ganha espaco util.

Para a maioria dos projetos com interface, a combinacao que funciona bem e Mode em canvas_items e Aspect em expand. Voce define isso uma vez no editor, mas tambem da para mudar em runtime se precisar de um menu de opcoes que troque o comportamento:

extends Node

func _ready() -> void:
    # Define resolucao base logica e o modo de esticar.
    # Equivale a configurar Project Settings via codigo.
    get_window().content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
    get_window().content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
    get_window().content_scale_size = Vector2i(1280, 720)

Esse content_scale_size e a sua resolucao logica de referencia. Com expand, telas mais largas vao mostrar mais largura em vez de barras pretas, entao planeje seu fundo para nao deixar buraco.

Ancoras e o que elas realmente fazem

Todo no que herda de Control (Button, Label, Panel, etc) tem ancoras: quatro valores entre 0 e 1 que dizem a que ponto do retangulo pai cada borda do elemento se prende. Ancora 0 e o canto superior esquerdo do pai, ancora 1 e o canto inferior direito.

O ponto que confunde iniciante: ancora nao e o mesmo que posicao. A ancora define o ponto de referencia, e os offsets definem a distancia em pixels a partir dessa ancora. Se voce ancora um botao no canto inferior direito (ancoras 1,1) e da offset negativo, ele fica grudado naquele canto independentemente do tamanho da tela. Por isso ancoras resolvem a maior parte dos problemas de UI responsiva godot sem precisar de codigo.

Voce raramente vai mexer nos quatro valores na mao. O editor oferece os Anchor Presets, aquele botao no topo da viewport quando um Control esta selecionado. Os presets mais usados:

  • Center: centraliza o elemento no pai.
  • Full Rect: ancoras em 0,0 e 1,1, o elemento ocupa o pai inteiro. Use no no raiz da sua UI.
  • Top Wide, Bottom Wide: barras de topo ou rodape que esticam na largura. Otimo para HUD.
  • Cantos (Top Left, Bottom Right, etc): para botoes fixos em cantos, tipo pausar ou voltar.

Em codigo, da para aplicar um preset direto:

extends Control

@onready var botao_pausar: Button = $BotaoPausar
@onready var painel_hud: Panel = $PainelHud

func _ready() -> void:
    # Painel raiz ocupa a tela toda.
    set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
    # Botao de pausa grudado no canto superior direito.
    botao_pausar.set_anchors_and_offsets_preset(
        Control.PRESET_TOP_RIGHT,
        Control.PRESET_MODE_KEEP_SIZE
    )
    # Barra de HUD esticada no rodape.
    painel_hud.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_WIDE)

O PRESET_MODE_KEEP_SIZE mantem o tamanho atual do botao ao reposicionar, em vez de zerar os offsets. Isso evita que o botao encolha para o nada quando voce troca a ancora.

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

Containers: pare de posicionar na mao

Ancoras sao otimas para elementos fixos, mas se voce tem uma lista de botoes (Continuar, Opcoes, Sair) posicionar cada um com offset vira pesadelo na hora de adicionar ou remover item. Para isso existem os Containers. Um Container assume o controle do tamanho e da posicao dos filhos. Voce nao mexe mais em ancora nos filhos, o Container faz isso.

Os principais:

  • VBoxContainer e HBoxContainer: empilham os filhos na vertical ou na horizontal, com separacao configuravel.
  • CenterContainer: centraliza um unico filho.
  • MarginContainer: adiciona margem interna, util para respeitar bordas.
  • GridContainer: organiza em grade, bom para inventarios.

Um menu principal tipico junta CenterContainer com VBoxContainer. O CenterContainer mantem o bloco no meio em qualquer tela, e o VBox cuida do espacamento entre botoes. Se voce ainda nao montou um, vale ver o passo a passo de menu principal no Godot e depois aplicar a parte responsiva daqui em cima.

extends CenterContainer

# Cria botoes de menu dinamicamente dentro de um VBox.
# Estrutura: CenterContainer > VBoxContainer (este no e o CenterContainer raiz).

@onready var caixa: VBoxContainer = $VBoxContainer

func _ready() -> void:
    var opcoes: Array[String] = ["Continuar", "Opcoes", "Sair"]
    for texto in opcoes:
        var botao := Button.new()
        botao.text = texto
        botao.custom_minimum_size = Vector2(220, 56)
        botao.pressed.connect(_on_botao_pressionado.bind(texto))
        caixa.add_child(botao)

func _on_botao_pressionado(qual: String) -> void:
    print("Clicou em: ", qual)
    if qual == "Sair":
        get_tree().quit()

Repare em custom_minimum_size. Como o Container controla o tamanho, voce nao define largura e altura direto; voce sugere um minimo e deixa o Container e as flags de tamanho fazerem o resto. As flags size_flags_horizontal e size_flags_vertical (Fill, Expand) decidem se o filho ocupa o espaco extra. Sem entender essas flags, voce vai brigar com Containers a vida toda, entao reserve um tempo para testar Fill e Expand no editor.

Safe Area: o notch do celular

Em celular, parte da tela e ocupada por notch, camera frontal ou barra de gestos. Se voce gruda um botao no topo absoluto, ele pode ficar embaixo do notch e o jogador nao consegue tocar. A area visivel e segura chama-se safe area, e o Godot expoe ela via DisplayServer.

A ideia e ler o retangulo seguro e aplicar uma margem na sua UI raiz, normalmente atraves de um MarginContainer. Faca isso quando a UI inicia e tambem quando a tela gira:

extends MarginContainer

func _ready() -> void:
    _aplicar_safe_area()
    # Reaplica quando a janela muda de tamanho ou gira.
    get_tree().get_root().size_changed.connect(_aplicar_safe_area)

func _aplicar_safe_area() -> void:
    var area: Rect2i = DisplayServer.get_display_safe_area()
    var tela: Vector2i = DisplayServer.screen_get_size()

    # Calcula quanto cada lado precisa recuar para fugir do notch.
    var topo := area.position.y
    var esquerda := area.position.x
    var direita := tela.x - (area.position.x + area.size.x)
    var baixo := tela.y - (area.position.y + area.size.y)

    add_theme_constant_override("margin_top", topo)
    add_theme_constant_override("margin_left", esquerda)
    add_theme_constant_override("margin_right", direita)
    add_theme_constant_override("margin_bottom", baixo)

Uma observacao honesta: a safe area so retorna valores diferentes de zero em dispositivos que de fato tem area ocupada, e principalmente no Android e iOS reais. No editor do desktop ela costuma vir como a tela inteira, entao nao adianta tentar validar isso na janela do PC. Voce vai precisar testar no aparelho. Se ainda nao publicou para celular, o caminho esta em exportar para Android.

Texto e fontes que escalam junto

Tem um detalhe que pega muita gente: com canvas_items o texto escala junto com a UI, o que e bom. Mas se voce usa viewport para pixel art, o texto vai borrar no upscale. A regra pratica e: jogo de UI com texto nitido fica melhor em canvas_items; jogo de pixel art que aceita texto pixelado pode usar viewport.

Para fontes, prefira fontes dinamicas (formato .ttf ou .otf importadas como FontFile) em vez de bitmap fixo, porque elas reescalam sem perder qualidade. E evite cravar font_size minusculo achando que vai aumentar via escala; defina um tamanho legivel na resolucao base e deixe o stretch cuidar do resto.

extends Label

func _ready() -> void:
    # Garante um tamanho de fonte legivel na resolucao base.
    # O stretch canvas_items escala isso proporcionalmente.
    add_theme_font_size_override("font_size", 28)
    autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
    # Em telas estreitas, quebra a linha em vez de cortar.

O autowrap_mode evita que um texto longo estoure a largura em telas estreitas. Combinado com um Container que limita a largura, voce tem um label que se adapta sem virar uma linha unica gigante.

Testando sem precisar de mil aparelhos

Voce nao precisa de uma gaveta de celulares para validar. No editor, com a cena de UI aberta, use o seletor de tamanho da viewport no canto superior e teste algumas proporcoes comuns. Em runtime, da para forcar tamanhos de janela e ate proporcoes extremas para ver onde a UI quebra:

extends Node

# Testa rapidamente algumas resolucoes apertando teclas 1, 2, 3.
func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventKey and event.pressed:
        match event.keycode:
            KEY_1:
                get_window().size = Vector2i(1280, 720)
            KEY_2:
                get_window().size = Vector2i(1080, 2400) # celular vertical
            KEY_3:
                get_window().size = Vector2i(2560, 1080) # ultrawide

Rode isso, aperte as teclas e observe: os botoes fixos continuam nos cantos? A lista central continua centralizada? O texto quebra em vez de cortar? Se passar nesses tres, sua UI esta solida. Vale fazer o mesmo teste depois de exportar para web HTML5, porque a janela do navegador muda de tamanho o tempo todo e expoe problemas que o desktop esconde.

Fechando o ciclo

UI responsiva no Godot 4 nao depende de truque, depende de ordem. Primeiro voce define o stretch mode no projeto, normalmente canvas_items com aspect expand, para o viewport inteiro reagir a janela. Depois voce escolhe entre ancoras (para elementos fixos em cantos e barras) e Containers (para listas e blocos que se reorganizam sozinhos). Por ultimo, no mobile, voce respeita a safe area com um MarginContainer alimentado pelo DisplayServer. Cada camada resolve um problema diferente, e juntas elas fazem o mesmo layout funcionar do celular vertical ao monitor ultrawide. Comece simples, teste em proporcoes extremas cedo, e ajuste antes de a UI virar uma teia de offsets na mao.