Voltar para o Blog
Quest Log

Salvar Opcoes do Jogo com ConfigFile no Godot 4

Tela de opcoes de um jogo no Godot com controles de volume e resolucao

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.

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

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.