I figured out how to create components without nodes (and it is much better!)

The Problem

__________________________

(Note: this is a very long read so bare with me. I think it will be useful!)

Amidst my 4 years of learning Godot, one of the things I desperately wanted was an easy, smooth way to create component-based systems in Godot. I’ve always loved components and used the typical node-based approach for it. While it works well, I have a big problem with this approach: it’s not good when paired with scene inheritance.

Having made lots of games, some published while others never saw the light of day, I realized that you just only use components in your game, you need some inheritance as well. Imagine you have a class Enemy, and midway through development you wanted to add a new component to your enemies. Say, you wanted to add an XP component, so enemies drop XP on death. It should be easy enough, just create an XP component and attach it to your enemies. The problem is, you have already created 100 enemies so you need to go through all 100 of them to add that component… Ouch. You may try to circumvent this by making use of Godot’s scene inheritance, but that comes with its own problems. What if you wanted to change something in the original scene or script? Well, get ready for the component to be completely reset on all 100 enemies from that one small change, because Godot’s scene inheritance is literally Russian roulette. Really hope they improve scene inheritance in the future, but I think it’s high time I move on from them since this happened to me 3 times now on 4 different projects and I would have been livid if it weren’t for Git.

There are some other smaller problems too. Nodes are just not easy to manipulate in the Remote view and sometimes I just want to be easily able to access everything without having to go through the huge list of nodes. Even in the new Live Editing feature, if you select a node using the List Node option, all of its child nodes will be displayed including your components. I would much rather have the actually useful nodes in that list.

Additionally, setting up an Actor variable just to connect to the parent node of whatever the component is already a child of is redundant and involves unnecessary mouse clicking. Using nodes also clutters up the SceneTree, and I find that it is better to utilize the SceneTree for genuinely important nodes like Area2Ds that needs to physically exist in the scene. There is just no need for abstract components like Health, Input and Velocity to take up scene space. I know that nodes don’t have too much of an overhead but it would be better that these components are more abstract and let classes like Areas, CollisionShapes, Particles, Sounds, VisibilityNotifiers, etc. actually exist in the scene.

I want to be more involved in the code than the editor. I want to the components to be easy to setup. I want them to be more lightweight. I want them to be easy for testing. And most importantly, I want to be able to use inheritance AND composition smoothly.

Luckily, I found a much nicer, smoother, more lightweight approach for creating components using Resources, Classes and the _init() function!

Basic Implementation

__________________________

Let me demonstrate a basic example of how to create a new component in Godot without nodes. Note that this test is done in Godot 4.4.1. I haven’t tested this in older or later version but we aren’t using any new features so I believe this should work across other versions too.

Let’s think of a problem and try to solve it. Say that you want to create a Player class. You want to attach a bunch of components to this class, but for now let’s focus on one. We will try to attach a HealthComponent to the player so it can have some health.

We can define a HealthComponent class by using class_name. Unlike Nodes, the HealthComponent doesn’t actually need to exist in the scene, so we can extend Resource (or even RefCounted), since it is more lightweight than Node. I will give it a variable value which is going to hold the actual health value.

# health_component.gd
extends Resource
class_name HealthComponent

var value: int = 100

Then in my Player class, I will create a new instance of this HealthComponent by simply writing HealthComponent.new(). Assume that the player is a CharacterBody2D, since it will need physics to interact with the physics environment. I will also add the _ready() function so that I can print the health value once the player node is ready.

# player.gd
extends CharacterBody2D
class_name Player

var health: HealthComponent = HealthComponent.new()


func _ready() -> void:
  print(health.value)

Once you run the game, you should see the output:

100

Perfect! We created a new instance of the HealthComponent class and it is printing the value in that class. Now, the question is… what if we wanted to initialize the HealthComponent with a different value? Instead of 100 what if we wanted to assign, say, 150? or 200? We can’t simply pass an integer into the new() method since that method doesn’t accept arguments. AT LEAST, not yet! This is where the _init() function comes into play!

The _init() function

__________________________

I always wondered how the _init() function worked, and what the difference between _ready() and _init() was. After fiddling around, I think I figured it out. The _ready() function is called when a node is a ready, ie. when a node has been physically instantiated into the scene. On the other hand, the _init() function is called when a class is ready. Or when a class script is instantiated. That is why the Resource class doesn’t have the _ready() function nor the _process() or _physics_process() functions, but every class has the _init() function.

Well that’s cool and all, but how do we initialize a class instance with different values? Very simple! In the HealthComponent script, we need to add in the _init() function. In here, we can pass in any argument we want to. In this case, I want to initialize a HealthComponent with a different health value so I will create hp as an argument and assign hp to value .

# health_component.gd
extends Resource
class_name HealthComponent

var value: int = 100


func _init(hp: int) -> void:
  value = hp

Now, in the player script, I can pass in any integer value I want to into the new() method when creating a new HealthComponent instance. You should even see autocompletion showing the arguments inside _init()!

# player.gd
extends CharacterBody2D
class_name Player

var health: HealthComponent = HealthComponent.new(150)


func _ready() -> void:
  print(health.value)

If you play the game, you should now see the new value of 150 as the value of the health.

150

If you want to, you can even set up a default value for hp in _init() so you don’t have to always pass an argument to new() when creating a new HealthComponent class, like so:

# health_component.gd
...
func _init(hp: int = 100) -> void:
  value = hp
...

And with that, we now have a working, independent component for health that we can attach to any other class we want. We could attach this to an Enemy class or Destructable class, and it is as easy as calling new()!

Extending Further

__________________________

You can do similar things you would do in node-based components with resource-based components as well. As an example, let’s implement some signals in the HealthComponent. Whenever the health value changes, I want to emit a signal called health_changed which takes in the old health and the new health. I will use a setter function in value that will emit the old and new health value and then manually update the old health value to the new one. I will also create a method called take_damage() which we can use to subtract health from.

# health_component.gd
extends Resource
class_name HealthComponent

signal health_changed(old: int, new: int)

var value: int:
  set(new_value):
    health_changed.emit(value, new_value)
    value = new_value


func _init(hp: int) -> void:
  value = hp


func take_damage(amount: int) -> void:
  value -= amount

In Player, I will connect to the signal and print the change between the old health and new health. In _ready() I will use take_damage() to subtract the health by 60 so the setter function in HealthComponent will be called, and the signal will be emitted.

# player.gd
extends CharacterBody2D
class_name Player

var health: HealthComponent = HealthComponent.new(150)


func _ready() -> void:
  health.health_changed.connect(on_health_change)

  print(health.value)
  health.take_damage(60)
  print(health.value)


func on_health_change(old: int, new: int) -> void:
  print("Health has changed | %s -> %s" % [old, new])

The output should print the initial health, followed by the change, followed by the new health.

150
Health has changed | 150 -> 90
90

As demonstrated, we can emit signals from the HealthComponent instance and also call methods from it as well.

More Components

__________________________

Let’s add a new component to our player. I will add an InputComponent which will take in inputs from the keyboard. Let’s assume we are making a top-down game and that our player can move in eight directions. We will create a method called get_input() which will return a Vector2 with x-axis and y-axis, which contains the resultant of the four directional keys.

# input_component.gd
extends Resource
class_name InputComponent


func get_input() -> Vector2:
  var input_dir: Vector2 = Vector2.ZERO
  input_dir.x = Input.get_axis("ui_left", "ui_right")
  input_dir.y = Input.get_axis("ui_up", "ui_down")
  return input_dir.normalized()

One thing to note: since Resources do not have _process() nor _physics_process() nor even _input() and _unhandled_input(), we cannot do the check within the class itself. One solution would be to simply make the InputComponent extend Node instead. Note that this will NOT create a scene/node instance of the object in the SceneTree when you callnew(). Again, new() is only for instantiating a class, ie. its script. So it will not physically exist in the scene, but it does exist in memory. You can treat it exactly like a Resource but with the additional features inside the Node class.

However, if you want to continue extending Resource (or RefCounted), you can instead call get_input() from within the Player instead. I personally think this approach is better as well, because you may sometimes want to call get_input() while other times you may not want to (like during a cutscene), so calling it within the Player would give you more control of when that method is called, compared to having the InputComponent as a Node and having it run the method in _process() every tick.

Let’s implement this inside the player. I will create a new instance of InputComponent and then inside _process() I will call the get_input() method and print the results.

# player.gd
extends CharacterBody2D
class_name Player

var health: HealthComponent = HealthComponent.new(150)
var input: InputComponent = InputComponent.new()


func _ready() -> void:
  health.health_changed.connect(on_health_change)


func on_health_change(old: int, new: int) -> void:
  print("Health has changed | %s -> %s" % [old, new])


func _process(_delta: float) -> void:
  print(input.get_input())

If you run the game and hold down the arrow keys, you should now see the input result of pressing them.

Fun Fact: in this situation, you don’t even need to create an instance at all! We are not trying to save any value here (like we did with health), we are simply calling get_input() to get the immediate state of the arrow keys. We are not storing any value. So in this case, we can simply define the get_input() method as a static function, like so:

# input_component.gd
...
static func get_input() -> Vector2:
  var input_dir: Vector2 = Vector2.ZERO
  input_dir.x = Input.get_axis("ui_left", "ui_right")
  input_dir.y = Input.get_axis("ui_up", "ui_down")
  return input_dir.normalized()
...

This will allow us to call the function directly within Player, without creating an instance. Just write the name of the class and call the function (you should even see autocompletion once you define it as static).

# player.gd
...
func _process(_delta: float) -> void:
  print(InputComponent.get_input())
...

Notice in _process() we directly reference the class then call the get_input() method.

Benefits of this System

__________________________

The benefits of a resource-based composition system over a node-based system are:

  • (Best Advantage) it allows you to seamlessly use both inheritance and composition since everything is handled via code. You don’t have to rely on the scene inheritance which, from my experience of doing lots of refactors, requires a stronger foundation for it to be reliable.
  • Quick and easy to implement and also reuse in other projects.
  • It declutters the SceneTree of your nodes and only the most compulsory nodes have scene instances inside of them.
  • It makes testing easier as both the Remote tree view and the Live Editing view will be much cleaner.
  • You can export these class instances so they can be set in the properties panel (if you really need the editor) or even manipulated during debugging (for testing purposes).
  • Less fiddling with the mouse and more time in the code editor (especially useful if you use an external IDE like VScode or Rider).
  • Slightly better performance since Resources and RefCounteds are more lightweight than Nodes.
  • You can create multiple instances of the same component. For example, you can create two HealthComponents like so:… var health: HealthComponent = HealthComponent.new(150) var health_2: HealthComponent = HealthComponent.new(50) …player.gd

This can be useful if you want to introduce a unique mechanic, or you want to test something. If you were using a node-based approach, you’d need to create a second duplicate of the component again which would clutter your SceneTree even more.

  • Extremely versatile! You don’t have to stop at just components; you can use these for all sorts of things, since they are just abstract objects. Say you want to create an inventory system for the player. You could create an Item class which contains some useful information, like its name, then create instances inside of an inventory array. The items would be treated as objects and can be initialized by passing in arguments to the _init() function. If we assume that name is an argument and we want to initialize an Item with a name, then the inventory array could look like this:… var inventory: Array = [ Item.new(“key”), Item.new(“wood”), Item.new(“stick”), Item.new(“sword”), Item.new(“potion”), ] …player.gd

Drawbacks

__________________________

  • Unlike Python, we can’t set the argument of a specific value inside new(). If we have a huge list of arguments in _init() and they have default values, but we only want to change one argument, then we need to write the whole line of arguments before you can change what you need. So if you have a StatsComponent with Health, Attack and Defense, and you want to change Defense only, you can’t do StatsComponent.new(defense=def_value). You have to write the value for Health and Attack first before you can change the Defense: StatsComponent.new(hp_value, att_value, def_value). You can also change the value by using the dot notation, so stats.defense = def_value also works. This is another thing that I hope gets implemented in GDscript soon.
  • If the node needs to exists within the SceneTree, like Area2Ds and Audio, then you’d probably need to write a bit more code, since you need to instantiate the node and add it as a child to the scene you want. In this case, a node approach would work better. Perhaps you could use a node-based component system for these objects or simply code them in, but this would depend on what the developer is comfortable with.
  • Like highlighted above, Resources do not have access to functions like _ready(), _process(), _input(), etc. This can be circumvented by simply calling whatever method you want in the entity class your components are a part of inside of that entity’s _ready(), _process(), _input(), etc.

Conclusion

__________________________

This has gotten very long but I think you get the drill now. Personally, I will be using this method from now on for my projects because I find it more comfy than the node-based approach. I believe there is a proposal for Traits in Godot, so when that get added I might explore that too. I wanted to let others know so that we can start a good discussion of this here. Perhaps there are more advantages or disadvantages that I haven’t found out, so I’d like to know what others think. If anyone has any more knowledge, or if I had made a mistake, do put it in the comments because I’d love to learn more. Thanks for reading!

- Awf Ibrahim (Awfy)

4 Likes

Thanks for taking the time to write this, I found it very interesting. :+1:
I’m still new to Godot myself, but in the sense of this being a new game engine for me compared to many others I’ve worked in during past game projects.

You mentioned how it might be tedious or time consuming to set specific values in the new() component you create because it might have too many arguments. My question is, can you pass an array of these arguments that would contain all the information needed to the new component to be setup? If so, that might create a way to pass a single argument that the new component can later figure out what needs to be set versus the defaults?

If it is an array, you’d still need to know which value is what. In the StatsComponent example, if the stats included Health, Attack and Defense, in the array, you’d need a way to figure out which element is which. So I’m not sure how that would help.

I guess you could use a Dictionary instead? So you could have a dict like:

{
   "health": 120,
   "attack": 20,
   "defense": 5,
}

I suppose you could pass this to new(), so your init function would take this in and set each variable like:

health = dict.health
attack = dict.attack
defense = dict.defense

But then you’d need to individually check if a key exists in the dictionary. If you try to access a key that doesn’t exist you’d get an error, so if you pass in a dictionary with only dict.defense, you’d get an error because dict.health and dict.attack would not exist.

Honestly, best thing to do would be to declare defaults inside _init(), then later change the specific value you need by doing component.foo = bar in _ready().

Someone else on the thread could also let me know if there is another way to do this. I’m also a bit clueless on this too.

1 Like

Oh, I think you found the solution then with the dictionary because then you can leave things out that you don’t need to set from the default.

Example, I want to only pass the “health” and not worry about attack or defense values, my function that is always expecting the health, attack, defense can be simplified to have built-in defaults so that no matter which parts are missing, the function can still work. If I’m understanding how you have this setup, the new() can just wait for a dictionary, one argument entry should be enough to contain all possible keys that one may or may not use? My example should work, even if you send an empty like {} because it should all run in _init() if I am understanding everything correctly? I was just using the my_dictionary variable as a stand-in for what is passed to _init()

	var my_dictionary = {"health": 120}

	var health
	if "health" in my_dictionary:
		health = my_dictionary["health"]
	else:
		health = 120
	print(health)

	var attack
	if "attack" in my_dictionary:
		attack = my_dictionary["attack"]
	else:
		attack = 20
	print(attack)

	var defense
	if "defense" in my_dictionary:
		defense = my_dictionary["defense"]
	else:
		defense = 5
	print(defense)
1 Like

Good read.

I’m not sold on the system you’re selling yet, because I don’t like things like health being components. It doesn’t make sense to me. I’ve been playing with resources for stats for characters, and I go back and forth on it. I’ve used this in the past to trigger whether or not a player has gotten an ability or not.

I do think all your arguments are valid, I just don’t think most of those things are things I would make with nodes. Still, really thoughtfully written.

I do like using _init() in certain cases. It’s just a constructor, but it is really useful at times. (It becomes more complex to use in nodes because of the way nodes get constructed.)

@knightmb if you want to pass variable arguments I’d go with passing a Dictionary. Also, you can very easily check if a dictionary has a key and operate on it that way.

In a component based system, using nodes for separating things like Health is quite common. There are a lot of tutorials on both Unity and Godot on YouTube showing how to do things like this.

This write-up is similar to that but in a more code based approach.

Yes I know. I’ve followed a few just to try out the idea.

I completely understand your approach from a technical point of view. The most compelling part is that scene inheritance is wonky. My question is more just I prefer inheritance over composition for things like stats, so I’m not sure I see the benefits. What I’m thinking is that perhaps it’s good because your code for handling health is self contained.

My state machine is node based. I have a hurt state that listens for the player to say it is hurt. I guess ultimately I’d just be tying either a signal to a signal, or I’d be tying directly to the health component signal. It seems overly complicated though.

Other than encapsulated code, what do I get having a health component?

If you are making a game like BOTW, imagine every entity that has health. It’s not just the player or the enemy, but the trees, the grass, the items, the boxes, the animals, etc. they all have health or hitpoints. They’ll all have HP, but they will use it in different ways. You enemies may have health, attack and defense, but your trees may only have health.

If you use inheritance for your stats, how would you separate the health part of your stats to your trees? Do you just create a separate health variable for trees? What about animals, just another health specific to the animal class? Wouldn’t you then have to reimplement the same health related methods, like taking damage or displaying a health bar, to your new classes? If you have to do this, it means you’d need to overthink about your inheritance structure. You need to think about which classes in your game will have health as a stat otherwise you’d have to go through the trouble of refactoring your inheritance again.

By making health a component, you can easily attach it to any class as it’s own object. You can implement health to any class without worrying about inheritance or reimplement the same health functionalities again in another class. It’s generally better to keep your inheritance structure small. If you have a base class for enemies then you make enemies from that, that’s fine. But if your stats class is being inherited by every other object, you’ll be in trouble midway through development when you need to refactor your code or add in a new feature.

Another advantage is that it’s easy to reuse that component for other projects. Maybe one of your projects use multiple stats like HP, attack, defence, etc. but another project only requires HP. So it’s easy to copy-paste your HP component from your current project to your new project with very little friction. You may need to make small changes but it saves a lot of time and make prototyping very easy too.

2 Likes

Good points. I’ll keep them in mind. Thanks.

I read the article with intrest because i find most entity component systems clunky when managing many object types, for example a Pickup object might have a collision component, a mesh instance, and an ‘item’ with name, properties, inventory image etc. Then once picked up, the collision component is no longer useful, the ‘item’ can go into the inventory, and the mech instance isnt needed because the inventory only displays an image. Then, for example, if the item must be equipped then the corresponding scene with a mesh instance must be created, and the inventory item removed. I have seen this pattern several times and cant help thinking that the pickup, the item, and the equipped object should be unified into a single object.
When i tried to unify them the problem i encountered is that the collision object would still emit pickup signals and would still detect player collisions when the item was equipped, even when i tried to disable the feature in code.
And where should the collision shape /3d node go when the item exists (or doesnt exist) in the player inventory ? Perhaps to (0.0, -999999.9, 0.0).
How do you create these objects anyway - they should just be created with a mesh instance, the name, and other data, not multiple sets, and perhaps an image, or maybe there could be a 3d scene that renders the image automatically.
Ideally items could be created in script based on mesh resources.
Then of course you might get loads of junk objects hiding in player inventories or lurking in the world map, never being used, just wasting memory …

And there is often a problem with packed scenes in the scene tree with components that require a bunch of @export var’s to be set up - they cant be configured easily from the root node because of when the _ready() gets called, so it would seem like a good idea to make the packed scenes configurable and to inject the data from the root using _init() functions.
The might seem to violate the theory that centralized systems are bad, however.

I’m still very new to godot but isn’t that one of the main selling points about components? Like if you don’t need one anymore you could just set it to null when you’re done with it

Here especially with the example of doing it in code it would be simple to handle the event of once picked up, set to null. Idk how it would work in editor version but I imagine it would be just as easy

Yeah, thats what i did have in mind. Then when the item is dropped the component gets recreated. Then if entities are not nodes they cant be parented to other nodes, i.e. right hand bone attachment. So the mesh component is parented and when drops it needs to know the item/entity (i.e. they both contain a reference to the other). I was theorizing that human language describes inventory items in the order item->model, then when dropped it becomes model->item. For example when thrown the model needs a rigid body component and it flies off into the scene.

Of course the item could be equipped into a special inventory slot representing the right hand instead.

I suppose that was a bad example of why entity component systems are ‘clunky’.
A better example is the packed scenes with internal components that have to be modified inside the scene. Thats why i thought the original post was a good read.

I think you should contribute this to the godot documentation as comment to the scripting topic. It’s a must read for newbie scripters. Scripting in godot docs is not covered well enough. You could do more powerful things with RefCounted and internal class scripts. Btw. I liked your approach, because I’m doing the same thing form the day one with gdscript :+1: .