Getting the value of a node variable by instance ID

Godot Version

4.2

I am relatively new to Godot 4 and I am making a basic 2D game. I am trying to detect which enemy enters my hitbox and get the damage variable from that particular enemy to assign the damage to the player.

My idea is to create a switch statement where a value of “damage” is returned by matching an instance ID against a particular enemy.

The enemy’s are all inherited scenes from a base enemy.

My problem is that I don’t know how to access the damage variable on the enemy which enters my hitbox.

func on_hitbox_area_enetered(area):
	var enemy_instance = area.get_instance_id() #41188066754
	var instance = instance_from_id(enemy_instance)#HitBox <Area2D#41188066754>

The code above shows how I can get back the child node of the enemy but I can’t access the damage var from the enemy script from this.

It’s very likely I am making this harder than it needs to be, any advice would be very appreciated!

Is there a reason why you want to match by instance ID? Usually I would just store the instances itself and check by equality.

That being said, what exact error do you get when trying to access the variable? Usually it should work. We would need to see more of your code and node structure to properly debug this.

Thank you for your response!

The reason i’m doing this is probably not the right reason, I’m a JS front-end engineer and new to programming games so i’m probably treating the problem in the wrong way!

You mentioned storing the instance itself to check by equality, this sounds like the right approach but i’m not sure how I would go about doing it. Could you point me in the right direction?

Many thanks

Depends a bit on your overall approach, like how did you plan to get the instance ID in the first place? I assume you have some mechanism in your code where you have access to the object, otherwise you wouldn’t have a way to get the ID that the object has in that application run. At that point, you can just store the object in a variable instead of the ID.

Since you mentioned being new to Godot/Game dev, maybe a more common approach that you could use: Groups

You would assign different groups to the different kinds of enemies, and then simply check if an instance is within a specific group. Does this sound like something that would work for your usecase? I’m a bit speculating here since you asked a somewhat abstract question (which is fine!) so I don’t know the overall context of your game.

Oh and another option is to match by script/class in case you need to differentiate between different class types.

Thanks so much for your help!

I will try and be a little clearer with the sample below of what I am trying to do.

enemy_1.gd
const DAMAGE: int = 20
...

enemy_2.gd
const DAMAGE: int = 30
...

player.gd
func on_hitbox_area_entered(area)
	...

In the player script I need to know whether it is enemy_1 or enemy_2 that has entered the player hitbox area so I am able to apply the correct damage to the player.

As I said, I am probably over complicating things and going about this the wrong way.

Thanks

In this case I would use the is keyword to check if the object matches your custom type. Your player script would look like this:

const Enemy1 := preload("res://path/to/enemy_1.gd")
const Enemy2 := preload("res://path/to/enemy_2.gd")

func on_hitbox_area_entered(area):
    if area is Enemy1:
        prints("one", area.DAMAGE)
    elif area is Enemy2:
        prints("two", area.DAMAGE)

Or, if you don’t want to preload the class files everywhere you need them, you can also give the enemy scripts a global class name, making it available as a type to all scripts. The enemy scripts would look like this then:

class_name Enemy1
extends WhateverNodeTypeYouNeed

const DAMAGE: int = 20

In this case, you would skip the const Enemy... lines in the player script, as they are available globally already.


Which approach you use is up to you. Advantage of the class_name is that you don’t rely on a hardcoded path to the script file, disadvantage is that you “pollute” the global scope.

1 Like

Is your enemy script really attached to the Area2D?? More common structure is to have

  • Enemy (CharacterBody or Node2D)
    • Hitbox (Area2D)

In such case your method really detects the Hitbox, not the Enemy. You can call get_parent() on it to get the variables, etc.

If your enemy really inherits from Area2D, it might be worth doing a type check. Other Area2Ds could trigger the collision and not all of them are necessarily enemies.

1 Like

Oh right, that is a really good point! You may need to get the parent or similar. If you show us your node tree structure we can probably tell you the exact tree traversal you need. I personally often use hitbox.owner as that is independent of the tree structure (the owner is the root node of the scene file a node belongs to).

In this case my example code would become if area.owner is Enemy1 for the conditionals, and area.owner.DAMAGE to access the constant value of it.

My enemy script is attached to the CharacterBody2D, perhaps I didn’t make this clear or made it appear otherwise, my apologies.

Thanks for the response

1 Like

This is exactly what I was looking for, thanks so much, you are an absolute legend! Apologies for my poor description in the first place, it’s all very new to me.

Don’t worry, it’s impossible to know what exactly is needed while still learning… Otherwise you wouldn’t need to ask in the first place. So this is pretty normal and why we ask clarifying questions :grin:

Be sure to mark one of the replies here as the solution if everything is working, or feel free to ask for more info if you still need something to make this work! (Include any error messages you may get)

image

1 Like

Just thought I would share another solution that I managed to work out that seems to work very well, however, I’m not sure if it might have any performance pitfalls.

func _on_hit_box_area_entered(area):
*#Get the parent node of the enemy collision layer (CharacterBody2D where the script is attached)*
	var parent_node = area.get_parent()

*#Access the DAMAGE constant on the parent node*
	var damage = parent_node.DAMAGE

*#Pass the damage var into a function which manages the player health*
	player_health = PlayerManager.manage_player_lose_health(damage)

Hopefully this is also a viable solution (it’s working fine for me).

1 Like

Looks good!

That is perfectly fine if you don’t need to differentiate between the different enemy types, and as long as you only use areas for enemy hitboxes. But the approach will fail once you use area for something else. Imagine for example if you implement coins that can be picked up. They will have an area too, but the area won’t have a parent with the DAMAGE property. So that would produce an error if the player enters the coin area.

For this reason you almost always want to have some check if the detected area is the correct one. Either by checking the group, or checking the type, or even duck typing.

1 Like