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.

Ferramenta:Godot

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. Se o jogo for lancar na Steam e voce quiser que essas preferencias sigam o jogador de uma maquina para outra, da para espelhar esse mesmo arquivo na nuvem ao integrar a Godot com a Steam usando o GodotSteam.

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.

Curso de Godot
Quer fazer seu primeiro jogo na Godot?

No curso de Godot da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Godot
Suporte no WhatsAppAcesso vitalĂ­cioGarantia de 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.

Para um menu de opções completo, veja configurações de vídeo, resolução e tela cheia no Godot.