I'm making a small game with Godot, and the player can keep some panels on top of the game display while playing, for easy access to some actions.
The panel is a Control node, and it could appear on top of the main game display, which is of type Node2D. Both the panel and the Node2D nodes have their own handling for mouse clicks, but if I click on the panel, I expect only the panel will get the button press and release events, not the Node2D underneath it.
I made a simple reproducible example.
This is the scene tree and the corresponding 2D view
The input handling is in the Sprite2D and PanelContainer scripts seen in the picture.
The script of the sprite/node2D:
extends Sprite2D
func _unhandled_input(event):
if event is InputEventMouseButton:
print("Mouse pressed=%s event in 2D nodes" % event.pressed)
get_viewport().set_input_as_handled()
The script for the panel:
extends PanelContainer
func _gui_input(event):
if event is InputEventMouseButton:
print("Mouse pressed=%s event in control nodes" % event.pressed)
get_viewport().set_input_as_handled()
According to the Godot documentation, GUI input events (handled by the method _gui_control
) will be processed before unhandled input (by the method _unhandled_input
). And that is indeed the case during normal execution. However, if I am debugging and placing a breakpoint on the print message of _gui_input
in the panel script, the following happens:
_gui_input
and gets to the print statement_gui_input
._unhandled_input
methodSomething like this can be reproduced without a debugger as well.
The behavior in step 6 is not wanted. If the player clicks and releases the click on the panel, I would like to distinguish between the event being on top of a UI element, or it being outside of the UI element (independent of whether it's on the sprite or not). But the release event is sent to _unhandled_input
, which is processed by the game display in the background of the panel. And the player may not see the result of the click behind the panel, leading to a bad player experience.
I cannot make the panel modal (in the sense that while the panel is visible, nothing else can handle mouse clicks) because the player should be able to click on the area outside the panel and have that clicked be handled normally.
I cannot use the _input
method to handle the event because that runs before the _gui_input
, and it doesn't let the Control nodes handle GUI input.
Is my only choice adding a check myself in _unhandled_input
or _input
to see if the position of the 'mouse button release' event is within the bounds of the panel/UI? It doesn't seem like the correct way to do things with Godot, since the _gui_input
method is the one specifically designed to handle GUI input.
Godot can listen to OS notifications about the window losing focus. See doc for sample notifications it can listen to. The _unhandled_input
call can ignore the event if the game window is not focused, so the button release event can be handled differently in that special case.
For example, add a singleton script that listens to the focus in/out notifications:
class_name FocusChecker
extends Node
static var focused: bool = true
func _notification(notification):
match notification:
NOTIFICATION_APPLICATION_FOCUS_IN:
focused = true
NOTIFICATION_APPLICATION_FOCUS_OUT:
focused = false
And you can check the status in _unhandled_input
to decide how you want to process the event.
extends Sprite2D
func _unhandled_input(event):
if event is InputEventMouseButton:
if (FocusChecker.focused):
print("Normal processing of unhandled input")
else:
print("Special case handling of unhandled input")
get_viewport().set_input_as_handled()
You could do a similar thing for only the control losing focus, and not the whole window. You can listen to the focus_exited
signal of the control, and also keep track of whether the last mouse button event was a press or release. If the focus_exited
event happens between a press and a release, set a global flag that can be accessed by input handling methods.