How to Differentiate Between GUI and World Clicks in Godot 4

Hi everyone,

I’m working on a project in Godot 4.3 and facing an issue with differentiating between clicks on GUI elements (like HUD buttons) and clicks intended for world interaction (e.g., moving a player character). Here’s the scenario:

Setup:

I have a CanvasLayer for HUD, which contains UI elements like buttons, health bars, and skill slots.
I also have a game world where clicks should trigger movement or actions, but only when they’re not on GUI elements.
Problem:

I’m using _unhandled_input in my world interaction scripts to handle clicks, but this also triggers when clicking on GUI elements.
I’ve tried using gui_pick() to detect clicks on GUI elements, but it seems to interfere with other events like button pressed signals. When a GUI element is clicked, I need the click to trigger the appropriate UI behavior and not propagate to _unhandled_input.
Current Approach:

I’m consuming the input in _gui_input for GUI elements and using event.accept() for clicks on the HUD.
However, this makes buttons in the HUD unresponsive because the event never reaches their pressed signal.
Desired Behavior:

Clicks on HUD elements should trigger their specific behavior (e.g., button pressed events).
Clicks outside the HUD should trigger world interaction like movement or targeting.
Questions:

Is there a recommended way to differentiate between GUI clicks and world interaction clicks in Godot 4?
How can I ensure that events for GUI elements like buttons still function correctly while preventing them from propagating to _unhandled_input?
Any guidance or examples would be greatly appreciated! Thank you in advance for your help.

Viewport.set_input_as_handled()

Thank you for your reply.
While I’ve been advised to use set_input_as_handled(), I’m having trouble getting it to work as expected.

Current Issue

  • Using get_viewport().gui_pick(event.position) doesn’t seem to correctly detect UI elements consistently in my setup.
  • I need a concrete example of how to manage UI clicks versus game interactions effectively.

Node Setup
Here’s my current node structure for reference:

Main
├── GameLayer (Node2D)
│   ├── Player (CharacterBody2D)
├── UILayer (CanvasLayer)
│   ├── HUD (CanvasLayer)
│   │   ├── UIContainer (Control)
│   │   │   ├── Button (Button)

Relevant Code
Here’s how I’m currently handling inputs:

# Player.gd
func _input(event: InputEvent) -> void:
    if (event is InputEventMouseButton and event.pressed) or (event is InputEventScreenTouch and event.is_pressed()):
        var position = get_global_mouse_position()
        # Logic to move the player or interact with the game

# UILayer.gd
func _input(event: InputEvent) -> void:
    if event is InputEventMouseButton and event.pressed:
        var clicked_node = get_viewport().gui_pick(event.position)
        
        if clicked_node and is_instance_valid(clicked_node):
            print("Clicked on UI element:", clicked_node.name)
            get_viewport().set_input_as_handled()

Desired Behavior

  • Clicking on UI elements like Button should stop the input from propagating to the player (or other gameplay elements).
  • Clicking outside of UI elements should allow for gameplay interaction, such as moving the player character.

It is recommended that the Player.gd use _unhandled_input so all ui can have a go first with the event.

As you can see _input happens before gui events take place.

1 Like