How to get the object I'm clicking on?

Godot Version

4.5

Question

Hi, I have character that can roam around the map and destroy trees/rocks to gather resources, simple stuff


so the blue circle around the character is the area that the player will be able to click on rocks(area2d of the tool im holding), as you can see in the picture I have 2 bodies in the area2d, is there a way to only mine the rock im clicking on and not all the bodies in area2d? so it would be something like this:
if the body is in the area2d of the tool scene(which is instantiated in the player scene)
if im clicking on the body
then mine it

my approach is kinda messy since im a newbie, each rock is an instantiated scene, a script is attached to it that contains its health var
the tool is also instantiated in the player scene, has a script that hold its damage var
so it would be that each time I click on the rock (if the conditions are met) the damage will be reduced from the rock’s health and when it reaches 0 it will be destroyed and drop the resources)
and then theres the player scene which is instantiated in the world

idek if this is the correct approach so if you think I should start from the scratch with a different approach please let me know

CollisionObjects such as Area2D and any PhysicsObject do have a input_event signal that can detect being clicked if input_pickable is true. From there you could check if the clicked object is overlapping with the player’s area, there are a few ways to get that player reference since you’re dealing with collision, but a global may be easiest.

I would put the click detection in the rock scene itself. Then in the future you might, if not in a tool range, do something else with it, like show what it is, in a label or something.

You could then emit a signal like “player_clicked_on_this(object_clicked)” and have the player manager check what is in the current tools range.

I see @gertkeno has said the same thing. I agree with them. Although whether you check it is in range or susceptible to your particular tool or whatever with a global ‘in tool range’ list or a signal or using a player_ref would depend on your game structure and what else you intended to do with clicks etc.

But your approach is good. Just put the click detection in the object would be my suggestion.

Once the player has the object clicked it could apply damage directly back to the object like 'object_clicked.apply_damage(tool_damage_amount). And if the object has the click position, you can apply damage like cracks appearing at the click point.

sorry does this mean I need an Area2D for the rock scene? right now its a StaticBody2D with collision and sprite as children, do I have to switch to Area2D instead? or the Area2D on the tool scene will do the job?

Can you by any chance show me how to apply this? doesn’t have to be the exact scenario but something similar so I can understand it better will really help me if you can spare the time and effort, I have an overall idea of what to do but I don’t think I know how to do it lol sorry

No, the bodies of the rocks are also CollisionObjects, they have a input_event signal which can detect clicks.

What approach are you most comfortable with? Without seeing your code it will be difficult but something like:

In your rock scene’s area2D make it pickable:
image

Then connect the input event signal to a function in your rock script.
image

Something like (not tested):

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

So now when you click on a rock it should print it’s name.

Now here you have many ways forward. One way is to create a signal_manager autoloaded script.
create a script called signal_manager.gd and in project settings under globals add it as an autoload.

signal player_clicked_on_me(object_clicked: Node)

Now back in your rock scene just emit that signal:

# replace
 print("Rock clicked:", self.name)

# with
SignalManager.player_clicked_me.emit(self)

You may want to emit self.get_parent() depending on how you structured your rock scene. Now in your player scene you need to listen for that signal:

func _ready() -> void:
   SignalManager.player_clicked_me.connect(_on_player_clicked_me)

# react to the signal something like
func _on_player_clicked_me(object_clicked: Node) -> void:
     var overlapping_bodies = $YourArea2D.get_overlapping_bodies()
     if object_clicked in overlapping_bodies:
        print("The object is inside my Area2D")
    else:
        print("The object is NOT inside my Area2D")

Now you will need to react to it.

#replace
print("The object is inside my Area2D")

# with something like
current_tool.take_wear_and_tear()
player.play_animation("using_axe")
object_clicked.take_damage(axe_damage_applied)
play_sound("axe_chopping_rock") 

Of course what functions you call and how you call them will all depend on your code and structures etc. The important thing here is now you can tell the rock that was clicked on directly to take any damage you want to apply. You may want to apply fire_damage or impact_damage so your function might be:

object_clicked.take_damage("fire_damage", fire_damage_applied)

Now back in your rock scene, you react any way you want with your rock:

func take_damage(damage_type: String, damage_applied: int) -> void:
     if damage_type == "fire_damage":
            show_label("Immune to fire damage")
      if damage_type == "impact_damage":
            rock_health -= damage_applied

I hope that helps.

When you type these things out it seems ever so long winded, but in all honesty you are just detecting a click, passing a variable, and reacting in code to that variable. All basic stuff really so although it might read complicated (and this is not an edited and well authored teaching manual) it should be straight forward when you get the hang of passing data around.

Anyway, I hope that helps in some way. There are other ways to pass data around too, but this way would work and is relatively simple.

PS I used Area2D here as I thought that was what you were using but as @gertkeno said, if you look in the docs you will see any collision type object is fine:

PPS You don’t have to use your player area2D, once you have the rock that was clicked on you can just see how far it is from your player. Again, as I said, there are many ways to do these things. This might be a better method so you can do distance attacks, ie fire an arrow at a rock.

Last edit: For overlapping bodies you may want to maintain your own list using body entered and body exited signals. Just wanted to mention that you need to properly set your masks to so your player mask actually detects your rocks.

3 Likes

Thank you so much! this has been very helpful, simple and straightforward I really didn’t think I’d grasp the context of this whole thing this soon, thank you for the effort and time you’ve put into this reply, hopefully it’ll help others in the future as it helped me today :smiley:

here is my code for now, its not complete or that readable but it works lol
this is the script attached to my resource nodes scene(tree scene and rock scene only for now):

extends StaticBody2D

@onready var dur_bar = $DurabilityBar
@export var durability = 20.0

func _ready() -> void:
	dur_bar.max_value = durability
	dur_bar.value = durability

func _process(delta: float) -> void:
	if dur_bar.value < dur_bar.max_value:
		dur_bar.show()
	
	dur_bar.value = durability
	
	if durability <= 0:
		destroyed()


func _on_input_event(viewport: Node, event: InputEvent, shape_idx: int) -> void:
	if event.is_action_pressed("attack"):
		SignalManager.player_clicked_object.emit(self)

func take_damage(damage_amount):
	durability -= damage_amount

func destroyed():
	var dropped_amount = randi_range(1,5)
	print("destroyed and you got this amount: ", dropped_amount)
	queue_free()

and this is the one attached to my tool scene:

extends Area2D

@export var tree_damage = 10.0
@export var rock_damage = 5.0

func _ready() -> void:
	SignalManager.player_clicked_object.connect(on_player_clicked_object)

func on_player_clicked_object(obj: Node):
	var overlapping_bodies = get_overlapping_bodies()
	if obj in overlapping_bodies:
		if obj.is_in_group("rock"):
			obj.take_damage(rock_damage)
		elif obj.is_in_group("tree"):
			obj.take_damage(tree_damage)
	else:
		print("out of range")

again tysm this would’ve been a headache and disappointment if not for you and gertkeno

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.