最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

mouseevent - How do I make my Godot 2D game resolve input on colliders before control? - Stack Overflow

programmeradmin5浏览0评论

I am making a 2D game using Godot 4 and gdscript, and I am running into a slight issue with handling input.

The background for the game is a TileMapLayer. On it there are several non-overlapping CharacterBody2D objects with colliders. Finally, there is a Control to handle input outside the characters.

If I set the Control's mouse_filter to MOUSE_FILTER_IGNORE, then I can click on the characters and the click is detected by their _on_input_event function, but I cannot detect a click on the background (of course, as it is ignored). If instead I set it to MOUSE_FILTER_PASS, then the Control's _gui_input function runs as expected, but the _on_input_event function of the characters does not fire, since as far as I understand, the input is only passed to other controls.

My intended behaviour would be that a click would first activate the _on_input_event function of any character underneath the pointer, and if none was detected, then fire the _gui_input of the Control. As an example, if I click on the 'duck' (CharacterBody2D) then I score a point, but if I miss it (background/Control) then I lose one.

The three solutions I can think of all sound rather computationally expensive and inefficient: I could loop over the characters in the scene, set a different layer collider on the background TileMapLayer, or handle input detection in the _process method.

Is there a solution that I have not yet found? Thanks!

I am making a 2D game using Godot 4 and gdscript, and I am running into a slight issue with handling input.

The background for the game is a TileMapLayer. On it there are several non-overlapping CharacterBody2D objects with colliders. Finally, there is a Control to handle input outside the characters.

If I set the Control's mouse_filter to MOUSE_FILTER_IGNORE, then I can click on the characters and the click is detected by their _on_input_event function, but I cannot detect a click on the background (of course, as it is ignored). If instead I set it to MOUSE_FILTER_PASS, then the Control's _gui_input function runs as expected, but the _on_input_event function of the characters does not fire, since as far as I understand, the input is only passed to other controls.

My intended behaviour would be that a click would first activate the _on_input_event function of any character underneath the pointer, and if none was detected, then fire the _gui_input of the Control. As an example, if I click on the 'duck' (CharacterBody2D) then I score a point, but if I miss it (background/Control) then I lose one.

The three solutions I can think of all sound rather computationally expensive and inefficient: I could loop over the characters in the scene, set a different layer collider on the background TileMapLayer, or handle input detection in the _process method.

Is there a solution that I have not yet found? Thanks!

Share Improve this question asked Mar 17 at 15:42 Sap1ensSap1ens 156 bronze badges 3
  • i think using a control to registered all missed clicks is the wrong way to do it. add a script to the level and there check all clicks and compare the globalPos of the click with all the globalPos of ducks and determine this way if you hit or not. Or is there another reason you used a Control for this? – Molbac Commented Mar 18 at 12:01
  • @Molbac I mostly was using a control because it was recommended in the resources I used to learn. My rationale for not comparing clicks with positions is that when eventually I have a lot of objects it feels like it would be inefficient to iterate over them. – Sap1ens Commented Mar 19 at 20:26
  • i think if you dont have hundreds of ducks flying around, this approach should be fine. you could also use the mouse_entered / mouse_exited signals on the ducks to track this way which ducks are under the mouse cursor. – Molbac Commented Mar 20 at 10:21
Add a comment  | 

1 Answer 1

Reset to default 0

I can think of a solution that leverages InputEvent and its related callbacks without hacking a Control node to capture mouse clicks outside of other objects.

Arrange your Scene so that all CharacterBody2D ducks come after the TileMapLayer background in Tree order:

This is important, because we'll use _unhandled_input() to check for clicks (not _input() because I suppose you want UI to receive user input first), and since input events propagate in reverse depth-first order, we need the ducks to receive mouse clicks before the background.

Let's override _unhandled_input() for the ducks (based on this GameDev.SE answer):

# CharacterBody2D
func _unhandled_input(event: InputEvent):
    if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
        var query := PhysicsPointQueryParameters2D.new()
        query.position = get_global_mouse_position()
        var result := get_world_2d().direct_space_state.intersect_point(query, 1)
        if result and result[0].collider == self:
            print("Clicked %s" % name)
            get_viewport().set_input_as_handled()

The important bit here is Viewport.set_input_as_handled(): if, upon clicking, this duck was below the mouse, we consume the current InputEvent; otherwise, it keeps propagating among other ducks. (I'm assuming no other solid shapes will ever overlap, hence the 1 optional argument to limit the size of result.)

If no duck consumed the InputEvent so far, then it will eventually reach the TileMapLayer background. At this point, we are certain no duck was hit, therefore, we can react to the player's miss hit:

# TileMapLayer
func _unhandled_input(event: InputEvent):
    if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
        var where: Vector2i = local_to_map(get_local_mouse_position())
        if not get_cell_source_id(where) == -1:
            print("Hit a cell")
        else:
            print("Hit outside")
        get_viewport().set_input_as_handled()

Here, we always consume this InputEvent so that it stops propagating (unless you want nodes higher in the hierarchy to perform additional game logic).

发布评论

评论列表(0)

  1. 暂无评论