Table of Contents
In this post of Godot Fundamentals, we're covering how we can remap our input actions to different keys to allow our users full customization of how they play our game. We'll create a custom button that we can select in our menu to listen for our InputEvents and remap the action to the pressed key.
To get started we will be creating a custom
RemapButton class that will extend the default behavior of our menu's button.
Create a new file in
res://scenes/ui/menus that we'll all remap_button.gd:
# remap_button.gd extends Button class_name RemapButton @export var action: String func _init(): toggle_mode = true theme_type_variation = "RemapButton" func _ready(): set_process_unhandled_input(false) update_key_text() func _toggled(button_pressed): set_process_unhandled_input(button_pressed) if button_pressed: text = "... Awaiting Input ..." release_focus() else: update_key_text() grab_focus() func _unhandled_input(event): if event.pressed: InputMap.action_erase_events(action) InputMap.action_add_event(action, event) button_pressed = false func update_key_text(): text = "%s" % InputMap.action_get_events(action).as_text()
We'll take advantage of this toggle by altering the input handling to listen for input events only once the button has been pressed.
Button's toggle_mode to true, it will keep the pressed state of our
Button on each press.
We'll update the
Button's text to display that we are listening for input. Once we receive the input, we'll update our
InputMap's action and release the
Button's pressed state. From here we'll update the
Button's text again to dispaly the new action event text.
We've given our
Button a class_name of
RemapButton. The @export variable of action will be a String of the action this
Button will remapping for.
When we initialize, we want to ensure the toggle_mode is set to true and we'll also create theme variation called RemapButton.
_ready function we'll set our processing of the
_unhandled_input function to false and call our
update_key_text function; which updates the
Button's text variable to the
InputMap's action event converted to text.
_toggled function will set the processing of our
_unhandled_input based on whether our button is pressed or not. If our button is pressed, we set our text to "... Awaiting Input ..." and release it's focus in our UI. Once the button is no longer pressed, we update it's text again with our
update_key_text function and regrab focus.
_unhandled_input function is in charge of listening for a keypress and remapping it to the
InputMap by erasing all existing events and then adding the one we just pressed. We then set the button_pressed to false.
Rather than adding each
RemapButton to our menu, we are going to create a variable Array of our action items and when our
GameMenu is ready, we'll create our action remap items and add them to our ui.
To do this we'll add an @export variable for action_items of type
# game_menu.gd ... @export var action_items: Array[String]
We also will rename our
SettingsGridContainer and give it and the
MainMenuButton a Unique Access Name. We will then bind our
%MainMenuButton to local variable references so we can access them to add children to our
SettingsGridContainer and properly set our next and previous focus neighbors.
# game_menu.gd ... @onready var settings_grid_container = %SettingsGridContainer @onready var main_menu_button = %MainMenuButton
Then we'll create a new function called
# game_menu.gd ... func create_action_remap_items() -> void: var previous_item = settings_grid_container.get_child(settings_grid_container.get_child_count() - 1) for index in range(action_items.size()): var action = action_items[index] var label = Label.new() label.text = action settings_grid_container.add_child(label) var button = RemapButton.new() button.action = action button.focus_neighbor_top = previous_item.get_path() previous_item.focus_neighbor_bottom = button.get_path() if index == action_items.size() - 1: main_menu_button.focus_neighbor_top = button.get_path() button.focus_neighbor_bottom = main_menu_button.get_path() previous_item = button settings_grid_container.add_child(button)
Our function will grab the last child from our current
SettingsGridContainer and set it to the previous_item variable.
We loop over the list of action_items and get the action String. We then create a
Label with the text of our action String and add it first to the
Then we create a
RemapButton called button and set the action equal to this current action then set focus_neighbor_top to our previous_item's path in the node tree. We then set a reference to our new
Button in our previous_item. If our current action is the last in the list, we'll set the reference to focus neighbors in both directions.
We then set our previous_item to our button to prepare our next iteration of our loop, then add it to the
Let's call our function from within our
# game_menu.gd ... func _ready(): create_action_remap_items()
Now we can select our
GameMenu and in our Inspector panel, we can add an element to the action_items
Array for each action we want to be able to remap from our menu. For now I'll add the
Lastly we will setup a quick Theme Type Variant for our new
RemapButton class we've created.
res://assets/theme/default_theme.tres and select Manage items. We'll Add a type called
RemapButton from the bottom left. Close the popup dialogue and select
RemapButton from the Type dropdown and select the last tab of our override options, here we'll set that our
RemapButton has the Base Type of
This will allow us to override specific
Button attributes, such as the font or background color and style.
For our case, we'll want to modify the
StyleBox using a
StyleBoxFlat adn giving it a BG Color that contrasts with the rest of our UI elements. We will also set the expand margins to 2px.
Go ahead and run the game, start a new game and open the
GameMenu; we should see all of our action items mapped with a
Button. If we navigate them, they should travel between the next and previous items properly.
If we select one of our action items to remap, it should await input from us, indicated by the text changing, then once we press a new key, that action is now remapped.
This is just the start of remapping our input action events. You could introduce Keyboard, Mouse, Controller specific mappings, limit the types of buttons you can press, enable multiple tracks of events per action, and setup profiles of popular mappings for your game. This should be enough to get you introduced to the concept, but as always learn more about the
InputMap from Godot's documentation . Be sure to check out the project's GitHub project for the source code! If you have questions or ideas on how to leverage these concepts, be sure to jump into our growing Discord community and start a chat!