how to detect click inside collisionobject2d

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By billyshears

Started my first godot project and have now gotten stuck …

The main scene is a room, I spawn a box (kinematicbody2d) with add_child. The box falls to the floor of the room and stops. This part works fine. I then need to detect a mouse click inside the box.

I tried adding _input(event) function to detect a left mouse click inside the box. This kind worked or so I thought, unfortunately the problem is it doesn’t matter if I click in or outside of the box, _input(event) treats it the same, as long as I left click somewhere in the room.

I was then informed that I should use _input_event() to detect inside the box and make the kinematicbody2d pickable. Then check if event type is InputEventMouseButton and event is pressed. I’ve tried this but it doesn’t detect any mouse click, never gets into the method when I click anyway.

It was also suggested I add an area2d with a collisionshape as a child to the parent kinematicbody2d. I tried this and still couldn’t get the mouse click to fire off the input_event(). I didn’t like having a collisionshape for the kinematicbody2d and a duplicate for the area2d anyway but would do this if I had to. Not sure if I did that part correctly.

I am only getting a result from _input(). I added print(event) in that function and it does print InputEventMouseButton … just doesn’t ever get to _input_event() when I try to use that instead with left mouse click.

It was also suggested I use _unhandled_input(event) but I do not even no where to start with that one.

So, as a beginner I am failing miserably somewhere.

  1. Any idea what I am missing here? Could it have something to do with main scene add_child? Any suggestions what to try next?

  2. Also, more boxes could fall and they should collide with the box below it. With that in mind and the fact I need to detect a click inside each box, is kinematicbody2d the correct node to start with?

Thanks for any assistance!

:bust_in_silhouette: Reply From: Jowan-Spooner

Hi billyshears,
I have created this very basic scene setup. Follow the instructions and there should be no problems. From what you described I would say you forget to do the signals connection (11-14), but I don’t know. Do you know how signals work? It’s well explained here. For me it works.

  1. Use Godot 3.1
  2. Create a new 2D Scene.
  3. Create a new PhysicsBody2D. I used a RigidBody2D because I didn’t wanted to write the gravity but you can use a KinematicBody2D too. Name it Box.
  4. Add a Sprite as a child of your Box node.
  5. Set a texture to that Sprite.
  6. Add a CollisionShape2D to as a child of your Box node.
  7. Create a new shape by clicking into the CollisionShapes shape property.
  8. Scale it so it fits to your sprite.
  9. Select your Box node and add a script to it.
  10. In the inspector set the pickable property to true.
  11. Now head over to the signals tab (same place as the inspector).
  12. Under CollisionObject2D double click input_event(bababa).
  13. In the Popup box select your Box node, leave everything else untouched and click Connect.
  14. The Script editor should have opened. There should be a function called _on_Box_input_event(viewport, event, shape_idx)

Delete the pass and type in

if  event is InputEventMouseButton and event.pressed and event.button_index == BUTTON_LEFT:
     print("lol it works")
     queue_free()

Hope it helps. Good luck!

Still not working for me … but I have more info.

I run the main scene and the Box drops to the floor, I click and still get nothing.

However, when I run the Box scene by itself it works!!! I can click the box and see “lol” in the Output before it drops off screen.

So does this have something to with how I am spawning from the main scene? Here is my code:

onready var box = preload("res://Scenes/box.tscn")

func _ready():
      new_box()

func new_box():
      var new_box = box.instance()
      add_child(new_box)

billyshears | 2019-05-28 19:23

Also, thanks so much for your steps to follow. I got too excited when it worked on it’s own that I forgot my manners.

billyshears | 2019-05-28 19:41

:bust_in_silhouette: Reply From: kidscancode

The API provides for detecting clicks on any CollisionObject2D:

As the doc suggests, connect the input_event signal and you’ll be able to detect mouse events. The input_pickable property enables/disables this functionality.

The other input handling methods you listed are more general - they detect inputs but are not tied to the object’s shape, so you would get a click event from anywhere on the screen.

For example, I have the following code on a RigidBody2D that I want the player to be able to click to pick up:

func _input_event(viewport, event, shape_idx):
    if event is InputEventMouseButton:
            if event.button_index == BUTTON_LEFT and event.pressed:
                print("clicked")

Don’t forget to connect the signal.

Thanks for the info, I was able to get this working on the scene itself. However, when I instance from my main scene, it no longer detects click inside.

billyshears | 2019-05-28 19:25

What else does your main scene contain? Control nodes will consume events first, and CollisionObject is last in the flow. See Using InputEvent — Godot Engine (latest) documentation in English for details.

kidscancode | 2019-05-28 19:29

my main scene is a Node2d

Children of main are:

  • TextureRect for background image
  • Staticbody2d for floor

Nothing else in the script other than what I provided.

Thanks!

billyshears | 2019-05-28 19:46

TextureRect is a Control node, and it’s consuming the clicks. See Control — Godot Engine (latest) documentation in English

kidscancode | 2019-05-28 20:00

Yes, I just deleted the TextureRect and it works … thanks for solving!

ps - what node should I use for background?

billyshears | 2019-05-28 20:01

TextureRect is fine if you set mouse_filter to Ignore. Or you can use a Sprite. Either one displays a texture.

kidscancode | 2019-05-28 20:04

Wow … yeah went back and set to ignore now everything works with how I originally had it.

Thanks again!!!

billyshears | 2019-05-28 20:24

:bust_in_silhouette: Reply From: bdani2000

I couldn’t get an elegant solution to work with _unhandled_input, so I’m using this function:
I’m using a singleton named Globals to get a reference to the camera.

func is_point_inside_collision_shape(point: Vector2, cs:CollisionShape2D):
	var r = RectangleShape2D.new()
	r.extents = Vector2(1,1)
        var cam = Globals.get("currentCamera")
	return cs.shape.collide(cs.get_global_transform(), r,
		Transform2D(0, point + cam.get_global_transform().get_origin()))

I then use this function inside _unhandled_input with event.position and a collision shape as arguments

if is_point_inside_collision_shape(event.position, collision_shape):
   # do something

This is exactly what I ended up doing.

I’d love to know if there’s a better way to handle _unhandled_input, as it seems like something that should be handled by the engine more cleanly, but it works for me, thanks

npoult | 2021-08-01 17:36