Key inputs being lost at random after focusing button

Godot Version

4.2.1.stable

Question

I’ve built a very basic platformer prototype, and am trying add a pause menu. The relevant part of the project structure is a Node2D as a root node, and the pause menu containing a few buttons.

The root node loads, instances, and adds the pause menu as a child when it enters the scene tree. The pause menu is set to process mode When Paused, while everything else is set to Inherit.

The code to bring the pause menu is as follows:

func _input(event):
	if Input.is_action_just_pressed("menu_change"):
		pause_menu.show()
		get_tree().paused = true

The code to unpause and hide the menu again is the following:

func _input(event):
	if Input.is_action_just_pressed("menu_change"):
		hide()
		get_tree().paused = false

“menu_change” is currently bound to “J” on my keyboard, although I have used the Esc key as well.
I am currently using _input() rather than _unhandled_input() for testing reasons, and set the inputs as handled when using the latter.

The issue I am having is that I open the pause menu, and then immediately hit the “menu_change” key again and it goes away. However, if I put UI focus on one of the buttons in the pause menu, when I hit the “menu_change” key, it doesn’t always run the code to hide the menu until I hit the key again, or even worse, it skips several key presses, or waits several key presses to skip one. It also sometimes doesn’t skip any. I haven’t found any consistency in what happens.

If it matters, I am running Godot on Linux Mint 21.1 Cinnamon.

Am I doing something wrong, or have I misunderstood how a part of the engine works? Or is this an issue with the engine?

Thanks in advance for any help!

not sure, but have you tried using

event.is_action_pressed("menu_change")

in _input method instead of Input.is_action_just_pressed?

I think you should only be querying the event object inside of that method b/c the flags on Input singleton are frame-dependent. For example, is_action_just_pressed will only return true for 1 frame, afaik.

ps: I didn’t even know about _input(event) until just now… just switched my UI CanvasLayer to listen to that instead of doing it in _process and I don’t have any issues with button focus + pressing when I use event.is_action_pressed. But yeah, thx for improving my code a little accidentally =)

1 Like

Tested your suggestion. Still no improvement on the issue. Continuing to try troubleshooting it myself. I believe you are right about is_action_just_pressed, but it doesn’t appear to be the source of the issue.

And, happy to have accidentally helped your code :smile:

Ahh. Well, you can always upload the project so we can take a look.

Yeah, something to play with would help.

1 Like

I’ve found that having one of the buttons in the pause menu grab focus right away, rather than focusing on a button by user input, circumvents the problem, and makes more sense design-wise anyway. I will make some time to upload the version where I discovered the issue, though, so you guys can poke at it.

Edit: Immediately after writing this, I discovered that I had not, in fact resolved the issue. It actually seems to be related to moving the mouse cursor while attempting to toggle the menu. I’ve uploaded the project folder as a .zip: prototype folder zip

Reproduction is to play the project, press the start button, press the escape key to bring up the pause menu, move the mouse around constantly, and try to make the pause menu go away by pressing escape again. It doesn’t work, until you stop moving the mouse. Based on print statements, it looks like the input is being doubled.

Having discovered this, I may be able to track the issue down, but if you guys want to have a go, that would be great, as well.

2 Likes

I have tested your project on Windows and I wasn’t able to replicate the problem there.

Same here. Tested on Mac and don’t have any problems, both using _unhandled_input in pause_menu as you had it, and if I change it to _input. Could be a Linux issue?

A few side notes about your code, feel free to ignore obviously.

  1. Consider using the built-in “ui_cancel” instead of making own “menu_changed” action. That way it works on any platform Godot supports. For Mac/PC, that’s ESC key, for consoles it will be w/e the button on w/e controller is default for the console, Android that can be the Back key, etc. So:
if event.is_action_pressed("ui_cancel"):
  1. Do not expose internal implementation to external objects. For example, calling .focus() on pause_menu from other menus is, imho, a violation of this principle. It shouldn’t be anyone’s concern that pause_menu auto-focuses on display. Instead, define signals in child menu like ‘signal dismissed’ and fire it when user presses back button. In the parent menu, connect to it and name func descriptively, such as:
controls_menu.dismissed.connect(_on_controls_menu_dismissed)

It is the parent menu that should be showing/hiding child menus, not child menu that should be calling .show() and/or .focus() on parent menu.

Why am I saying all this? B/c I decided to test without the focus just to be thorough and then got error after error as .focus() was used in other places in the code and now that method didn’t exist. Changing internal implementation of an object should not break any other objects; this creates maintenance nightmare.

For this reason, tbh, calling show() or hide() on external scenes is just not practical since usually more has to happen for a given scene like new menu to appear. So I usually put that logic in my own display() function on child scene/menu that a parent scene/menu calls.

Maybe you already know and just did it as a shortcut on a test project, ofc, so np, just thought I’d mention it.

  1. For movement input, a convenient system method is Input.get_vector(). It does 2 things: a) it works whether controls are WASD or arrow keys or controller joystick or dpad (and lets you specify custom dead zone for controllers, if you want), and b) it allows you to reduce your _physics_process by over half, if you’d like:
	var move_vector = Input.get_vector("move_left", "move_right", "move_down", "move_up")
	move_vector.y *= -1 # adjust to godot coordinate system where "up" is negative
	
	if up_direction == Vector2.UP or up_direction == Vector2.DOWN:
		if Input.is_action_just_pressed("jump") and is_on_floor():
			velocity.y += (up_direction * jump).y
		elif move_vector.x != 0:
			velocity.x = sign(move_vector.x) * speed
		elif abs(velocity.x) > 2 and is_on_floor():
			velocity.x *= slip
		elif is_on_floor():
			velocity.x = 0
		velocity.y -= up_direction.y * gravity_force
		
	elif up_direction == Vector2.LEFT or up_direction == Vector2.RIGHT:
		if Input.is_action_just_pressed("jump") and is_on_floor():
			velocity.x += (up_direction * jump).x
		if move_vector.y != 0:
			velocity.y = sign(move_vector.y) * speed
			print(velocity.y)
		elif abs(velocity.y) > 2 and is_on_floor():
			velocity.y *= slip
		elif is_on_floor():
			velocity.y = 0
		velocity.x -= up_direction.x * gravity_force

I feel like the above can be shortened even more, but this is all the time I had.

  1. I saw you’re manually calculating position for your pause menu, may I ask why? Generally, you want to rely on Godot’s built-in layout methods/controls. For one, do not use any Node2Ds for UI in general, use Control and its children only (in general). But for root nodes specifically, use CanvasLayer as the root node. Just right-click pause_menu root node, select Change Type… and select Canvas Layer. In 20 seconds I changed your tree to this:

Screenshot 2024-01-21 at 4.32.40 PM

Just go to CenterContainer and under Layout → Anchor Preset select “Center”, and for VBoxContainer go to Theme Overrides → Constants and set Separation property (like 30). And boom, menu is auto-positioned in the center for you no matter the resolution, etc.

  1. This one is inconsequential, but instead of:
	if event.is_action_pressed("ui_cancel"):
		if visible == false:
			return
		else:
			hide()
			print("hidden")
			get_tree().paused = false
			get_viewport().set_input_as_handled()

You can just write:

	if visible and event.is_action_pressed("ui_cancel"):
		hide()
		print("hidden")
		get_tree().paused = false
		get_viewport().set_input_as_handled()

Less lines, less indents! :yum:

Hope some of this is helpful, cheers!

1 Like

Sounds like I need someone on Linux to try to replicate the issue, in that case. Would that be worthy of a bug report, if the issue replicates on other Linux systems, but not on other OSs?

As for your other better code suggestions:
1: I don’t know what weird logic I used to decide that I shouldn’t use the UI input events for UI things, but having put it that way, changing to the built-in event is going on the to-do list.
2: Hard-linking things together is a bad habit I have when I’m trying to get something done quickly, since signals take more thought for me to understand how they will work. It is a bad habit because then I never change it after I’ve finished my quick testing. Thanks for yelling at me for that; it probably will help my code in the long term.
3: I’d missed get_vector()! Thanks for pointing it out to me. I’ll be figuring out how it works, now.
4: I’d forgotten how to do this, since I took about a year break from development projects, and couldn’t remember how to make the base of a UI scene. I had figuring out how to make the menus resize cleanly on my to-do list, and I’d like to think I would have rediscovered CanvasLayer and done it right, but probably not. Thanks, yet again, for pointing me in the right direction.
5: This one is weird. I tried writing and statements like you give in your example, but wasn’t getting the expected behavior. I just tried things until I found a configuration that behaved like I expected, and went with that. It also felt like a bug, although I may have been messing something up on my end, and just not catching it.

1 Like

I can confirm the problematic behavior with your project on Linux X11 Xfce. To be honest, I’m not entirely sure, why I’m unable to replicate the problem on Windows.

Your code has the following problem and can be corrected by changing scene_root.gd from

func _unhandled_input(event):
	if Input.is_action_just_pressed("menu_change"):

to

func _unhandled_input(event):
	if event.is_action_pressed("menu_change"):

The problem in your code is, that Input accesses the global state and asks: “has there been any event in the current frame, with a menu_change-action?”. When in the same frame an InputEventMouseMotion and a “menu_change” action happen, then you run into your problem.
The changed version asks: “Is the event, that is passed to the _unhandled_input()-function a menu_changed-action?” and this question is false for InputEventMouseMotion.

As a suggestion for the future, please don’t include the “.godot” folder of your projects, when sharing them as zip-files, because that folder unnecessarily bloats the size of the zip file (for small projects, the .godot folder increases the size by a factor of 10-100).

1 Like

Word. It’s cool to split ifs, ofc, especially for testing. One other way that I usually follow and find clean:


if not visible:
    return

# the rest here.

This worked perfectly. It makes sense now that you have explained it, but I wouldn’t have thought about why those two methods of checking input are different.

And, I will keep your tip about the “.godot” folder in mind.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.