extends Node ## Global keybinding manager (autoload `Controls`). ## Loads saved keyboard bindings on startup and applies them to InputMap so ## rebinds made in the intro menu persist into the game scene. Only the ## keyboard event of each action is overridden; joypad/mouse events are kept. const SAVE_PATH := "user://controls.cfg" const SECTION := "keybinds" ## Actions the options menu lets the player rebind (keyboard only). const REBINDABLE_ACTIONS: PackedStringArray = [ &"move_forward", &"move_back", &"move_left", &"move_right", &"ability_kick", &"ability_slam", &"ability_dash", &"ability_roll", ] func _ready() -> void: load_saved() ## Returns the first keyboard event bound to an action, or null. func get_key_event(action: StringName) -> InputEventKey: for event: InputEvent in InputMap.action_get_events(action): if event is InputEventKey: return event return null ## Human-readable label for the action's current key (e.g. "W", "Space"). func get_key_label(action: StringName) -> String: var event := get_key_event(action) if event == null: return "—" return event.as_text_physical_keycode() ## Replaces the primary keyboard event of `action` with `event`, keeping ## every other event (e.g. arrow-key alternates, joypad) in its place. func rebind(action: StringName, event: InputEventKey) -> void: rebind_no_save(action, event) save() func save() -> void: var cfg := ConfigFile.new() for action: StringName in REBINDABLE_ACTIONS: var event := get_key_event(action) if event != null: cfg.set_value(SECTION, String(action), event.physical_keycode) cfg.save(SAVE_PATH) func load_saved() -> void: var cfg := ConfigFile.new() if cfg.load(SAVE_PATH) != OK: return for action: StringName in REBINDABLE_ACTIONS: var key := String(action) if not cfg.has_section_key(SECTION, key): continue var keycode: int = cfg.get_value(SECTION, key) var event := InputEventKey.new() event.physical_keycode = keycode as Key rebind_no_save(action, event) ## Rebuilds the action's event list, swapping the first keyboard event for ## `event` in place so ordering (and thus the "primary" key) is preserved. func rebind_no_save(action: StringName, event: InputEventKey) -> void: var events := InputMap.action_get_events(action) InputMap.action_erase_events(action) var replaced := false for existing: InputEvent in events: if not replaced and existing is InputEventKey: InputMap.action_add_event(action, event) replaced = true else: InputMap.action_add_event(action, existing) if not replaced: InputMap.action_add_event(action, event) func reset_defaults() -> void: InputMap.load_from_project_settings() if FileAccess.file_exists(SAVE_PATH): DirAccess.remove_absolute(SAVE_PATH)