Easily differentiate between a button tap, double tap, press, long press, and hold for all of your input actions in Godot.
InputController requires at least Godot 4.2. It may work with earlier versions, but they have not been tested.
Let's install InputController into your Godot project:
- Download the
.zip
ortar.gz
file for your desired InputController version here. - Extract the
addons
folder from this file. - Move the
addons
folder to your Godot project folder.
Now, let's verify you have correctly installed InputController:
- You have this folder path
res://addons/input_controller
. - Head to
Project > Project Settings
. - Click the
Plugins
tab. - Tick the
enabled
button next to InputController. - Restart Godot.
To get started, simply add an InputController
node to your scene tree. This node will automatically start listening
for input events and emitting a signal when it detects a tap, double tap, press, long press, or hold for any action.
Once you've added the InputController
to your scene tree, simply connect an event handler function in your script to
the input_detected
signal:
const InputType = InputController.InputType
@onready var input_controller = $InputController
func _ready():
input_controller.input_detected.connect(_on_input_detected)
func _on_input_detected(event: InputEvent, action: String, input_type: InputType):
match input_type:
InputType.TAP:
prints(action, "tapped")
InputType.DOUBLE_TAP:
prints(action, "double tapped")
InputType.PRESS:
prints(action, "pressed")
InputType.LONG_PRESS:
prints(action, "long pressed")
InputType.HOLD:
prints(action, "held")
See the Signals section below, for more information on the input_detected
signal.
The following exported values can be modified in the Godot Editor Inspector, or programmatically by directly accessing the properties of the node.
Here is an example of how you can modify the settings in a script:
@onready var input_controller = $InputController
func _ready():
# Input Timing
input_controller.max_button_tap = 0.18
input_controller.max_double_tap_delay = 0.12
input_controller.max_button_press = 0.45
input_controller.max_long_press = 0.85
# Input Handlers
input_controller.ui_inputs = ["ui_*", "menu_*"]
input_controller.shortcut_inputs = ["shortcut_*", "quit_game"]
input_controller.unhandled_key_inputs = ["*_key"]
input_controller.unhandled_inputs = ["player_*_action", "player_*_move"]
# Event Propagation
input_controller.set_input_as_handled = true # Default value
Use these settings to fine tune the timing used to differentiate between a tap, double tap, press, long press, and
hold. These are float
values measured in seconds, so you can get very precise.
Inspector Label | Property Name | Type | Default |
---|---|---|---|
Max Button Tap | max_button_tap |
float |
0.2 |
Max Double Tap Delay | max_double_tap_delay |
float |
0.1 |
Max Button Press | max_button_press |
float |
0.5 |
Max Long Tap | max_long_press |
float |
1 |
Use these settings to customize which event handlers are used to detect different types of actions, and which input actions to listen for.
Inspector Label | Property Name | Type | Default | Method |
---|---|---|---|---|
UI Inputs | ui_inputs |
Array[String] |
["ui_*"] |
_input() |
Shortcut Inputs | shortcut_inputs |
Array[String] |
[] |
_unhandled_shortcuts() |
Unhandled Key Inputs | unhandled_key_inputs |
Array[String] |
[] |
_unhandled_key_inputs() |
Unhandled Inputs | unhandled_inputs |
Array[String] |
["*"] |
_unhandled_input() |
Each array can have zero or more strings that represent the names of the actions you want to listen for. The *
character is a wildcard that will match any string if used alone, or any part of a string if used in combination with
other characters.
For example:
["ui_*"]
will match any action that starts withui_
.["*_key"]
will match any action that ends with_key
.["player_*_action"]
will match any action that starts withplayer_
and ends with_action
.["shortcut_*", "quit_game"]
will match any action that starts withshortcut_
or is exactlyquit_game
.["*"]
will match any action.
By default, the _input() method will be used to handle all actions that start with ui_
; and _unhandled_input()
will be used to handle all other actions. This may or may not have a material impact on your game, but it's good to
know if things aren't behaving as expected.
More information about how input events are processed in Godot can be found here.
If set to true
(default value), the InputController
will consume an InputEvent
and stop it from propagating to
other nodes by calling get_viewport().set_input_as_handled()
.
To allow the event to propagate after handling it, set this value to false
. You might want to do this if you are
only using the InputController
for logging, analytics, or some other observational behavior.
Inspector Label | Property Name | Type | Default |
---|---|---|---|
Set Input as Handled | set_input_as_handled |
bool |
true |
The InputController
node has the following methods:
get_ticks()
find_actions(event: InputEvent, actions: Array[StringName])
map_actions_to_handlers(available_actions: Array[StringName] = InputMap.get_actions())
process_input(event: InputEvent, actions: Array[StringName])
This is a helper method that returns Time.get_ticks_msec()
in seconds, as a float
. It is used internally to compare
the time elapsed between inputs with the Input Timing configuration values, to determine
the type of input action.
This is a helper method that filters a given array of actions, returning a new array that contains any actions that
evaluate to true
when passed to event.is_action()
for the given InputEvent
.
This method is used internally to apply the Input Handlers configuration values. You should not need to call it manually; if you do, be aware that it will clear and override the configuration settings applied in the inspector section of the editor.
This is the primary method that is used at runtime. It is called from each of the four input handler methods
(_input()
, _unhandled_input()
, _unhandled_key_input()
, and _unhandled_shortcuts()
) described
here, based on your
Input Handlers configuration settings.
For each action in the given actions
array, it will emit the appropriate input_detected
signal, based on the given
InputEvent
. Then, it will optionally mark the event as handled, based on your
Event Propagation configuration settings.
The InputController
node emits the following signal:
input_detected(event: InputEvent, action: StringName, type: InputController.InputType)
The signal will include three arguments:
event
: TheInputEvent
that triggered the actionaction
: The name of the action that was triggeredtype
: TheInputType
of input that was detected.
The InputType
enum has the following values:
ACTIVE
: An input action has just begun; its type is not yet determined.TAP
: A quick press and release of a button.DOUBLE_TAP
: Two quick, consecutive taps of a button.PRESS
: A standard press of a button.LONG_PRESS
: A press and slightly prolonged hold of a button.HOLD
: A press and hold of a button that exceeded the long press duration.CANCEL
: An input action that has been canceled and can be ignored.
The InputType.ACTIVE
value is used to indicate that an input event has just begun (i.e. the action was just pressed).
The actual type of input will be determined when the button is released.
The InputType.CANCEL
value is used to negate the first tap in a double tap sequence. The signal for the first tap
cannot be emitted until either a second tap is detected or the double tap delay has been exceeded; if a second tap
is detected, the first tap gets canceled and can be ignored.
The InputController
will only receive an input event if it has not already been handled by a child node, or a sibling
node that appears below it in the scene tree.
Try creating a new scene with only the InputController
node and see if the input actions are detected. If they are,
then you know the actions are being handled elsewhere in your code before they reach the InputController
.
IMPORTANT: Be sure you are using a version of InputController >= 1.0.0
. In earlier versions, there was a bug that
prevented custom configurations from being recognized.
If you are using a version >= 1.0.0
, this is likely an issue with your
Input Handlers Configuration.
When using wildcards, be aware that the order of the handlers in the list matters. The first handler that matches an action will be the one that ends up handling it.
For example, let's say you have the following configuration:
- UI Inputs:
["ui_*", "*_menu"]
- Shortcut Inputs:
["shortcut_*"]
- Unhandled Key Inputs:
["*"]
- Unhandled Inputs:
["player_*"]
In this case, an action named shortcut_menu
would be handled by the UI Inputs handler, because it matches *_menu
and that handler gets first pick of the actions. Likewise, no actions would make it to the Unhandled Inputs handler,
because the *
wildcard was used in the Unhandled Key Inputs handler.
This project is licensed under the terms of the MIT license.