Input remapping for Godot 4

Hello everyone, welcome back.

Today, we are going to look at Input remapping! Now if you are wondering what does it means then allow me to explain.

Input remapping simply means that the developer provides the players or users the ability to customise the game controls from within the game.

For this I will be using Godot 4.1.

And now without further ado let’s get into it!



Intro

Okay for this tutorial, I have setup an Input map to go up, down, left and right.



For the tutorial it doesn’t matter if we setup the keys but I have define the WASD key none the less.

I have also setup the nice UI.



GameInputs.gd

Now first we want to create a custom resource that will hold our default input map. For that let’s create a script. I will call it GameInputsWhich will extend from Resource class.

  1. class_name GameInputs extends Resource
  2.  
  3. enum actions {
  4.     ACTION_UP,
  5.     ACTION_DOWN,
  6.     ACTION_RIGHT,
  7.     ACTION_LEFT
  8. }
  9.  
  10. @export var action_up_event: InputEvent
  11. @export var action_down_event: InputEvent
  12. @export var action_right_event: InputEvent
  13. @export var action_left_event: InputEvent
  14.  

I like to define the class name for all my scripts but for the custom resource I think you must define the class name.

Now if you are wondering what is custom resource, then think of it as a data container. It is also equivalent of Unity’s scriptable objects.

Anyway, in our script, we have defined the enum which will define have our input actions. Make sure you type in exactly what you have defined in InputMap. Then we have defined input events for all the actions, basically input keys.

Ok that’s it for our GameInputs script. In our project now we can create custom resource.

Right click > create new > resource then select GameInputs that we have just created.



Let’s give a name for our resource, I will also call it GameInputsIn the inspector, we will have 4 input events. For Up let’s give new Input event key. It will have lots of properties, but we don’t have to worry about that, just click on the configure button. Define the key, I will set W and hit ok.



Let’s do the same for the rest. Now we want to access this resource so let’s create another script. I will call it InputMapController which extends from Node.

InputMapController.gd

  1. class_name InputMapController extends Node
  2.  
  3. const GAME_INPUTS_DEFAULT_PATH: String = "res://tutorial/resources/GameInputs.tres"
  4. const GAME_INPUTS_PATH: String = "user://GameInputs.tres"
  5.  
  6. static var game_inputs: GameInputs
  7.  
  8. func _ready() -> void:
  9.     game_inputs = ResourceLoader.load(GAME_INPUTS_PATH)
  10.     if game_inputs == null:
  11.         game_inputs = ResourceLoader.load(GAME_INPUTS_DEFAULT_PATH)
  12.  
  13.     apply_input_map()
  14.  
  15. static func apply_input_map() -> void:
  16.     var action_up: String = GameInputs.actions.keys()[GameInputs.actions.ACTION_UP].to_lower()
  17.     InputMap.action_erase_events(action_up)
  18.     InputMap.action_add_event(action_up, game_inputs.action_up_event)
  19.  
  20.     var action_down: String = GameInputs.actions.keys()[GameInputs.actions.ACTION_DOWN].to_lower()
  21.     InputMap.action_erase_events(action_down)
  22.     InputMap.action_add_event(action_down, game_inputs.action_down_event)
  23.  
  24.     var action_right: String = GameInputs.actions.keys()[GameInputs.actions.ACTION_RIGHT].to_lower()
  25.     InputMap.action_erase_events(action_right)
  26.     InputMap.action_add_event(action_right, game_inputs.action_right_event)
  27.  
  28.     var action_left: String = GameInputs.actions.keys()[GameInputs.actions.ACTION_LEFT].to_lower()
  29.     InputMap.action_erase_events(action_left)
  30.     InputMap.action_add_event(action_left, game_inputs.action_left_event)
  31.  
  32. static func save_input_map() -> void:
  33.     ResourceSaver.save(game_inputs, GAME_INPUTS_PATH)
  34.  

In our scene, let’s create a Node, I will also call it InputMapControllerAttach the script to it.

In our script, we are loading our custom resource and then applying and saving the Inputmap with the keys defined in our GameInputs custom resource.

RemapButton.gd

Now let’s create another script called RemapButton which extends from Button.

  1. class_name RemapButton extends Button
  2.  
  3. @export var action: GameInputs.actions = GameInputs.actions.ACTION_UP
  4.  
  5. func _ready() -> void:
  6.     set_process_unhandled_key_input(false)
  7.     toggled.connect(on_button_toggled)
  8.     display_key()
  9.  
  10. func _unhandled_key_input(event: InputEvent) -> void:
  11.     set_key(event)
  12.     button_pressed = false
  13.  
  14. func on_button_toggled(toggle_state: bool) -> void:
  15.     set_process_unhandled_key_input(toggle_state)
  16.     if toggle_state:
  17.         text = "..."
  18.     else:
  19.         display_key()
  20.         InputMapController.apply_input_map()
  21.         InputMapController.save_input_map()
  22.  
  23. func set_key(event: InputEvent) -> void:
  24.     match action:
  25.         GameInputs.actions.ACTION_UP:
  26.             InputMapController.game_inputs.action_up_event = event
  27.         GameInputs.actions.ACTION_DOWN:
  28.             InputMapController.game_inputs.action_down_event = event
  29.         GameInputs.actions.ACTION_RIGHT:
  30.             InputMapController.game_inputs.action_right_event = event
  31.         GameInputs.actions.ACTION_LEFT:
  32.             InputMapController.game_inputs.action_left_event = event
  33.  
  34. func display_key() -> void:
  35.     match action:
  36.         GameInputs.actions.ACTION_UP:
  37.             text = InputMapController.game_inputs.action_up_event.as_text()
  38.         GameInputs.actions.ACTION_DOWN:
  39.             text = InputMapController.game_inputs.action_down_event.as_text()
  40.         GameInputs.actions.ACTION_RIGHT:
  41.             text = InputMapController.game_inputs.action_right_event.as_text()
  42.         GameInputs.actions.ACTION_LEFT:
  43.             text = InputMapController.game_inputs.action_left_event.as_text()

In our script, we have defined methods for settings and displaying key on our button. We are changing our button's text based on the toggle state in on_button_toggled(), we are also updating and saving our GameInputs resource after setting the new key. We have overridden _unhandled_key_input() where we are actually setting the new key.

This script will update and save our input map everytime we change any action key. Generally you want to give user an “apply settings button” and call these two when apply button is pressed. So keep that in mind. I will leave that to you.

Apply this script on our UI buttons. Also make sure we have set the button's toggle mode to true. Don't forget to set the action enum in the inspector.


And now we can change the input map.

Trouble following along? Check out this step by step video!



Thank you so much for reading!

Comments