Voltar para o Blog
Quest Log

Configurações de Vídeo no Godot 4: Resolução, Tela Cheia e VSync

Tela de configurações de vídeo de um jogo com opções de resolução, tela cheia e sincronização vertical em uma lista lateral

Monte uma tela de configurações de vídeo no Godot 4: troca de resolução, tela cheia, VSync e content scale sem distorcer a UI, com GDScript tipado.

Configurações de Vídeo no Godot 4: Resolução, Tela Cheia e VSync

Toda tela de opções de jogo tem uma aba de vídeo, e ela é enganosamente simples de descrever: um botão de tela cheia, uma lista de resoluções, um interruptor de VSync. Só que na hora de montar isso no Godot 4 batem as dúvidas de sempre. Qual a diferença entre os modos de janela? Por que a UI distorce quando troco a resolução? O VSync fica em qual API? Este tutorial monta uma tela de configurações de vídeo no Godot 4 completa, cobrindo troca de resolução, tela cheia em todas as variantes e VSync, com uma UI de opções de verdade e todo o GDScript estaticamente tipado.

A boa notícia é que quase tudo aqui passa por uma única classe, o DisplayServer, mais os enums da classe Window. Depois que você entende esses dois nomes, o resto é ligar um OptionButton e uns CheckButton na lógica. Vamos por partes.

O DisplayServer e os modos de janela

No Godot 4, quem controla a janela do sistema operacional é o singleton DisplayServer. Ele é global, você chama de qualquer lugar, e é por ele que se troca modo de janela, tamanho, posição e VSync. A propriedade central é o modo da janela, que aceita os valores do enum Window:

func aplicar_modo_janela(modo: Window.Mode) -> void:
    DisplayServer.window_set_mode(modo)

Os modos que interessam numa tela de vídeo são quatro:

  • Window.MODE_WINDOWED: janela normal, com borda e barra de título, que o jogador pode mover e redimensionar.
  • Window.MODE_FULLSCREEN: tela cheia "borderless", uma janela sem borda do tamanho do monitor mantendo a resolução do desktop. O alt-tab é instantâneo. É o padrão que a maioria dos jogos deveria usar.
  • Window.MODE_EXCLUSIVE_FULLSCREEN: tela cheia exclusiva. O jogo assume o monitor e pode trocar o modo de vídeo. Às vezes reduz a latência, mas o alt-tab fica mais lento.
  • Window.MODE_MINIMIZED e Window.MODE_MAXIMIZED: existem, mas raramente entram numa aba de vídeo.

Para o modo sem borda (borderless) em janela, o caminho é um pouco diferente: você fica em MODE_WINDOWED e liga a flag de "sem borda" separadamente. Uma função que cobre os quatro casos práticos fica assim:

func trocar_janela(indice: int) -> void:
    match indice:
        0:  # Janela
            DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
            DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, false)
        1:  # Janela sem borda
            DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
            DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true)
        2:  # Tela cheia
            DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
        3:  # Tela cheia exclusiva
            DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN)

Repare em um detalhe que confunde muita gente: o DisplayServer usa constantes com o prefixo WINDOW_MODE_ (por exemplo, DisplayServer.WINDOW_MODE_FULLSCREEN), enquanto a classe Window usa MODE_ (por exemplo, Window.MODE_FULLSCREEN). Os dois se referem à mesma coisa e têm os mesmos valores numéricos. Se você tiver a referência da janela em mãos, dá pra trocar o modo por ela também:

func tela_cheia_pela_window(ligada: bool) -> void:
    var janela: Window = get_window()
    janela.mode = Window.MODE_FULLSCREEN if ligada else Window.MODE_WINDOWED

Use o que for mais legível no seu contexto. Eu prefiro o DisplayServer na tela de opções porque ela costuma ser um autoload ou um node solto, e nem sempre get_window() aponta pra janela principal.

Resolução: window_set_size e recentralizar

Trocar a resolução da janela é uma chamada só, window_set_size, que recebe um Vector2i:

func aplicar_resolucao(resolucao: Vector2i) -> void:
    DisplayServer.window_set_size(resolucao)

O problema prático é que, depois de redimensionar, a janela costuma "escorregar" pro canto da tela. A correção é recalcular a posição pra centralizar no monitor onde a janela está. O DisplayServer te dá o tamanho da tela útil (descontando barra de tarefas), então a conta é direta:

func centralizar_janela() -> void:
    var id_tela: int = DisplayServer.window_get_current_screen()
    var tamanho_tela: Vector2i = DisplayServer.screen_get_size(id_tela)
    var tamanho_janela: Vector2i = DisplayServer.window_get_size()
    var pos: Vector2i = (tamanho_tela - tamanho_janela) / 2
    pos += DisplayServer.screen_get_position(id_tela)
    DisplayServer.window_set_position(pos)

Uma observação importante: trocar resolução só faz sentido no modo janela. Em tela cheia, a janela já ocupa o monitor inteiro, e é o content scale (o Stretch, que vou explicar já já) que decide como a arte se encaixa ali. Então na hora de aplicar, o padrão é: se estiver em tela cheia, aplico o modo e ignoro o window_set_size; se estiver em janela, aplico o tamanho e recentralizo.

Content scale: por que a UI não pode distorcer

Aqui está o detalhe que separa uma tela de opções amadora de uma profissional. Se você trocar a resolução e a interface esticar ou achatar, o problema não está no seu código de vídeo, está no Stretch do projeto.

O Godot renderiza o jogo numa resolução "base" (a que você define em Project Settings, Display, Window, Viewport Width e Height) e depois escala isso pra janela real. Como ele escala é decidido por duas opções na mesma seção, dentro de Stretch:

  • Mode: deixe em canvas_items. Assim o Godot escala os elementos de UI e sprites de forma limpa, sem pixelar as fontes. O modo viewport renderiza numa resolução fixa e faz upscale bruto (bom pra pixel art), e o disabled não escala nada (é o que causa a distorção clássica).
  • Aspect: deixe em keep. Isso mantém a proporção da resolução base. Se a janela tem outra proporção, o Godot adiciona barras pretas (letterbox) em vez de deformar a imagem. As alternativas expand, keep_width e keep_height servem pra casos específicos, mas keep é o que garante que nada distorça.

Com canvas_items + keep configurados, você troca a resolução à vontade que a UI acompanha proporcionalmente. O seu código GDScript não precisa fazer nada disso na mão: é configuração de projeto, aplicada uma vez, e vale pra qualquer resolução que o jogador escolher. Vale a mesma lógica de quando você monta o menu de pause do jogo, onde a UI também precisa se manter íntegra em qualquer tamanho de tela.

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

VSync: window_set_vsync_mode

O VSync (sincronização vertical) alinha a entrega dos frames à taxa de atualização do monitor, o que elimina o "tearing", aquela linha horizontal de imagem cortada em movimentos rápidos. O custo é limitar o FPS e adicionar um pouco de latência de input. É clássico oferecer como opção, e no Godot 4 mora no DisplayServer:

func aplicar_vsync(ligado: bool) -> void:
    var modo: DisplayServer.VSyncMode = DisplayServer.VSYNC_ENABLED if ligado else DisplayServer.VSYNC_DISABLED
    DisplayServer.window_set_vsync_mode(modo)

Existem quatro modos no enum DisplayServer.VSyncMode:

  • VSYNC_DISABLED: desligado. FPS livre, com tearing possível.
  • VSYNC_ENABLED: ligado, o padrão. Sem tearing, FPS travado na taxa do monitor.
  • VSYNC_ADAPTIVE: liga quando o FPS está acima da taxa e desliga quando cai abaixo, reduzindo o "stutter" em quedas.
  • VSYNC_MAILBOX: renderiza mais rápido que o monitor e mostra sempre o frame mais recente. Menos latência, mais uso de GPU.

Para uma tela de opções, oferecer só ligado/desligado com um CheckButton já cobre 90% dos casos.

A UI de opções

Agora juntamos tudo numa cena. A ideia é um Control com três controles principais: um OptionButton pra lista de resoluções, um CheckButton pra tela cheia e outro pra VSync. A estrutura fica assim:

VideoOptions (Control)
└── VBoxContainer
    ├── ResolucaoOption (OptionButton)
    ├── TelaCheiaCheck (CheckButton, "Tela cheia")
    ├── VSyncCheck (CheckButton, "VSync")
    └── AplicarButton (Button, "Aplicar")

O script começa declarando as resoluções suportadas como uma lista tipada de Vector2i e populando o OptionButton. Prefira @onready tipado pra pegar os nodes:

extends Control

const RESOLUCOES: Array[Vector2i] = [
    Vector2i(1280, 720),
    Vector2i(1600, 900),
    Vector2i(1920, 1080),
    Vector2i(2560, 1440),
]

@onready var resolucao_option: OptionButton = $VBoxContainer/ResolucaoOption
@onready var tela_cheia_check: CheckButton = $VBoxContainer/TelaCheiaCheck
@onready var vsync_check: CheckButton = $VBoxContainer/VSyncCheck

func _ready() -> void:
    _popular_resolucoes()
    _sincronizar_com_estado_atual()

func _popular_resolucoes() -> void:
    resolucao_option.clear()
    for res: Vector2i in RESOLUCOES:
        resolucao_option.add_item("%d x %d" % [res.x, res.y])

O _sincronizar_com_estado_atual lê a janela na hora que a tela abre e marca os controles conforme o que já está valendo, pra UI não mentir pro jogador:

func _sincronizar_com_estado_atual() -> void:
    var modo_atual: int = DisplayServer.window_get_mode()
    var em_tela_cheia: bool = (
        modo_atual == DisplayServer.WINDOW_MODE_FULLSCREEN
        or modo_atual == DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN
    )
    tela_cheia_check.button_pressed = em_tela_cheia

    var vsync_atual: int = DisplayServer.window_get_vsync_mode()
    vsync_check.button_pressed = (vsync_atual != DisplayServer.VSYNC_DISABLED)

    var tamanho_atual: Vector2i = DisplayServer.window_get_size()
    var indice: int = RESOLUCOES.find(tamanho_atual)
    if indice != -1:
        resolucao_option.select(indice)

Com os controles no lugar, falta a função que pega o estado da UI e aplica de fato. Ela concentra a decisão que comentei antes: em tela cheia, aplica o modo; em janela, aplica tamanho e recentraliza. E aplica o VSync sempre.

func _on_aplicar_button_pressed() -> void:
    _aplicar_configuracoes()

func _aplicar_configuracoes() -> void:
    var em_tela_cheia: bool = tela_cheia_check.button_pressed
    var vsync_ligado: bool = vsync_check.button_pressed
    var indice_res: int = resolucao_option.selected
    var resolucao: Vector2i = RESOLUCOES[indice_res]

    # VSync vale pros dois modos
    var modo_vsync: DisplayServer.VSyncMode = (
        DisplayServer.VSYNC_ENABLED if vsync_ligado else DisplayServer.VSYNC_DISABLED
    )
    DisplayServer.window_set_vsync_mode(modo_vsync)

    if em_tela_cheia:
        DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
    else:
        DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
        DisplayServer.window_set_size(resolucao)
        _centralizar_janela()

Não esqueça de ligar os sinais no editor (ou por código) do AplicarButton.pressed pra _on_aplicar_button_pressed. Se quiser aplicar na hora, sem botão "Aplicar", conecte também o item_selected do OptionButton e o toggled de cada CheckButton às mesmas funções. A função _centralizar_janela é a mesma que mostrei lá em cima, só copiada pra dentro deste script.

Persistindo as escolhas

Aqui vem o ponto que pega muita gente de surpresa: nada disso é salvo. Quando o jogador fecha o jogo, o DisplayServer esquece tudo, porque ele só mexe na janela da sessão atual. Na próxima abertura, o jogo volta pro que está definido no Project Settings.

A solução é gravar as três escolhas (modo de janela, resolução e VSync) num arquivo quando o jogador aplica, e ler esse arquivo logo no início do jogo pra reaplicar. O caminho padrão no Godot pra isso é o ConfigFile, que grava um .cfg legível em user://. O fluxo é: no _aplicar_configuracoes, além de mexer no DisplayServer, você chama uma função que salva os valores; e num autoload de boot, você lê e reaplica antes da primeira cena aparecer. Montei esse lado do sistema em detalhe em como salvar as opções do jogador com ConfigFile, então aqui deixo só o gancho: separe bem a camada que "aplica na janela" da que "grava em disco", porque são responsabilidades diferentes e você vai reusar a de aplicar no boot.

Um esqueleto mínimo da parte de salvar, pra fechar o raciocínio:

func salvar_video() -> void:
    var config: ConfigFile = ConfigFile.new()
    config.set_value("video", "tela_cheia", tela_cheia_check.button_pressed)
    config.set_value("video", "vsync", vsync_check.button_pressed)
    config.set_value("video", "resolucao", RESOLUCOES[resolucao_option.selected])
    config.save("user://opcoes.cfg")

No boot, você faz o caminho inverso: config.load("user://opcoes.cfg"), lê cada get_value com um valor padrão e chama as mesmas funções de aplicação. Assim a tela de vídeo e o carregamento inicial compartilham a lógica.

Fechando

Uma tela de configurações de vídeo no Godot 4 se resume a três ferramentas: DisplayServer.window_set_mode com os enums de Window pra alternar janela e tela cheia, window_set_size mais o recentralizar pra resolução, e window_set_vsync_mode pro VSync. O Stretch em canvas_items + keep garante que a UI não distorça, e o ConfigFile fecha o ciclo persistindo tudo. Note que nenhuma dessas peças é complicada isolada; o trabalho é conectá-las com estados coerentes e uma UI que reflete o que está valendo.

Se você está montando o menu de opções do zero, faz sentido ter a base de UI e sinais bem firme antes. Caso ainda esteja começando, vale seguir uma trilha de Godot para iniciantes pra não ficar tropeçando em Control nodes no meio do caminho. Com a fundação no lugar, essa tela de vídeo vira quase um exercício de encaixe.

Perguntas frequentes

Como colocar o jogo em tela cheia no Godot 4?

Use DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) ou, quando você tem a referência da janela, atribua get_window().mode = Window.MODE_FULLSCREEN. Para tela cheia exclusiva (que troca o modo de vídeo do monitor), use WINDOW_MODE_EXCLUSIVE_FULLSCREEN. Voltar pro modo janela é WINDOW_MODE_WINDOWED.

Como trocar a resolução da janela no Godot 4?

A resolução da janela se troca com DisplayServer.window_set_size(Vector2i(largura, altura)). Depois vale recentralizar a janela com window_set_position. Para a arte do jogo escalar junto sem distorcer, configure o Stretch em Project Settings, Display, Window com o modo canvas_items e aspect keep.

Qual a diferença entre tela cheia e tela cheia exclusiva no Godot?

A tela cheia normal (WINDOW_MODE_FULLSCREEN) é uma janela sem borda do tamanho do monitor, mantendo a resolução do desktop, e o alt-tab é instantâneo. A exclusiva (WINDOW_MODE_EXCLUSIVE_FULLSCREEN) assume o controle do monitor e pode trocar o modo de vídeo, o que às vezes reduz a latência mas deixa o alt-tab mais lento.

Como ligar e desligar o VSync no Godot 4?

Use DisplayServer.window_set_vsync_mode com DisplayServer.VSYNC_ENABLED para ligar e DisplayServer.VSYNC_DISABLED para desligar. Existem ainda VSYNC_ADAPTIVE e VSYNC_MAILBOX para casos específicos. O VSync elimina o tearing na tela ao custo de limitar o FPS à taxa de atualização do monitor.

Por que a interface distorce quando eu troco a resolução no Godot?

Isso acontece quando o modo de Stretch está em disabled ou o aspect está em ignore, fazendo a UI esticar junto com a janela. A correção é ir em Project Settings, Display, Window, Stretch e usar o modo canvas_items com aspect keep, assim o conteúdo escala proporcionalmente com barras pretas em vez de deformar.

As configurações de vídeo do Godot são salvas sozinhas?

Não. Trocar o modo da janela ou a resolução em tempo de execução não persiste nada em disco. Você precisa gravar as escolhas do jogador em um arquivo, tipicamente com ConfigFile, e ler esse arquivo no início do jogo para reaplicar tudo. O DisplayServer só mexe na janela da sessão atual.