Salvar Opcoes do Jogo com ConfigFile no Godot 4

Aprenda a salvar configuracoes godot com ConfigFile, gravar volume e resolucao em user://, ler por secao e aplicar tudo no boot via autoload de settings.
Quando o jogador abre seu menu de opcoes, ajusta o volume e troca a resolucao, ele espera que essas escolhas continuem la na proxima vez que abrir o jogo. Para isso voce precisa de uma forma confiavel de salvar configuracoes godot em disco e de carregar tudo de volta no inicio. O ConfigFile resolve exatamente esse problema: ele grava pares de chave e valor organizados por secao em um arquivo de texto simples, parecido com um .ini, e faz a leitura sem voce ter que escrever um parser na mao.
Salvar Opcoes do Jogo com ConfigFile no Godot 4
Opcoes de jogo nao sao save game. Um save game guarda o progresso (fase atual, inventario, posicao do personagem). As opcoes guardam preferencias da pessoa: volume da musica, idioma, modo tela cheia, resolucao. Por isso vale separar os dois sistemas. Se voce ainda nao tem um fluxo de progresso, veja o tutorial de salvar e carregar no Godot para entender a diferenca na pratica. Aqui o foco e so nas configuracoes.
Por que usar ConfigFile e nao um JSON qualquer
Voce poderia serializar um dicionario em JSON e gravar com FileAccess. Funciona. Mas o ConfigFile ja organiza os dados em secoes, faz o cast de tipos automaticamente (booleano volta como booleano, float como float) e aceita qualquer tipo nativo do Godot como valor, incluindo Vector2i e Color. Voce escreve menos codigo e o arquivo final fica legivel, o que ajuda muito na hora de depurar.
O arquivo gerado tem esta cara:
[audio]
master_volume=0.8
music_volume=0.5
[video]
fullscreen=true
resolution=Vector2i(1920, 1080)
Cada [secao] agrupa chaves relacionadas. Isso evita que um arquivo de opcoes vire uma lista plana de cinquenta chaves sem contexto.
Onde gravar o arquivo: o caminho user://
Nunca grave configuracoes dentro de res://. Esse caminho aponta para os arquivos do projeto e, depois que voce exporta o jogo, ele fica somente leitura (vira parte do pacote .pck). O lugar certo para dados que mudam em tempo de execucao e o user://.
O user:// aponta para uma pasta gravavel por usuario, fora do executavel. No Windows costuma cair em %APPDATA%\Godot\app_userdata\NomeDoProjeto, no Linux em ~/.local/share/godot/... e no macOS dentro de ~/Library/Application Support. Voce nao precisa decorar isso: o Godot resolve o caminho real sozinho. Voce so usa user://settings.cfg e pronto.
Lendo e escrevendo com set_value e get_value
A API gira em torno de tres metodos: set_value, get_value e save. Para ler de volta voce usa load e depois get_value. Veja um exemplo isolado antes de montar o sistema completo:
extends Node
func _exemplo() -> void:
var config := ConfigFile.new()
# Escreve valores por secao e chave
config.set_value("audio", "master_volume", 0.8)
config.set_value("video", "fullscreen", true)
config.set_value("video", "resolution", Vector2i(1920, 1080))
# Grava em disco
config.save("user://settings.cfg")
# Mais tarde, em outra execucao, carrega de volta
var leitor := ConfigFile.new()
var erro := leitor.load("user://settings.cfg")
if erro == OK:
var volume: float = leitor.get_value("audio", "master_volume", 1.0)
print(volume)
Repare no terceiro argumento de get_value: ele e o valor padrao retornado quando a chave nao existe. Isso e importante na primeira vez que o jogo roda, porque ainda nao ha arquivo. Sempre passe um padrao sensato em vez de deixar o jogo quebrar com um valor nulo.
Um autoload de Settings para centralizar tudo
Espalhar ConfigFile.new() por varios scripts e receita para bug. O ideal e ter um unico ponto de verdade. Crie um script de autoload (singleton) chamado Settings. Va em Project, Project Settings, aba Autoload, e registre o script com o nome Settings. Assim qualquer cena acessa as opcoes por Settings.master_volume sem precisar instanciar nada.
extends Node
const CAMINHO := "user://settings.cfg"
# Valores padrao, usados na primeira execucao
var master_volume: float = 1.0
var music_volume: float = 0.8
var fullscreen: bool = false
var resolution: Vector2i = Vector2i(1280, 720)
func _ready() -> void:
load_settings()
apply_all()
func load_settings() -> void:
var config := ConfigFile.new()
var erro := config.load(CAMINHO)
if erro != OK:
# Arquivo ainda nao existe: grava os padroes e segue
save_settings()
return
master_volume = config.get_value("audio", "master_volume", master_volume)
music_volume = config.get_value("audio", "music_volume", music_volume)
fullscreen = config.get_value("video", "fullscreen", fullscreen)
resolution = config.get_value("video", "resolution", resolution)
func save_settings() -> void:
var config := ConfigFile.new()
config.set_value("audio", "master_volume", master_volume)
config.set_value("audio", "music_volume", music_volume)
config.set_value("video", "fullscreen", fullscreen)
config.set_value("video", "resolution", resolution)
config.save(CAMINHO)
Note que os padroes do get_value sao as proprias variaveis ja preenchidas. Se a chave existir, o valor do disco substitui o padrao. Se nao existir (por exemplo, voce adicionou uma opcao nova numa versao posterior do jogo), a variavel mantem o padrao e nada quebra. Esse detalhe deixa seu sistema tolerante a arquivos antigos.
Aplicar volume de fato no AudioServer
Guardar o numero nao adianta se ele nao mexe no som. O Godot controla volume por bus de audio, e o volume e medido em decibeis, nao em porcentagem linear. Um slider de menu vai de 0.0 a 1.0, entao voce precisa converter esse valor linear para decibeis com linear_to_db. Sem essa conversao o volume soa errado, porque a percepcao de intensidade nao e linear.
Antes de tudo, no editor, abra o painel Audio (na barra inferior) e crie os buses Master, Music e SFX. Depois aplique assim:
func apply_audio() -> void:
_set_bus_volume("Master", master_volume)
_set_bus_volume("Music", music_volume)
func _set_bus_volume(nome_bus: String, valor_linear: float) -> void:
var indice := AudioServer.get_bus_index(nome_bus)
if indice == -1:
return
if valor_linear <= 0.0:
# Silencio total: muta o bus em vez de mandar -inf dB
AudioServer.set_bus_mute(indice, true)
else:
AudioServer.set_bus_mute(indice, false)
AudioServer.set_bus_volume_db(indice, linear_to_db(valor_linear))
O cuidado com valor_linear <= 0.0 existe porque linear_to_db(0.0) retorna menos infinito, e e mais limpo simplesmente mutar o bus quando o slider esta no zero.
Aplicar resolucao e tela cheia no boot
A parte de video usa a DisplayServer e a janela atual. No Godot 4 o modo de janela e setado por DisplayServer.window_set_mode, e o tamanho por DisplayServer.window_set_size. Aplicar no boot e o que faz a opcao parecer permanente: o jogador escolhe tela cheia, fecha o jogo e, ao abrir de novo, ja entra em tela cheia.
func apply_video() -> void:
if fullscreen:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
else:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
DisplayServer.window_set_size(resolution)
_centralizar_janela()
func _centralizar_janela() -> void:
var tela := DisplayServer.screen_get_size()
var janela := DisplayServer.window_get_size()
DisplayServer.window_set_position((tela - janela) / 2)
func apply_all() -> void:
apply_audio()
apply_video()
Como apply_all() e chamado no _ready() do autoload, e como autoloads carregam antes da primeira cena, tudo ja esta no lugar quando o menu principal aparece. So aplique resolucao em modo janela; em tela cheia o tamanho acompanha o monitor e setar window_set_size ali nao faz sentido.
Ligando o menu de opcoes ao Settings
Agora a tela de opcoes so precisa ler e escrever no singleton. Suponha um HSlider para volume e um CheckButton para tela cheia. Conecte os sinais e chame save_settings() quando o jogador confirmar. Vale conectar pelo codigo com .connect para deixar claro o que esta ligado a que:
extends Control
@onready var slider_master: HSlider = $VBox/SliderMaster
@onready var check_fullscreen: CheckButton = $VBox/CheckFullscreen
@onready var botao_aplicar: Button = $VBox/BotaoAplicar
func _ready() -> void:
# Reflete o estado atual nos controles
slider_master.value = Settings.master_volume
check_fullscreen.button_pressed = Settings.fullscreen
slider_master.value_changed.connect(_on_master_changed)
check_fullscreen.toggled.connect(_on_fullscreen_toggled)
botao_aplicar.pressed.connect(_on_aplicar)
func _on_master_changed(valor: float) -> void:
Settings.master_volume = valor
Settings.apply_audio() # feedback imediato ao arrastar o slider
func _on_fullscreen_toggled(ativo: bool) -> void:
Settings.fullscreen = ativo
Settings.apply_video()
func _on_aplicar() -> void:
Settings.save_settings()
Repare numa decisao de design: o audio aplica na hora (o jogador ouve a mudanca enquanto arrasta o slider), mas a gravacao em disco so acontece quando ele clica em aplicar. Gravar a cada movimento do slider seria desperdicio de escrita. Se voce tem um menu de pause, pode abrir essa mesma tela de opcoes a partir dele, reaproveitando o singleton sem duplicar logica.
Adicionando idioma como mais uma secao
Como ConfigFile e organizado por secao, encaixar idioma e trivial. Crie uma secao locale e grave o codigo da lingua. No boot, aplique com TranslationServer.set_locale. Isso conecta direto com seu fluxo de localizacao e traducao:
func apply_locale() -> void:
var idioma: String = ConfigFile.new() and "pt_BR"
# Em codigo real, leia da variavel ja carregada:
TranslationServer.set_locale(Settings.locale)
Na pratica, adicione var locale: String = "pt_BR" no autoload, leia com get_value("locale", "language", locale) dentro de load_settings() e chame TranslationServer.set_locale(locale) em apply_all(). O padrao continua sendo a sua tabela de defaults.
Fechando: um sistema pequeno que resolve muito
O ConfigFile cobre praticamente toda a necessidade de salvar configuracoes godot sem dependencias externas. O resumo do que importa: grave sempre em user://, organize por secao com set_value, leia com get_value passando um padrao para tolerar arquivos antigos, centralize tudo num autoload Settings e aplique audio e video no _ready() para que as escolhas valham desde o boot. A partir desse esqueleto voce adiciona qualquer opcao nova (sensibilidade do mouse, brilho, rebind de teclas) so criando mais uma chave numa secao. O sistema cresce sem virar bagunca, e o jogador sente que o jogo lembra das preferencias dele.


