Menu de opções na Godot 4: áudio, tela e save

Como criar um menu de opções godot na Godot 4: sliders de volume por bus de áudio, resolução, tela cheia e salvar as preferências com ConfigFile.
Menu de opções na Godot 4: áudio, tela e como salvar tudo
Todo jogo que se leva a sério tem uma tela de configurações, e montar um menu de opções godot do zero é mais simples do que parece quando você separa bem as três partes: a interface, a lógica que aplica cada ajuste e o código que salva as preferências em disco. Neste tutorial da CursoGame.Dev você vai construir uma tela de Opções na Godot 4 com sliders de volume por categoria de áudio, seleção de resolução, alternância entre tela cheia e janela, e um sistema de save que lembra tudo na próxima vez que o jogador abrir o jogo.
Aqui o foco é a tela de Settings especificamente. Se você ainda não tem a porta de entrada do jogo, vale primeiro montar o menu principal na Godot e depois pendurar o botão de Opções nele. O menu de opções costuma ser aberto tanto pelo menu principal quanto pelo menu de pausa, então pensar nele como uma cena independente e reaproveitável economiza muito trabalho.
Montando a cena da tela de opções
Comece com uma cena nova cujo nó raiz seja um Control. Dentro dele, um VBoxContainer organiza os controles em coluna sem você posicionar nada na mão. A estrutura mínima fica assim:
Control(raiz, com o scriptopcoes.gd)VBoxContainerLabel(título "Opções")HSliderchamadoSliderMasterHSliderchamadoSliderMusicHSliderchamadoSliderSFXCheckButtonchamadoCheckTelaCheiaOptionButtonchamadoOptionResolucaoButtonchamadoBotaoVoltar
Nos três HSlider, ajuste no Inspector os campos Min Value para 0, Max Value para 1 e Step para 0.01. Isso deixa o slider num intervalo linear confortável, e a conversão para decibéis fica por conta do código. Deixe também Tick Count em algo como 5 se quiser marcações visuais.
Antes de escrever qualquer script de áudio, abra o painel Audio no rodapé do editor e crie dois buses novos além do Master: um chamado Music e outro SFX. Cada AudioStreamPlayer do seu jogo aponta, na propriedade Bus, para a categoria certa. Assim o slider de música mexe só na música e o de efeitos só nos efeitos.
Ligando os sliders de volume ao AudioServer
O ponto que confunde muita gente: o AudioServer da Godot trabalha em decibéis, não em porcentagem. Um slider de 0 a 1 é intuitivo para o jogador, mas 0.5 não é "metade do volume" em decibéis. A ponte entre os dois mundos são as funções linear_to_db() e db_to_linear().
Crie o script opcoes.gd no nó raiz e comece pegando as referências tipadas dos nós:
extends Control
@onready var slider_master: HSlider = $VBoxContainer/SliderMaster
@onready var slider_music: HSlider = $VBoxContainer/SliderMusic
@onready var slider_sfx: HSlider = $VBoxContainer/SliderSFX
@onready var check_tela_cheia: CheckButton = $VBoxContainer/CheckTelaCheia
@onready var option_resolucao: OptionButton = $VBoxContainer/OptionResolucao
var idx_master: int = AudioServer.get_bus_index("Master")
var idx_music: int = AudioServer.get_bus_index("Music")
var idx_sfx: int = AudioServer.get_bus_index("SFX")
Agora a função que aplica o volume de um slider a um bus. Ela recebe o valor linear (0 a 1) e o índice do bus:
func aplicar_volume(indice_bus: int, valor_linear: float) -> void:
var db: float = linear_to_db(valor_linear)
AudioServer.set_bus_volume_db(indice_bus, db)
# Se o jogador arrastar o slider até 0, silencie o bus de vez
AudioServer.set_bus_mute(indice_bus, valor_linear < 0.001)
Conecte o sinal value_changed de cada slider a uma função. Você pode fazer isso pelo editor ou no código, dentro do _ready():
func _ready() -> void:
slider_master.value_changed.connect(_on_master_changed)
slider_music.value_changed.connect(_on_music_changed)
slider_sfx.value_changed.connect(_on_sfx_changed)
carregar_configuracoes()
func _on_master_changed(valor: float) -> void:
aplicar_volume(idx_master, valor)
func _on_music_changed(valor: float) -> void:
aplicar_volume(idx_music, valor)
func _on_sfx_changed(valor: float) -> void:
aplicar_volume(idx_sfx, valor)
Repare que a chamada carregar_configuracoes() já aparece no _ready(). Vamos escrever essa função mais para frente, mas a ideia é ela preencher os sliders com o que estava salvo, e como conectamos os sinais antes, cada set_value já dispara a aplicação real no AudioServer.
Resolução e tela cheia com o DisplayServer
Na Godot 4, quem cuida da janela é o DisplayServer. Para a tela cheia, o modo que a maioria dos jogos quer é o WINDOW_MODE_FULLSCREEN, que ocupa o monitor inteiro. Ligue o CheckButton assim:
func _ready() -> void:
# ... (conexões anteriores) ...
check_tela_cheia.toggled.connect(_on_tela_cheia_toggled)
popular_resolucoes()
func _on_tela_cheia_toggled(ligado: bool) -> void:
if ligado:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
else:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
Para a resolução, preencha o OptionButton com algumas opções comuns e guarde cada uma como um Vector2i. A função abaixo popula a lista e conecta a troca:
var resolucoes: Array[Vector2i] = [
Vector2i(1280, 720),
Vector2i(1600, 900),
Vector2i(1920, 1080),
]
func popular_resolucoes() -> void:
option_resolucao.clear()
for res in resolucoes:
option_resolucao.add_item("%d x %d" % [res.x, res.y])
option_resolucao.item_selected.connect(_on_resolucao_selecionada)
func _on_resolucao_selecionada(indice: int) -> void:
var res: Vector2i = resolucoes[indice]
DisplayServer.window_set_size(res)
# Centraliza a janela no monitor depois de redimensionar
var tela: Vector2i = DisplayServer.screen_get_size()
DisplayServer.window_set_position((tela - res) / 2)
Um detalhe importante para a interface não ficar borrada ou minúscula quando a resolução muda: em Project Settings, na aba Display > Window, configure o Stretch Mode como canvas_items e o Aspect como expand. Com isso a Godot escala toda a UI junto com a janela, e você não precisa reposicionar nada manualmente.
Se estas configurações vão ser lidas por várias cenas (o HUD do jogo, a tela de pausa, o próprio jogo), vale centralizar o gerenciamento num singleton Autoload na Godot. Assim qualquer parte do jogo pergunta "qual o volume atual da música?" sem depender de a tela de opções estar aberta.
Salvando as preferências com ConfigFile
De nada adianta o jogador ajustar tudo e perder na hora que fecha o jogo. A Godot tem uma classe feita exatamente para isso: ConfigFile. Ela grava um arquivo texto em formato de seções e chaves, parecido com um .ini, e é perfeita para preferências. Salve sempre em user://, que é uma pasta do sistema separada do projeto e que continua funcionando no jogo exportado.
Defina o caminho como constante e escreva a função de salvar:
const CAMINHO_CONFIG: String = "user://settings.cfg"
func salvar_configuracoes() -> void:
var config := ConfigFile.new()
config.set_value("audio", "master", slider_master.value)
config.set_value("audio", "music", slider_music.value)
config.set_value("audio", "sfx", slider_sfx.value)
config.set_value("video", "tela_cheia", check_tela_cheia.button_pressed)
config.set_value("video", "resolucao_idx", option_resolucao.selected)
config.save(CAMINHO_CONFIG)
A leitura é o espelho disso. Note que, quando o arquivo ainda não existe (primeira vez que o jogador roda o jogo), usamos valores padrão sensatos no terceiro argumento do get_value:
func carregar_configuracoes() -> void:
var config := ConfigFile.new()
var erro: int = config.load(CAMINHO_CONFIG)
if erro != OK:
# Ainda nao ha arquivo salvo; usa os padroes dos sliders
return
slider_master.value = config.get_value("audio", "master", 1.0)
slider_music.value = config.get_value("audio", "music", 1.0)
slider_sfx.value = config.get_value("audio", "sfx", 1.0)
var tela_cheia: bool = config.get_value("video", "tela_cheia", false)
check_tela_cheia.button_pressed = tela_cheia
_on_tela_cheia_toggled(tela_cheia)
var res_idx: int = config.get_value("video", "resolucao_idx", 2)
option_resolucao.select(res_idx)
_on_resolucao_selecionada(res_idx)
Como conectamos os sinais dos sliders antes de chamar carregar_configuracoes(), atribuir slider_master.value já dispara _on_master_changed, que por sua vez aplica o volume no AudioServer. Para o CheckButton e o OptionButton, definir .button_pressed e .select() por código não dispara os sinais automaticamente, por isso chamamos as funções na mão logo abaixo.
Falta decidir quando salvar. A abordagem mais segura é salvar quando o jogador sai da tela, no botão Voltar:
func _on_botao_voltar_pressed() -> void:
salvar_configuracoes()
# Volte para o menu de origem (principal ou pausa)
get_tree().change_scene_to_file("res://cenas/menu_principal.tscn")
Essa lógica de gravar e ler um arquivo em user:// é a mesma base de um sistema de save e load na Godot para o progresso do jogo, só que ali você usa mais frequentemente JSON ou recursos para dados de partida. Para preferências simples, o ConfigFile é o caminho mais limpo.
Detalhes que separam um menu bom de um menu quebrado
Alguns cuidados que fazem diferença na prática:
- Aplique as configurações também na inicialização do jogo, não só quando a tela de opções abre. Se você usar um Autoload, chame o carregamento no
_ready()dele, para o jogo já abrir no volume e na resolução certos. - Cuidado com o
linear_to_db(0.0), que retorna menos infinito. Oset_bus_muteno valor muito baixo, como fizemos, evita ruídos e comportamento estranho quando o slider está no zero. - Teste o arquivo exportado, não só o editor. O
user://aponta para lugares diferentes no editor e no jogo empacotado, e é justamente aí que bugs de save costumam aparecer. - Reaproveite a cena. Como o menu de opções é chamado tanto pelo menu principal quanto pela pausa, guarde de onde ele foi aberto para o botão Voltar devolver o jogador ao lugar certo.
Próximo passo
Com áudio por bus, resolução, tela cheia e persistência com ConfigFile, você já tem um menu de opções completo e reutilizável na Godot 4. O próximo movimento natural é plugar essa cena no menu de pausa da Godot, para o jogador ajustar tudo sem sair da partida, e transformar o gerenciador de configurações num Autoload para o jogo inteiro respeitar as preferências.
Se você quer sair de tutoriais isolados e montar um jogo completo com arquitetura de verdade, ganho de tempo e menos retrabalho, vale aprofundar na Godot com um curso estruturado de Godot que conecta esses sistemas num projeto real do começo ao fim. Continue explorando os tutoriais da CursoGame.Dev e vá empilhando peças até o seu jogo ficar de pé.
Perguntas frequentes
Qual a diferença entre volume linear do slider e decibéis no AudioServer?
O slider trabalha bem de 0 a 1 (linear), mas o AudioServer espera decibéis. Você converte com linear_to_db(valor) na hora de aplicar e com db_to_linear(db) na hora de ler o valor salvo de volta para o slider.
Como faço os buses Master, Music e SFX aparecerem na Godot?
Abra o painel Audio na parte de baixo do editor, clique em Add Bus e renomeie. Cada AudioStreamPlayer aponta para um bus pela propriedade Bus. No código você acessa pelo índice com AudioServer.get_bus_index("Music").
Onde o ConfigFile salva o arquivo de preferências?
Use o caminho user:// (por exemplo user://settings.cfg). Na sua maquina isso vai para uma pasta do sistema, nao para a pasta do projeto, entao funciona igual no jogo exportado sem risco de sobrescrever nada.
Como mudo a resolução da janela sem mudar a resolução do render?
DisplayServer.window_set_size(Vector2i(largura, altura)) muda o tamanho da janela. Combine com o Stretch Mode em canvas_items no Project Settings para a interface escalar junto sem ficar borrada.
O menu de opções precisa ser um Autoload?
A tela em si nao, mas o gerenciador que aplica e guarda as configurações vale a pena ser um singleton Autoload, para que qualquer cena leia o volume ou a resolução atual sem depender da tela de opções estar aberta.


