Do input events have to be handled in _process()?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By denim_tuesday

Forgive me, I’m new to this.

I’ve follow a few tutorials and they all handle input events in _process(). Like so:

func _process(delta):
    if Input.is_action_pressed("ui_left"):
        print("You pressed left!")
    pass

Now, as a novice, I understand that _process() is called once per frame. It also has the delta property which can be used to calculate movement but, my gut tells me not to handle events in process. At least (and I know I’m replacing it with a switch statement) I am keeping the if statement out of _process(). Something like:

func _unhandled_input(event):
    match event.as_text():
        "Left":
            _move_left()
    pass
    
func _move_left():
    print("You pressed left!")

Of course, now I can’t access delta. move_left() could be called at any time in relation to the frame being displayed.

Can anyone offer any advice/examples on how to refactor event handling out of process()? Or must it absolutely happen in there? Should input events be handled dead on exactly the time the frame is displayed?

I aplogise for the rambling.

:bust_in_silhouette: Reply From: hilfazer

They don’t have to.
If you handle input in _process() you will be unable to stop input from getting to your game when, for example, you are displaying game menu without doing some additional work.

With _unhandled_input() input can be stopped by using set_input_as_handled() in your Control (game menu for example) node. It’s something like this:

func _unhandled_input(event):
	get_tree().set_input_as_handled()

It captures and throws into the void any input not used by your Control.

Pausing your game can be an option to prevent it from processing input but in multiplayer games pause it not really an option.

There’s a problem with _unhandled_input() however. If you rely on is_action_pressed() and is_action_released() and you open game menu between those 2 events, _unhandled_input() of your game will not receive is_action_released() event. It will go to game menu instead.

My idea to deal with this problem is to use Input.action_release( ) method for each action that may need releasing when you are spawning your GUI element.

Edit:
One more thing to keep in mind is order of calling _unhandled_input() method in your nodes. It’s called from bottommost node to the topmost (in a scene tree). If node has a child with this method it is first called for a child.

I don’t know if it’s guaranteed by Godot but that’s how it now works in 3.0.

Now I’m not sure why I get different behaviour when handling input events in _process() that when handling them outside.

I have this:

var action

func _ready(delta):
	set_process(true)

func _process(delta):
    if action:
        call(action, delta)
        action = TYPE_NIL
	
func _move_left(delta):
	move_and_slide(Vector2(-200,0))

func _unhandled_input(event):
	match event.as_text():
		"Left":
			action = ("_move_left")
	get_tree().set_input_as_handled()
	pass

When the left button is pressed, _unhandled_input(event) sets action to "_move_left()". The next time _process is called, move_left() gets fired. However, the movement of the node is noticeably different from when doing it this way:

func _process(delta):
	if Input.is_action_pressed("ui_left"):
		_move_left(delta)

func _move_left(delta):
	move_and_slide(Vector2(-200,0))

The sprite moves smoothly in the second example but in the first, it moves in tiny steps.

But in each, I’m calling “_move_left()” from the same place, the same amount of times. Aren’t I? So, I’m led to believe it had something to do with the way I’m handling the event. At the moment, I don’t understand why there’s a difference.

denim_tuesday | 2018-02-26 17:56

I think _process() is called much more often than _unhandled_input(). I did movement by having a member Vector2 (called m_movement) and modifying it like this:

func _unhandled_input(event):
	if (event.is_action_pressed(m_moveDownAction)  or event.is_action_released(m_moveUpAction)):
		m_movement.y += 1
	if (event.is_action_pressed(m_moveUpAction)    or event.is_action_released(m_moveDownAction)):
		m_movement.y -= 1
	if (event.is_action_pressed(m_moveLeftAction)  or event.is_action_released(m_moveRightAction)):
		m_movement.x -= 1
	if (event.is_action_pressed(m_moveRightAction) or event.is_action_released(m_moveLeftAction)):
		m_movement.x += 1

( this code isn’t perfect, it may malfunction if you generate your own release events via Input.action_release() )

I use variables for my actions (so each player can have its own) by you can use action names from your input map.

This vector is used in _physics_process(), but _process() can be used as well.

hilfazer | 2018-02-26 18:19

The general idea is this:
You have a member variable that is modified by _unhandled_input() and used (but not modified) by _process() or _physics_process()

hilfazer | 2018-02-26 19:10

I think _process() is called much more often than _unhandled_input().

Ah! I assumed it was the other way round. I can’t quite follow it in my head but action = TYPE_NIL was the cause of the juddering.

denim_tuesday | 2018-02-26 21:20

The general idea is this: You have a member variable that is modified by _unhandled_input() and used (but not modified) by
_process() or _physics_process()

This is the best comment!

t8 | 2021-11-26 14:39