What You’ll Learn in This Hour:
How nodes receive inputs from player
Querying for inputs from Input singleton
Making your game responsive to player’s input
What Action is and its importance in custom input mapping
Simulating player’s action using InputEventAction
Input management is among the most important topics when it comes to game development. Input is what differentiates other kinds of
entertainment and video games. A game would be very boring if players could not control their characters. Godot engine takes care of difficult
tasks, such as getting raw data from devices, and provides an easy interface for game developers. It has a very interesting way of integrating
user inputs with its node system. The engine also provides various helpers to make input handling easier.
In this hour, you’ll learn how to manage user input and make your game respond to it. We’ll first look at how a node receives inputs from a
player. Then we will move on to InputEvent class, a class that contains information that needs to properly respond to inputs. After that, we will
do some coding inGDScript. Then we will take a look at the InputMap singleton and how it can help when players want to choose how they
want to control the game. Finally, we’ll get to know InputEventAction, a special subclass of InputEvent that can simulate player action.
Input Basics
Before we get into more in-depth topics about responding to inputs and managing them, it is important that you know how to acquire those
inputs in context of the node system of Godot engine.
Getting User Input
There are two ways to get user input:
1. Having a function called every time when the user makes a change. (Using input)
This is mostly useful when the input of interest involves changes to the input device; for example, choosing button press or release,
mouse move or click, and screen touch or drag. You might wan
t
to get the input this way when dealing with user interface or game
actions, such as attacking, shooting, using an item, etc.
2. Querying the device. (Using Input singleton)
This is useful when you want to know if a specific button is being pressed at the time. You might want to do this every frame (in
_process callback) to make the player character move when a player holds a button down.
Most game inputs will fall into either one of the previous cases. When you have to respond to an input, it can be helpful to first ask yourself into
which category the input falls.
NOTE
Limitation of Querying the Device
Some input events cannot be reliably received by querying the device, as the event ends as soon as it happens. Mouse-wheel scrolling is one
example. Also, there is no method to query touch-screen events through the Input singleton yet.
Callbacks
Node has several callbacks (virtual functions) that can be used to manage inputs. Even when ignoring those not related to inputs, there is still
quite a lot of them, and this can be confusing to new developers. However, most of the time, you don’t really need more than the following two
callback functions.
Input Function
_input is a virtual function of a generic Node type that is called every time there is a change involving input devices. The node lowest in the
hierarchy is called first (see Listing 7.1).
LISTING7.1 Input Function
Click here to view code image
func _input(event):
event is an object derived from the InputEvent class containing information related to the received input. When a node responds to the input,
it should call get_tree().set_input_as_handled() to prevent other nodes from responding to it again.
If _input is present in the script, that node will automatically get input callbacks. To manually start or stop getting input callback, use the
function shown in Listing 7.2.
111111111
22222222LISTING7.2 Function to Manually Start or Stop Getting Input Callback
Click here to view code image
set_process_input(enable)
Set enable to false to stop getting callback. Setting it to true will resubscribe the node to receive input events.
Unhandled Input Function
_unhandled_input is similar to _input and is also a virtual function of a generic Node type. However, it is called in every node in the tree
when an input event passed to _input is not marked as handled (Listing 7.3).
LISTING7.3 Unhandled Input Function
Click here to view code image
func _unhandled_input(event):
event is the same object of the InputEvent class containing information related to the received input. This function is called when the previous
input is not handled by any node. It can be disabled and re-enabled the same way as _input. See Listing 7.4.
LISTING7.4 Function to Manually Start or Stop Getting Unhandled Input Callback
Click here to view code image
set_process_unhandled_input(enable)
Similar to _input, setting enable to false stops the node from getting callback. Setting it to true will resubscribe the node to receive
unhandled input events.
For a simple game, you can use either _input or _unhandled_input, but for more complex games, it is suggested you put actual game
controls, such as character movement, in the _unhandled_input function and leave _input to nodes that receive specific events to
handle them first. This includes advancing message dialog and setting them as handled. That’s one way to prevent your player character from
moving away while there’s a message displayed on screen.
NOTE
Nodes with Special Input Handling
There are several nodes that have additional methods for input handling. To name a few:
Viewport node can check for mouse position inside itself.
CollisionObject and its 2D counterpart, CollisionObject2D, can receive input events through Camera. You can click on objects directly
this way.
Control node also has a callback similar to those discussed previously: _gui_input(event).
This function is a bit different from previous functions, since it is local to Control. It is only called when it has the focus or the input happens
within its boundary. You only have to use this function when making your own control, as Godot engine provides an easier-to-use signal system.
The Control node and GUI are further discussed in Hour 9, “User Interface.”
Input Singleton
Input is a singleton that manages inputs at global level. It is ideal for querying if a specific button is pressed, setting cursor position,
capturing the cursor inside a game window, and containing input settings that affect the whole game. Listing 7.5 shows some of the helper
functions.
LISTING7.5 Input Singleton Helper Functions
Click here to view code image
Input.is_key_pressed(scancode)
Input.is_mouse_button_pressed(button)
Input.is_joy_button_pressed(device, button)
What you should put between the parentheses can be found in the @Global Scope section of Godot Engine APIDocumentations. Keyboard
buttons are prefixed with KEY. For example, KEYESCAPE for escape (esc) key, KEY_RETURN for enter key, KEY_M for letter ‘M’ key, etc.
Mouse buttons are prefixed with “BUTTON”. Examples include BUTTONLEFT and BUTTON_WHEEL_DOWN.
InputEvent Class
InputEvent is a class that contains information about user input. As you may have noticed from the previous topic, it is given to callback
functions as an only parameter, so it is very important that you thoroughly understand this class.
Subclasses
InputEvent itself has very limited uses. Most important are the subclasses that derive from this class. Listing 7.6 shows the hierarchy of
subclasses derived from InputEvent.
111111111
22222222LISTING7.6 Subclasses of InputEvent Class
Click here to view code image
InputEvent
InputEventAction
InputEventJoypadButton - for joystick or gamepad button.
InputEventJoypadMotion - for joystick hat movement.
InputEventScreenDrag - for swiping on a touch screen.
InputEventScreenTouch - for tapping on a touch screen.
InputEventWithModifiers
InputEventKey - for keyboard buttons.
InputEventMouse
InputEventMouseButton - for mouse buttons and mouse wheel scroll.
InputEventMouseMotion - for mouse movement.
The InputEventAction class simplifies user inputs to game actions, such as “move player character forward.” We will discuss more about
this class later. The InputEventWithModifiers class, like its parent, has almost no uses on its own. Derived classes can be checked for
usage of modifier keys, such as control (ctrl) keys, alternate (alt) or meta keys, and shift keys. The InputEventMouse class also has very
little use on its own. Derived classes are events generated by the mouse. This means that the cursor position associated with events is
available for you to use. This might seem like a lot, but we will take a look at each one individually in subsequent topics.
Keyboard and Joystick Input
Let’s start coding some GDScript to better visualize what we have learned so far and how it can be applied in game development withGodot
engine. We are going to handle basic keyboard and joystick inputs.
Keyboard Input
Create a new node of any type (this example will use generic Node type), so attach a GDScript to it. It doesn’t matter if the script is built in or
not. Type the script (you can omit the code comments) in Listing 7.7.
LISTING7.7 Basic Keyboard Input Handling Script
Click here to view code image
extends Node
# _input callback will be called when a keyboard button is pressed or released.
func _input(event):
# The event of interest is keyboard event, which is of class InputEventKey
.
if event is InputEventKey:
# Display the word “Echo” at the end of the line if this event is an echo
var is_echo = “Echo” if event.echo else “”
if event.pressed:
# When the player holds a button down, display “Key pressed”, scancode and whether it’s an echo
prints(“Key pressed”, event.scancode, is_echo)
else:
# Same as above, but display “Key released” when the key is released
prints(“Key released”, event.scancode, is_echo)
Save the script and the scene. Try running the game. Try pressing a key button, then release it. Try pressing several ones simultaneously. See
how the texts are printed to console by your inputs. The number following the “Key pressed” or “Key released” text is the same value as defined
in @Global Scope. For example, number 16777217 stands for KEY_ESCAPE. It can be very difficult for developers to debug the game when
keys are represented as numbers. For this, OS singleton has a function that converts a scancode into human-readable text (Listing 7.8).
LISTING7.8 Function to Convert Scancode into Human-Readable Text
Click here to view code image
OS.get_scancode_string(scancode)
Try using this function with the previous code. Now you are able to see the name of the pressed key in text.
Try holding a key down for a bit. You may notice that when you hold a key down, text repeatedly prints to the output with the word “echo” at the
end of the line. This is the result of event.echo. It signifies that the InputEventKey is repeated automatically. You may also notice there
is a fixed delay before this happens. This may remind you of a similar behavior in a text editor. When you hold a key down and wait a bit, the
letter begins repeating as if you manually typed it very fast. Although it is useful when you make your own text input control, this behavior is
rarely used in games. The player character should walk normally as the player holds the walk key down. We don’t need to simulate the button
being pressed repeatedly. To prevent echoes from being processed as normal inputs, simply create another condition for it, like what is shown
in Listing 7.9.
LISTING7.9 Prevent Echoes fromBeing Processed as Normal Inputs
Click here to view code image
if event is InputEventKey && !event.echo:
Let’s try handling “keyboard button holding” using Input singleton (Listing 7.10).
LISTING7.10 Using Input Singleton Inside Process Callback to Handle Keyboard Button Holding
Click here to view code image
111111111
22222222extends Node
func _process(delta):
# KEY_SPACE is an alias for 32, a scancode for spacebar
if Input.is_key_pressed(KEY_SPACE):
print(“Holding spacebar”)
Try pressing and releasing the spacebar. You’ll notice that it outputs “Holding spacebar” not only the moment you press or release it, but every
frame that you hold it down. This is very useful when you want to check if, for example, a character should move in this frame or not. With
Using _process, you will have access to the delta variable. This variable tells how much time has passed since the last frame, and it
varies each frame. It’s very important to take this variable into account to keep the movement speed constant. Also, unlike previous handling
with echo, there is no delay between the button pressing and function calling.
At this point, you should be able to tell the differences between using _input and Input singleton.
Moving a Sprite with Keyboard
Printing texts is boring, so let’s try controlling a Sprite using GDScript with knowledge from the previous topic. Create a new node of type
Sprite and set its texture. You can use the default robot icon. Attach the following GDScript shown in Listing 7.11.
LISTING7.11 Controlling a Sprite Node
Click here to view code image
extends Sprite
export var SPEED = 100
func _process(delta):
var direction = Vector2(0, 0)
if Input.is_key_pressed(KEY_UP):
direction.y -= 1
if Input.is_key_pressed(KEY_DOWN):
direction.y += 1
if Input.is_key_pressed(KEY_LEFT):
direction.x -= 1
if Input.is_key_pressed(KEY_RIGHT):
direction.x += 1
direction = direction.normalized() # To make sure diagonal movements will be in
the same speed
direction = SPEED delta # Multiply by speed (pixels per second) and
the time passed (seconds)
translate(direction) # Move the Sprite by the direction vector
Try running the game and using directional keys to move the Sprite.
Joystick Input
Now let’s continue handling joystick input. Godot engine abstracts gamepad and joystick layout so that the same script can work with different
controller brands. You’ll need a game controller to test your code. Edit your script into the code shown in Listing 7.12, save it, and try running
the game.
LISTING7.12 Basic Joystick Input Handling Script
Click here to view code image
extends Node
func _input(event):
if event is InputEventJoypadButton:
# print the button index
prints(“Button:”, str(event.button_index))
Try pressing all joystick or gamepad buttons. Take note of the button index of directional keys. You will need them in the upcoming exercise.
Again, the human-readable button index values can be found in @Global Scope section of the documentations. It is prefixed by
“JOY_BUTTON”.
Moving a Sprite with Joystick: Exercise
Let’s try controlling a Sprite using the joystick the same way we did with the keyboard. Create a new node type of Sprite and set its texture.
Attach the following GDScript shown in Listing 7.13. Be sure to fill in the blanks with the button index you get from the previous step.
LISTING7.13 Controlling a Sprite Node Using Joystick: Exercise
Click here to view code image
extends Sprite
const SPEED = 100
var deviceindex = 0
func ready():
111111111
22222222Input.connect(“joyconnectionchanged”, self, “joyconnect”)
func joy_connect(index, connect):
# When a joystick is detected, keep the device index in a variable
if connect:
device_index = index
func _process(delta):
var direction = Vector2(0, 0)
# Query Input singleton with the device index
if Input.is_joy_button_pressed(device_index, ): # UP
direction.y -= 1
if Input.isjoybuttonpressed(deviceindex, _): # DOWN
direction.y += 1
if Input.isjoybuttonpressed(deviceindex, _): # LEFT
direction.x -= 1
if Input.isjoybuttonpressed(deviceindex, _): # RIGHT
direction.x += 1
direction = direction.normalized() # To make sure diagonal movements will be in the
same speed
direction = SPEED delta # Multiply by speed (pixels per second) and the
time passed (seconds)
translate(direction) # Move the Sprite by the direction vector
Try running the game and use directional keys of the joystick to move the Sprite. Did you get all the button indices correctly? If your Sprite did
not go where you expected, try switching the button index around.
Mouse and Touch Input
A large portion of gamers may prefer to use game controllers to play, but for some genres, such as first-person shooters (FPS), the mouse is
also a viable option due to its precision. Games on mobile devices can make use of touch screen if the device has one. We’ll learn how to
respond to inputs from such devices.
Mouse Input
Similar to keyboard and joystick input, mouse inputs can be handled by following the code in Listing 7.14, which is using _input callback.
LISTING7.14 Basic Mouse Input Handling Script Using Input Callback
Click here to view code image
func _input(event):
if event is InputEventMouseButton && event.pressed:
prints(“Button”, event.button_index, “is pressed at”, str(event.position))
if event is InputEventMouseMotion:
prints(“Mouse moved to”, str(event.position))
The following code in Listing 7.15 uses Input Singleton:
LISTING7.15 Basic Mouse Input Handling Script Using Input Singleton
Click here to view code image
func _process(delta):
if Input.is_mouse_button_pressed(BUTTON_LEFT):
prints(“Holding left mouse button at”, get_tree().get_root()
.get_mouse_position())
Moving a Sprite with Mouse
Let’s do the same as we did in the previous topic. Create a new Sprite node, set its texture, and attach the script shown in Listing 7.16.
LISTING7.16 Controlling a Sprite Node Using Mouse
Click here to view code image
extends Sprite
func _input(event):
if event is InputEventMouseMotion:
position = event.position
Try moving the mouse cursor inside the game window. The Sprite is now stuck to the cursor.
Touch Input
Touch input events can be handled like that shown in Listing 7.17:
LISTING7.17 Basic Mouse Input Handling Script Using Input Callback
Click here to view code image
func _input(event):
111111111
22222222if event is InputEventScreenTouch && event.pressed:
prints(“Screen touch at”, str(event.position))
if event is InputEventScreenDrag:
prints(“Screen drag at”, str(event.position))
If you do not possess a device with a touch screen or you prefer to test the game in the desktop, go to Project > Project Settings > General >
Display-Window > Handheld > Emulate Touchscreen and turn this option on.
Moving a Sprite with Touchscreen
Let’s finish this topic with the same example. Again, create a new Sprite node, set a texture to it, and attach the script shown in Listing 7.18.
LISTING7.18 Controlling a Sprite Node Using Touchscreen
Click here to view code image
extends Sprite
func _input(event):
if event is InputEventScreenTouch:
position = event.position
The Sprite should now go wherever you tap.
Input Mapping
It is generally more beneficial to use Action and Input Mapping instead of defining individual keys in the script so that it can be easily ported to
various platforms with minimal change to code. Most of the time, players are going to play your game with default controls given to them, but it
would be nice to give them the opportunity to edit the controls so they can get into your game more easily and comfortably. However, in other
engines or frameworks, this can be a burden to developers. Not only do they have to keep a list of user-defined controls, the game must be
saved and reloaded each time it is launched. To help developers focus on more important things, Godot engine provides easy interfaces for
this task, Action and Input Map.
Action
Action is basically a group of input events that achieve the same task. It can be named to reflect what it does in the game. For example,
“move_up” Action has both KEY_W and KEY_UP in it. When a player presses either the ‘W’ key or up arrow key,
Input.is_action_pressed(“move_up”) will return to true. You can check for this condition and make the player character move as
a result.
Listing 7.19 uses Using _input.
LISTING7.19 Handling Input Defined in Action Using Input Callback
Click here to view code image
func _input(event):
if event.is_action(“move_up”):
if event.pressed:
print(“start walking”)
else:
print(“stop walking”)
Using Input singleton is shown in Listing 7.20.
LISTING7.20 Handling Input Defined in Action Using Input Singleton
Click here to view code image
func _process(delta):
if Input.is_action_pressed(“move_up”):
print(“walk”)
This way, you don’t have to check each individual key for a matching event. You also don’t have to worry about user-mapped control. As long
as it is in an Action, you can refer to it by the Action’s name.
Modifying Actions in your game can be done prior to running the game using Input Map in the Project Settings or while in-game using
InputMap singleton.
The Input Map of your project is accessible from the top-left menu bar. Choose Project > Project Settings > Input Map. Here, you’ll find several
default Actions that you can use, such as ui_accept, ui_cancel, etc. These default Actions cannot be deleted, as they are used by
Control and its derivatives. You can add your own Action by typing the name in the text box and pressing a button that says “Add.” The newly
created Action will appear at the bottom of the list. You may add an event to it by pressing the
button next to the Action’s name on the right.
Choose the type (Key, Joy Button, Joy Axis, or Mouse Button) to configure the button. Finally, press the “OK” or “Add” button. Editing or
deleting an event from an Action can be done by pressing the
icon or the
icon, respectively.
InputMap Singleton
Input Map is a user interface provided by the editor. The engine will pass all Actions in the Input Map to the InputMap singleton and
automatically add them before the game starts. It is actually the InputMap singleton that does the job behind the scenes.
111111111
22222222InputMap singleton keeps a list of Actions and events associated with each of them. Here you can add an Action, add an event to an Action,
and check if an event matches an Action. It is very useful when you want to save or load user-defined controls. You can get Action and event
lists by calling Listing 7.21.
LISTING7.21 Functions to Get List of Actions and Events in InputMap
Click here to view code image
for action in InputMap.get_actions():
var list = InputMap.get_action_list(action)
Then you can serialize the output to a configuration file by a method that works best for your game.
InputEventAction
InputEventAction is a special subclass of InputEvent. Unlike other subclasses of InputEvent that represent changes in status of input devices,
it represents an Action. It can be used to simulate player actions. For example, a game has “move_up” Action, associated to KEY_UP,
defined in the Input Map. You want to simulate this Action as part of auto-move function. You may achieve this by using Listing 7.22.
LISTING7.22 Using InputEventAction
Click here to view code image
var action_move_up = InputEventAction.new()
func _ready():
action_move_up.action = “move_up”
action_move_up.pressed = true
func auto_move():
Input.parse_input_event(action_move_up) # Input.action_press(“move_up”) also
works
func _process(delta):
if Input.is_action_pressed(“move_up”):
move(Vector2(0, -1) delta)
From the previous code, if auto_move is called, action_move_up is processed as if the player generated it. Since this action is a
“pressed” action as defined by action_move_up.pressed = true, the game continues as if the button is held down indefinitely. You
can pass another “release” Action to stop imaginary button holding or use Input.action_release(“move_up”).
NOTE
Devices Can’t Create InputEventAction
InputEventAction will never be created by input devices. It can only be created via scripting or in the Inspector dock.
Summary
In this hour, you learned the differences between using _input and Input singleton to acquire player inputs. You learned how to respond to
these inputs by using various properties of InputEvent. You were able to apply it with the Sprite node movement. You also learned about Action
and InputMap and how they can be used in place of naming buttons. Finally, you learned about InputEventAction and saw one example
of how it can be used.
Q&A
Q. Which is better: child nodes have their own _input function or only the parent node has _input function and manages it for
its children?
A. There is no solution that works for every project. Both have their pros and cons. For reference, Godot engine codebase in C++ has some
forms. One could say that it is easier to maintain, because there is only one _input function and the relationship with its children can be
relatively easy to guess just by looking through the code. The downside is that the children depend on their parent and cannot work on their
own. Generally, if you want your node to work on its own, put _input function in it, but if this functionality is not the case, you can do it
either way.
Workshop
Answer the following questions to make sure you understood the content of this hour.
Quiz
1. Given event is InputEventMouse is true, event is InputEvent will be true or false?
2. If you want to call a function every time the player scrolls a mouse wheel, what callback functions would be the most appropriate place to
check for input: _ready, _process, _input, or _tree_exited?
3. Regarding the following GDScript of a node inside the Scene Tree (Listing 7.23), how many lines of the text “Godot” would be printed if
you type in the game window, one letter at a time, “g,” “o,” “d,” “o,” and “t,” then release all keyboard buttons?
111111111
22222222LISTING7.23 Quiz 3: GDScript of a Node Inside Scene Tree
Click here to view code image
extends Node
func _input(e):
if e is InputEventKey && !e.echo:
print(“Godot”)
4. Regarding the following GDScript of a node inside the Scene Tree (Listing 7.24), what would be printed to the output console if, after
“READY” is printed:
1. The left mouse button is clicked?
2. The mouse cursor is moved inside the game window?
3. The “Escape” key is held down?
4. The letter “M” key is held down?
5. The “Enter” key is released?
LISTING7.24 Quiz 4: GDScript of a Node Inside Scene Tree
Click here to view code image
extends Node
var event = Sprite.new()
func _ready():
set_process_unhandled_input(false)
if Input.is_mouse_button_pressed(BUTTON_LEFT):
print(“Left”)
print(“READY”)
func _process(dt):
if event is InputEventMouseMotion:
print(event.position.x)
if Input.is_key_pressed(KEY_ESCAPE):
print(“Quit”)
func _input(e):
if e is InputEventKey && !e.pressed:
print(“Key”)
elif e.is_action(“ui_accept”):
print(“Enter”)
func _unhandled_input(e):
if e is InputEventKey:
print(“Unhandled Key”)
5. Regarding the same GDScript in problem number 4, with the set_process_unhandled_input(false) line removed, what is the
function you would call to prevent “Unhandled Key” from being printed when “Key” or “Enter” is printed because of the same InputEvent?
Answers
1. true. InputEventMouse is one of the subclasses of InputEvent.
2. _input. Mouse-wheel scrolling cannot be reliably detected by querying Input singleton.
3. Ten lines—one for pressing and one for releasing, times five.
4.
1. Nothing; the input event is queried once before “READY” is printed and is never checked for again.
2. Nothing; “Left” is never printed because “event” is Sprite, not InputEventMouseMotion.
3. “Quit”; every frame as long as you hold it down.
4. Nothing; “Key” will print once you release it.
5. “Key”; it is not “Enter” at the releasing moment because the event matches the first condition.
“Unhandled Key” is never printed because of set_process_unhandled_input(false).
5. get_tree().set_input_as_handled()
Exercises
Try to execute the following exercises to get more acquainted with input handling inGodot engine:
1. Remake “Moving the Sprite using Keyboard” example to make use of Actions.
2. Add a WASD control scheme to the “Moving the Sprite using Keyboard” example. (The “W,” “A,” “S,” and “D” keys are up, left, down, and
right, respectively.)
111111111
22222222*3. Add joystick directional buttons to the “Moving the Sprite using Keyboard” example. Your example should support both keyboard and
joystick controlling like most games.