How to Efficiently Query and Use Component-Based Nodes in Godot?

In Godot, there’s something I find puzzling: the engine encourages behavior composition through various functional components (Nodes), but it doesn’t seem to provide an efficient way to filter or query for these components.

For example, a common implementation might look like this:

func _on_body_entered(body):
if body.has_method(“take_damage”):
body.take_damage()

However, in a composition-based design, take_damage would ideally reside in a separate CanDamageComponent node, rather than being directly in the root body.
This creates a need to first determine whether the body contains a CanDamageComponent node. The typical approach would be something like:
var damage_component = body.find_child(“CanDamageComponent”)
But using node names like “CanDamageComponent” for searching is unreliable, as the name might differ.

A more logical approach would be to define the function signature like this:

func _on_body_entered(body: (with CanDamageComponent))

which would inherently filter entities based on whether they contain the desired component.maybe by component name ,or interface name and so on.

The issue becomes even more complex in situations of using area like:

func _on_area_entered(area: Area3D):

Locate CanDamageComponent in the associated entity
Here, it’s necessary to first locate the entity through the area, and then find the damage component within the entity, making the process even less efficient.

Of course, it’s possible to record mutual references between the entity and its components, but this approach feels inelegant and wasteful.

If I’m mistaken or there are better approaches, I’d appreciate any corrections or suggestions. Thank you!

I fail to see what’s the problem with that, the only thing i see is like after the Unity disaster, people trying to use the same logic from Unity in Godot and don’t understanding their are in a different engine, with different ways to do stuff and their need to adapt for the new engine, not the opposite.

Anyways, if you think a has_method check is that problematic just set your physics bodies and areas to have specific layers and masks, so they’ll just collide with desired body/areas that you already know they have the desired method.


I also don’t see the complexity here, in 99% of the cases area will be a child of another node with all the code, so a get_parent will do the job.

1 Like

First of all, thank you very much for your response. However, I am not a Unity user. This node-based compositional design approach is encouraged and mentioned as part of the design philosophy in the official Godot documentation. Therefore, I believe there should be a more elegant way to handle these combinations.

Let’s first discuss the issue with has_method:

has_method is more like a temporary solution used in demos, and it is not suitable for extensive use, especially in complex projects. This approach can leave many hidden risks, which go against design principles. Methods are very flexible, and if has_method is extensively used for verification, it means that whenever you add a method to object A, you need to worry about whether there are checks for a method with the same name in object B that might be accidentally invoked. As the codebase grows, it becomes hard to keep track of all these scattered checks.

Additionally, when you modify the method name, it is also difficult to locate all the places where the method was checked using has_method, because its parameter might be runtime variables. If the names don’t match, the editor won’t throw any errors. This issue becomes more prominent in collaborative projects.

Furthermore, the functions that need handling here may not be limited to one. Checking has_method for each function call would be inefficient.

Layers and Masks
Regarding your suggestion about using layers and masks for physics bodies or areas: while this can improve filtering efficiency, it cannot serve as the sole basis for determining the target type. For example, you cannot assume that every object in a specific layer has a take_damage method. This approach would make the project fragile and difficult to maintain later. Even after filtering by layers, you would still need has_method or some other mechanism to determine the specific type of the target.

Interfaces
A more general approach to handling this issue might involve interfaces. I’ve recently noticed that many people are discussing this problem, and there are some proposals on GitHub. However, none of these solutions are perfect yet.

Proposal #4872: Suggests adding a more explicit interface system to GDScript.
Proposal #758: Discusses implementing a trait or mixin system for better code reuse.

1 Like

Hello!

This is a very valid fundemental question.

I’ll compare with Unity because I know it well and it’s a heavily component based engine.
Nodes in Godot are more specialized, smaller, reusable samples of code due to only having one script. This single script needs to contain all of the functionality for that node. Nodes can inherit from other nodes, and contain other nodes as children. There’s no components, but you can substitiute them with other nodes.

In your example, if you had a Hurtbox that looks for Hitboxes, you could simply do:

func _on_area_entered(area: Area3D):
    if area is Hitbox:
        area.take_damage()

Godot actually converts the area variable into Hitbox once you check whether it is that, that’s how you would look for other components(nodes). Due to them being quite specialized, they’re also more interwoven. If you get a higher level entity from the area entered function, you would first convert it to it’s class, and then work with the entity itself rather than it’s components:

func _on_area_entered(area: Area3D):
    if area is Enemy:
        area.take_damage()

The enemy’s take_damage function would look like:

@onready var hitbox: Hitbox = $"Hitbox"

func take_damage():
    hitbox.take_damage()

It’s redundant but it’s expected when you are working with higher level nodes.

The difference and transition from GameObjects with Components to just Nodes is surprisingly difficult to explain. The main gist is that you can achieve the same behavior as components if you wanted, but just having nodes is simpler and more lightweight.

The biggest problem I’m running into currently is that you can’t really add behavior to any of the base classes. You can inherit them, but this doesn’t change any of the classes which already inherit from that base. Making simple additions quite a bit more complex.

Adding components for example could look like:

class_name GameNode overrides Node

var components: Dictionary

func add_component(component: Component):
    components[component.get_script().get_global_name()] = component

func get_component(with_name: StringName):
    if components.has(with_name):
        return components[with_name]
    return null

but it looks like:

class_name GameCharacter extends CharacterBody3D

@onready var components: Components = $"Components"

func add_component(component: Component):
    components.add(component)
#...

and you’d need to repeat this for every lower level node you add which inherits a base class.

I might’ve not covered all of your questions, so just ask again if I’ve missed something.

1 Like

Its absolutely reachable thing if using _internal dictionary in entity, checking just

if node[&'_internal'].get(&'DamageComp', false):
	#target filtered before
	target[&'_internal'][&'HealthComp'][&'current']-=node[&'_internal'][&'DamageComp'][&'Damage']

Just using needed component keys you may edit its values without mandatory knowledge if it has it or not. This approach leaves only ONE Class for component and no additional objects on entity. Also makes (de)serializing pretty easy thing, because all entity data is tied to character/item and only in single dictionary.

It seems my title may have caused some confusion. I’m not necessarily looking to implement a component system like Unity’s. What I’m really trying to understand is: what is the best structure for Godot?

I’ve watched several videos on Godot’s design philosophy and learned that it encourages composition over inheritance. However, I haven’t found a clear explanation on how to design such a composition system effectively.

As you mentioned, each base class would need to include add_component and get_component methods. For example, in CharacterBody3D, StaticBody3D, and RigidBody3D, because the root node’s base class can differ, I would need to copy and paste the component-related code into each one. This kind of repetitive code makes maintenance difficult and is far from elegant.

In your earlier example, when checking if area is an Enemy and calling area.take_damage(), it actually invokes hitbox.take_damage() inside the Enemy class. This means that every child component in Enemy that interacts with external logic would require a mapping function in Enemy, making the code even more redundant and less elegant.

Additionally, since Enemy itself might have different base classes (e.g., a monster might inherit from CharacterBody3D, while an enemy turret might inherit from StaticBody3D), I would have to rewrite these component-mapping functions multiple times. This makes the components cumbersome to use and not truly plug-and-play.

What you described would eventually result in all component functionality, data, and logic being written directly in the Entity. The reason for using a composition of nodes is to decouple the code and make the logic more clear and modular.

no, that only keeps related key value to component.

#entity data as it could be
var _entity:={
	&'X':1,
	&'Y':25,
	&'map_tile':Vector2i(-15,-227),
	&'HealthComp':{
		&'current':5000.0,
		&'max':5000.0,
		}
	}

when it enters to area, area pseudocode script

const DAMAGE_VALUE:=100
func _body_entered(body:Node2D)->void:
	if &"HealthComp" in body._entity:
		HealthComp.damage(body, DAMAGE_VALUE)

The main idea I was trying to get across is that the nodes themselves should be self contained, that leads to them being reusable and plug and play as you said. Of course you’d have to define plug and play because you can’t plug the main menu onto the enemy and expect everything to be dandy. My point is that it’s dependant on your game and what you really need from it. If your goal is to make a set of pluggable nodes, then you can easily do so. However, there still needs to be something to glue all of them together. Because when an enemy suddenly gets a hitbox added as a child, it needs to be able to grab it and use it like it’s supposed to. I’m interested in knowing what exactly you’re looking for. This is a pretty interesting topic and problem to figure out. Give me an example of what you’d like to be able to do with this system and I’ll explain how you’d achieve that. Unfortunately I’m not experienced enough with the engine to have general dos and don’ts. I’ve made some small games but everything’s just been prototyping.

Yes, I completely agree with your point. My main concern right now is how to implement this binding layer elegantly.

For example, in my current game, I have trees, stones, animals, and mechanisms. Each entity is composed of a set of relatively independent functional nodes, with the root node responsible for binding these functional nodes together. Since this binding functionality is very generic - just managing, registering, and finding these components - it should only need one set of code inheriting from Node.

However, since these objects have different base classes (trees are StaticBody3D, animals are CharacterBody3D, mechanisms are RigidBody3D), it’s difficult to reuse this binding code across these objects, and we might need to copy-paste it many times (which is neither safe nor elegant).

But I’m very happy that after extensive research, I discovered that by using the node’s meta data, we can bypass this binding layer entirely, which feels great. Here’s a simple example:

Component usage example

Property component

class_name PropertyComponent:
extends Node
@export var health:int=0
func _ready():
get_parent().set_meta(“PropertyComponent”,self)
func hurt(amount: float) → void:
health -= amount
if health<0:
die()
func die():
pass

Damage component

class_name DamageableComponent:
extends Area3D
var property_com:PropertyComponent
func _ready():
get_parent().set_meta(“DamageableComponent”,self)
func get_property_com():
return get_parent().get_meta(“PropertyComponent”,null)
func take_damage(amount):
var propertycom=get_property_com()
if not propertycom:
printerr(“warning:no property com”)
return
propertycom.hurt(amount)

No need to write binding code in objects anymore, avoiding the issues of GDScript not supporting multiple inheritance and different base classes

If components have interdependencies, they can be exposed via export and connected in the editor

class_name PlantTree:
extends StaticBody3D

Components can be directly added as child nodes in the scene tree, plug and play

Can also have PlantTree-specific code

class_name Animal:
extends CharacterBody3D

PropertyComponent

DamageableComponent

Components can be directly added as child nodes in the scene tree, plug and play

Can also have Animal-specific code

Component usage example:

For bullet hits, we can filter bodies by component type and find the damageable component

Each function call is type-safe and supports editor hints

func _on_body_entered(body:ComponentManager):
var damageable_com:DamageableComponent=body.get_meta(“DamageableComponent”,null)
if damageable_com:
damageable_com.take_damage(10)

1 Like

I’m glad you found a way that you are comfortable with but I will point out that the meta data system you are using is, as a practicality, no different than that mutual reference system you described as “wasteful and inelegant”.
You are still storing a reference to the component object and you still have a layer of abstraction to get at the component.

1 Like

_set()
_get()
_get_property_list()

can add you new properties from multiple scripts just by adding them runtime in dictionary that read when one of these methods calls. So it better to look them. One drawback - autocomplete won’t work and have to address from self[&'property']