Game Dev Artisan Logo
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.

Table of Contents

Introduction 

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.

Video 

Custom Remap Button 

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)[0].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. By enabling 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.

In our _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.

Our _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.

The _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.

Display Remap Buttons Inside Menu 

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 Array[String]:

# game_menu.gd
...
@export var action_items: Array[String]

We also will rename our GridContainer to SettingsGridContainer and give it and the MainMenuButton a Unique Access Name. We will then bind our %SettingsGridContainer and %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 create_action_remap_items:

# 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 SettingsGridContainer.

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 SettingsGridContainer.

Let's call our function from within our _ready function:

# 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 move_forward, move_backward, turn_left, turn_right, and weapon_fire actions.

Theme Type Variant 

Lastly we will setup a quick Theme Type Variant for our new RemapButton class we've created.

Open our 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 Button.

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.

Run And Remap 

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 Label and 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.

Recap 

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!

Happy Coding!

Want to support our work?Support us via Ko-fi