Federico-Ciuffardi / GodotTouchInputManager

Asset that improves touch input support (includes new gestures) in the Godot game engine. It also translates mouse input to touch input.
MIT License
526 stars 35 forks source link

Missing CollisionObject input_event integration. #16

Open Splizard opened 2 years ago

Splizard commented 2 years ago

I've found this project incredibly helpful for building the mobile/touch camera input for my 3D project. I wanted to use the SingleTouch/SingleTap events for selecting objects in the scene howevever the events don't appear to propogate through to the CollisionObject input_event signal.

https://docs.godotengine.org/en/stable/classes/class_collisionobject.html

I've had a look through the Readme here and also at the Godot documentation, there doesn't appear to be a simple way to enable this.

Here's two ideas on how this could be achieved:

  1. emit a raycast from the primary camera whenever these events are triggered and then call _input_event on the collider.
  2. provide a helper function for converting the Godot touch events inside of a _input_event handler (leveraging the existing raycasts), for example:
    func _on_input(camera, event, position, normal, shape_idx):
    var gestures = InputGestureEvents(event)
    for gesture in gestures:
        // handle events
Federico-Ciuffardi commented 2 years ago

If I understand correctly this is similar to what happens with the buttons discussed in PR #19. I think this is something that should be fixed in the Godot engine itself. But for now I like your idea of using a raycast. I don't have much experience developing 3D games in godot but I put together something basic that should work, if you want to try it add the following code to the _emit method in InputManager.gd:

if "position" in val:
        var camera = get_viewport().get_camera()
        var from = camera.project_ray_origin(val.position)
        var to = from + camera.project_ray_normal(val.position) * (camera.far)
        var root = get_tree().get_root()
        if root is Node:
                var inter = root.get_world().direct_space_state.intersect_ray(from,to)
                if !inter.empty():
                        inter["collider"]._input_event(camera, val, inter["position"], inter["normal"], inter["shape"])

Also here is an example project I used for testing: example.zip

Some comments about the code:

JeffBusch commented 2 years ago

I am running into this issue in my 2D project. I planned on using the SingleTouch/SingleTap events on an Area2D input_event() instead of using the button nodes (mouse emulation) since my project is mobile only. I'm assuming the raycast solution is only relevant in a 3D project?

Maybe I'm approaching this incorrectly but my goal was just to make my own buttons that use the GTIM events.

Federico-Ciuffardi commented 2 years ago

Mouse emulation and button nodes should work even if your project is mobile only, but maybe you have another reason.

The raycast solution, as far as I know, is not useful in 2D, but in 2D it's not that hard to do it manually. I mean you need the coordinates of the 4 vertices that define the Area2D (assuming it's a rectangle) and then with that you can check if the position of the event is inside the Area2D. I'm not sure if it makes a difference, but you should also be able to do this kind of manual detection with the button nodes, in fact all you need is 4 dots describing a rectangle.

dpnnl commented 7 months ago

If I understand correctly this is similar to what happens with the buttons discussed in PR #19. I think this is something that should be fixed in the Godot engine itself. But for now I like your idea of using a raycast. I don't have much experience developing 3D games in godot but I put together something basic that should work, if you want to try it add the following code to the _emit method in InputManager.gd:

if "position" in val:
        var camera = get_viewport().get_camera()
        var from = camera.project_ray_origin(val.position)
        var to = from + camera.project_ray_normal(val.position) * (camera.far)
        var root = get_tree().get_root()
        if root is Node:
                var inter = root.get_world().direct_space_state.intersect_ray(from,to)
                if !inter.empty():
                        inter["collider"]._input_event(camera, val, inter["position"], inter["normal"], inter["shape"])

Also here is an example project I used for testing: example.zip

Some comments about the code:

  • The ray must have a length. I used camera.far because I assume that is a reasonable length.
  • I check if the root of the tree is a Node to check if the scene is 3D before trying to get the direct_space_state but I don't know if there is a better way to do it (there probably is).
  • The code can also be placed in the _input or _unhandled_input method of another node where it must only be executed if the processed event is non-native. This has the advantage of not being specific to GDTIM custom events.

Could you port this solution to Godot 4? I tried the code below:

    if DEBUG: print(val.as_string())
    emit_signal("any_gesture", sig, val)
    emit_signal(sig, val)
    Input.parse_input_event(val)
    if "position" in val:
        var camera = get_viewport().get_camera_3d()
        var from = camera.project_ray_origin(val.position)
        var to = from + camera.project_ray_normal(val.position) * (camera.far)
        var root = get_tree().get_root()
        if root is Node:
            var ray_query = PhysicsRayQueryParameters3D.new()
            ray_query.from = from
            ray_query.to = to
            var inter = root.get_world_3d().direct_space_state.intersect_ray(ray_query)
            if !inter.is_empty():
                inter["collider"]._input_event(camera, val, inter["position"], inter["normal"], inter["shape"])

But I get the following error: Invalid call. Nonexistent function '_input_event' in base 'StaticBody3D'.