_input call Order and _input_event

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Diego Machado
:warning: Old Version Published before Godot 3 was released.

Hey guys, Godot noob here!

First of all, I know that the Input processing order is as follows:

Godot Input Order

I’m trying to make a Area2D with a ColliderShape2D (circle) clickable through CollisionObject._input_event().

It’s basically a target. If I hit any one of the targets it’s a score, else is a miss.

I started implementing the miss feature using input()on the stage script, but then I discovered that it’s fired first and I can’t stop the input propagation in a CollisionObject._input_event(), so, everytime I hit a target I also get a miss.

Then I decided to make Area2D big as the stage to act as a miss collider. It works as expected, but it’s input_event()is fired before the target’s input_event(). So, again, first a miss then a hit.

  • How can I do this in a more elegant way?

  • I’ve thought about Raycasts. Is it simple to shoot a 2d raycast ‘through’ the screen and detect a hit? How could I do that?

  • Can I force the nodes input_event callback order? I’ve tried to use the Priorityproperty but it doesn’t seem to work.

  • What it’s ‘Priority’ used for then?

  • What if I have overlapping Area2D’s?

Thanks! Hope you guys can help me! :slight_smile:

as a temp solution for your problem only i believe you can remove the area2D and detect the hit using the geometry of a circle, calculate the distance of the mouse from the center of your circle and if it is less the 1 radius than it’s inside the circle, else wise it’s not…

rustyStriker | 2017-05-17 20:06

Actually that was kind of my first solution. I created a Rect2 based on the size of my Node’s sprite and used has_point()to check if it was hit.

It worked, but it was painfull to tweak the size of it, because I wanted to make the hit area bigger than it’s sprite. I wanted some solution to make possible to see the hit area in the editor.

Diego Machado | 2017-05-17 20:24

:bust_in_silhouette: Reply From: mollusca

Not sure if this is the best solution, but it seems to work:

extends Area2D

var hit = false
var clicked = false

func _ready():
    set_process_input(true)
    set_process(true)

func _input(event):
    if event.type == InputEvent.MOUSE_BUTTON and event.pressed:
        clicked = true

func _input_event(viewport, event, shape_idx):
    if event.type == InputEvent.MOUSE_BUTTON and event.pressed:
        hit = true

func _process(delta):
    if clicked:
        if hit:
            print("hit")
            hit = false
        else:
            print("miss")
        clicked = false

This does not work if I have multiple targets on the screen. If I hit one, I’ll miss all the others…what I actually want is to compute a miss if I do not hit any of them.

Diego Machado | 2017-05-18 11:17

I see you found a solution already, but here’s a way you could gather hit-info from several targets with the above method. The _input() and _process() functions in the targets can be removed and this script is added to the parent node of the target Area2Ds:

extends Node2D

var nodes = []
var clicked = false

func _ready():
    nodes = get_children() #assumes the only children are the Area2Ds
    set_process_input(true)
    set_process(true)

func _input(event):
    if event.type == InputEvent.MOUSE_BUTTON and event.pressed:
    clicked = true
	
func _process(delta):
    if clicked:
        var hit_count = 0
        for n in nodes:
            if n.hit:
                hit_count += 1
                n.hit = false
        if hit_count > 0:
            print("Hits: ", hit_count)
        else:
            print("Miss")
        clicked = false

mollusca | 2017-05-18 12:05

:bust_in_silhouette: Reply From: Diego Machado

Ok, finally came up with a solution:

I started searching how to do some raycasting programmatically and found the Physics2DDirectSpaceState which does not only shoots raycasts but have the intersect_point() method, exactly what I needed.

So, in the _input() of my stage I used:

if event.type == InputEvent.SCREEN_TOUCH and event.is_pressed() 
	hit_position = event.pos

hit_position is a stage variable that holds the last click / tap position.

Now we use the PhysicsSpace to check if any of the targets got hit on the _fixed_process(). It’ s a physics action, so we use the physics process:

if hit_position != null:
    hit_targets = get_world_2d().get_direct_space_state().intersect_point(hit_position, 1)

    if not hit_targets.empty():
        hit_targets[0].collider._get_hit()
    else:
        _miss()
		
    hit_position = null

This code did not worked at first, while being the same used in the docs. After having a long and hard time figuring it out, let me make this clear:

Physics2DDirectSpaceState intersect functions do not work with
Area2D + ColliderShape2D, they need a PhysicBody2D + ColliderShape2D to compute
intersects. So, changing the target node from Area2D to
StaticBody2D was the final trick to make it work.

As a bonus anwser for my last question, in the intersect_point(hit_position, 1) the 1 represents how many bodies do you want to return from the intersect, the default is 32. I wanted to eliminate only one target per click / tap, so I used 1. You can use this to handle multiple collisions from overlapping nodes, and use a for loop to iterate and process them as you wish.

Thanks for the anwsers @rustyStriker and @mollusca!

I chose to only use _unhandled_input(event) of the backgroud node,
from there you check collisions with other nodes in the order you want to

a good reference :
How to have CollisionObject2D._input_event() only trigger for the topmost collision object? - Archive - Godot Forum

jeyzu | 2019-03-21 20:32