Every game needs an options menu and most need rebindable controls. The naive version
breaks in the same two ways: a missing or corrupted config key returns the wrong type
and crashes, and "rebind your keys" turns out to mean serializing InputEvents by hand.
SettingsVault is the drop-in that does both correctly, in one human-readable file on disk.
Godot 4.x · GDScript · no dependencies · ~250 lines you can read in one sitting.
- Typed defaults, real reset. Every setting has a registered default, so a missing or
garbage key returns the right type — never
nullwhere you expected afloat.reset(),reset_section(), andreset_all()actually return to those defaults. - Remappable controls that persist. Rebind an action at runtime; SettingsVault applies
it to the
InputMapand serializes it to disk. On the next launch,load()restores it.reset_binding()returns an action to the project defaults you captured at startup. - Covers what players actually rebind — keyboard, mouse buttons, joypad buttons and axes — serialized properly, not stringified guesswork.
- One ConfigFile. Everything lives in one readable
.cfg, so saves are inspectable and pair cleanly with SaveVault for the rest of your persistence.
- Copy
addons/settings_vault/into your project. - Project → Project Settings → Plugins → enable SettingsVault
(or add
settings_vault.gdas an autoload namedSettingsVaultyourself).
func _ready() -> void:
# Declare your settings and their defaults, once.
SettingsVault.register_defaults({
"video": {"fullscreen": false, "vsync": true},
"audio": {"master": 1.0, "music": 0.8},
})
# Snapshot the project's InputMap defaults so reset_binding() has a target.
SettingsVault.capture_defaults(["jump", "move_left", "move_right"])
# Restore whatever the player saved last session (missing file is fine).
SettingsVault.load()
# Read and write settings — set_value autosaves by default.
var full := SettingsVault.get_value("video", "fullscreen")
SettingsVault.set_value("audio", "master", 0.5)
# React to changes anywhere (e.g. apply the volume live).
SettingsVault.setting_changed.connect(func(section, key, value):
if section == "audio":
apply_volume(key, value))# In your "press a key to rebind" UI, on the captured input event:
func _on_rebind(action: StringName, event: InputEvent) -> void:
SettingsVault.rebind(action, event) # applied to InputMap + saved
SettingsVault.add_binding("jump", controller_a_button) # a second key for the same action
SettingsVault.reset_binding("jump") # back to the captured default
var events := SettingsVault.get_bindings("jump") # for drawing the current bindingTurn off autosave, let the player fiddle, then commit on Apply:
SettingsVault.autosave = false
# ... player changes a dozen things ...
SettingsVault.save() # one write| Call | Does |
|---|---|
register_default(section, key, default) |
Declare one setting + its default. |
register_defaults(tree) |
Bulk register from {section: {key: default}}. |
get_value(section, key, fallback=null) |
Stored value, else default, else fallback. |
set_value(section, key, value) |
Set, emit setting_changed, autosave. |
has_value(section, key) |
Whether an explicit value was set. |
reset(section, key) / reset_section(s) / reset_all() |
Drop overrides back to defaults. |
capture_defaults(actions) |
Snapshot InputMap events as reset targets (call at startup). |
rebind(action, event) |
Replace an action's events with one, apply + persist. |
add_binding(action, event) |
Add another event to an action. |
get_bindings(action) |
Current Array[InputEvent] for an action. |
reset_binding(action) |
Restore captured default events. |
save() / load() |
Write / read the ConfigFile (returns an Error). |
config_path |
Where it's stored (default user://settings.cfg). |
autosave |
Auto-write on every change (default true). |
tests/test_settings_vault.gd is a headless suite covering typed defaults and fallbacks,
all three reset paths, the change signal, a save/load round-trip across instances, and
keyboard/mouse binding rebind → persist → restore → reset. Run it:
godot --headless --path . -s tests/test_settings_vault.gd
MIT. Drop it in a commercial game, no attribution required.