How to easily and universally access components of entities

Godot Version

4.3

Question

I really like the component side of Godot as it gives a nice and flexible way to build different entities, but there’s one aspect of it that I’m struggling with and that’s accessing the components in a nice, efficient and universal way.

For example, if I have a health component in an entity (i.e. a health component node added to a scene representing an entity), that makes the entity “damagable” (i.e. it can be damaged, since it now has health). However, this is not really the case from the API perspective, since no other entity can explicitly damage it, i.e. you can’t do something like entity.damage(x) or entity.get_component(health_comp_id) or anything similar. Also, adding a damage function to the entity’s root script also isn’t great, as that would require adding such a function every entity that has a health component which might be a lot, not to mention that if I ever wished to tweak the functions API, I’d need to remember and update the code of all “damagable” entities.

The easiest workaround I can think of would be entity.get_node("HealthComponent").damage(10), but that assumes that the component node has that specific name, which is not ideal, as it can be easily changed from the editor and I don’t think there’s a way to prevent that. It is also not very efficient. I could also find the component by type, but the question of efficiency remains.

I can also think of a nice way of enabling access to all of the components through some form of a “component manager” in each entity (e.g. by using a dictionary), but every entity with a component would need to have it and I can’t think of a way to enforce this automatically either.
So my question is, is there a nice way to solve this issue? How has everyone else coped with this?

I’m using GDScript.

Thanks!

1 Like

Well, the way I enable nodes to receive damage is simply by checking if a given node has a “health”-node i.e. search by type. If an instance is found, I call damage() with the required damage information. I use C# so I created an extension function that allows me to write Node.dmg() which is nice and tidy – but you can do without it.

As for whether there is a discernible drop in performance, I don’t know. I generally only worry about performance if the application becomes unusable. Performance is important, sure, but don’t let it grind you to a halt.

If you want to use a singleton approach (such as a “component manager”), you could just as easily create an Autoload through which nodes are damaged. There are two paths you can go down with such an Autoload:

  1. Make your Health nodes register themselves to the manager in _ready() together with the ID of the parent. This is basically what you mentioned.
  2. Register each Health node dynamically when Manager.damage(Node) is called. This would require searching by type to find the Health node under the target Node, but only once.

In both approaches, the goal is to form a dictionary of Node-Health pairs that eliminates the need to constantly search for the Health node. To damage a node, you would simply call Manager.damage() with the Node’s instance ID as a parameter along with the damage information.

var target_node: Node
var damage: float

Manager.damage(target_node.get_instance_id(), damage)

It’s, of course, important to manage this dictionary as object removal, scene changes, and other operations invalidates some of its values.


I would personally just search by type whenever damage occurs – it’s simpler without any apparent downside. But, creating a manager is also an option.

Let me know what you think.

@Sweatix Thanks a lot for sharing your way of working!
I did think of searching by type, but I feel like that’s a bit of a fragile way of doing things (even though it’s probably the best one I’ve got for now). For example, changing the name of the component type could break things all over the place without Godot warning you about it, although I can probably bring that risk to a minimum if I use a constant for that or something similar.
Of course, that still requires a search through the nodes for every function call, but I’m starting to think that that won’t be such a frequent use case and that there are ways around that if that ever becomes a problem, even though they aren’t too pretty. So I think I’m starting to ease off of the performance side of things a bit now, as you suggested :slight_smile:

As for the “component manager” idea… Actually, what I meant was having a dictionary (or array) local to each entity that would allow you to lookup a component for that entity, effectively avoiding the node search. The reason I didn’t like that is that it requires a lot of manual steps to setup a component based class - you’d have to add the manager as a property and also somehow register the components, which I think is, from that aspect, worse comparing to searching the node by type.

I actually didn’t think of your idea of making a global component manager, which would allow components to register themselves in a nice way and you wouldn’t need to do anything special to enable components in entities. That’s actually nice. Although, I think the necessary handling of the scenarios you mentioned at the end could turn out to be a bit problematic.

I think I like the search by type the most so far, but I’d be interested to hear if other people have different approaches then the ones we mentioned here. It does still bother me a bit that you can’t have both speed and convenience, but I’m afraid I’m gonna have to live with that :slight_smile:

Searching by type has nothing to do with the name of a node – it’s search-by-type, not search-by-name. It’s actually a reliable approach, not a fragile one. Perhaps you are misunderstanding this approach?

It’s only problematic if you don’t implement it properly. You should just try it out and see if you can make it work – you don’t have to overthink it.


I do hope someone else sees this post and enlightens us all on a simple, efficient, easy-to-use solution. However, as far as I’m aware, every implementation lies on the spectrum of generalized vs. specialized and has their own pros and cons. If you want convenience, it’s highly likely that you have to sacrifice speed, simplicity, or both.

Good luck with your search!

Well, it turns out you were right about that. For some reason I thought is_instance_of is called like this is_instance_of(object, "Node") instead of is_instance_of(object, Node), which is why I thought it was fragile. With this taken into account, you’re quite right that it’s a reliable approach. Thanks for motivating me to re-check this!

It does seem that that’s the case here, although with what I’ve realized above, it doesn’t feel like a compromise as much as it did.

Thanks. I think I’ll probably use the “search children by type” method for now and see where that takes me. Thanks again for chipping in - it really helped!

1 Like