Question about Composition/Inheritence

Let’s say I have a hypothetical scene like this with components and states attached to a node. The underlying code may be ignored for the time being.

Screenshot 2025-01-13 175151

So here, I have a bunch of components, each with their own individual code to run the specific things they do. I have states also as nodes, which will usually be modifying components so that they do the required action, like RunState would set the velocity in VelocityComponent to some velocity so it will start moving, and IdleState would set the velocity in VelocityComponent to (0,0) so it stops moving. So on and so forth, all good thus far.

Now, let’s say that I want to add a new feature to my enemies so that, whenever they take damage, it will play a squish + flash effect on the enemy. The HitboxComponent could send a signal for when it detects a collision and the squish and flash components can listen to that signal. Naturally I would make some components for this as well.

Screenshot 2025-01-13 180015

This is all fine too. However, imagine that I have only started adding this feature on the 101st enemy. I now have to go through the 100 other enemies I made before this enemy just to add these components. That would take a lot of time that wouldn’t really be worth it. This seems to be a situation where inheritance would actually help, but I have no idea how to fit inheritance in such a setup, so my setup is a little flawed in this scenario.

My question is, how would I handle node composition like this in such case? Is there anything I could change with this setup so that it makes it easy to update my nodes in the future? I want my components and states to be independent from each other so that I can reuse them for my other projects, so something that gives me the benefit of having multiple independent components while also being able to update my nodes/classes quickly so they have similar behavior.

NOTE: I’m aware that you can use ‘Inherited Scene’ in Godot, but I was wondering if there was a way to avoid using it. It’s a little unreliable as it does seldom break, but I can always use Git in case scenes break. Regardless, I would like to save ‘Inherited Scene’ as a last resort. Any way to setup components along with some kind of inheritance would be much preferred.

The whole thing seems a bit strange and raises a lot of questions for me:
Why aren’t your enemies all spawning from the same instance?
If it’s because of the large variety, then you’ve defined 101 enemy types?
What’s the problem with adding scenes as components that you’re avoiding?
Is your problem with coordinating these components under the main enemy? I think this coordination could be done with a helper function
Really confusing!!

I can only assume you mocked this up just for the question. But it is a fair question since I have changed how I do this many times too. This is my current and favourite at the moment.

Here is an example of two of my enemy ship types, Police and Civillian. (Not their in-game names). This is how I structure it at least…

You notice that except for the first two items, the BehaviourManager and the MovementManager are the only two scripts that are unique for all my enemies types. All the rest are shared components/scenes.

So when I make a new enemy I empty out the state manager (apart from the required ‘Idle’ state), change the visuals to whatever I need for this actor, and then clear out the custom scripts for the BehaviourManager and MovementManager which resets them to default movement and behaviour functions contained in their base classes.

Notice how all the states are scenes in themselves. If I want patrol behaviour I drag it into the states manager (which is also it’s own scene). If I need swarming behaviour I drag that in. The states manager reads the children automatically and creates a list of allowed states to enter or exit. The only adjustments I need to make is in the behaviour manager.

The main script (in these case ‘Civillian’ and ‘Police’ attached scripts) just contain references to child nodes, custom vars like top_speed, cruising_speed, damage_applied etc, and an initialisation function. Apart from this there is no code in there at all.

The BodyCollision node is the main collision shape, and the parent node just sends the signals and all information from this collider to my Collision Manager.

With this setup, my enemies with vastly different behaviours can all be created with the same setup. The only things I have to do is in the main script set any additional setting vars I might need, and in the visuals add any other animations or particles or whatever I might need. Then by overriding functions in the behaviour manager, and the same with the movement manager, or by adding new ones entirely (which I can add to the base class if required to be shared with other enemy types) I have complete control over all and any behaviours.

The movement and behaviour managers do not have a process function at all. All processing is done in the states, and only in the states. In fact only one states processing is enabled at a time (the current state). So these are so efficient I easily have 200 enemies running at a time, maintaining 60 fps at all times. If I push it to 400 then the frame rate starts to drop but only to 50 or 55 fps (and I do not need to push it that far.) (When I put 600 enemies in it dropped to 25 fps).

The states call the behaviour manager when something happens like ‘player_detected’ or ‘base_lost’. The only code I have to change is in the behaviour manager if the defaults need changing or when there is custom behaviour for an enemy type.

I have swarming enemies, loners, hunter/killers, cowardly enemies that run away, miners, minesweepers, mine layers, queens, group leaders, formations all sorts of great stuff, all done through the behaviour manager alone.

Hope that helps.

PS I suppose there is no ‘right’ way to do this. For me, and in my game case, this is working brilliantly.

PPS Sensors has all the raycasting I need. When something is sensed it alerts the behaviour manager. The only place where states are changed is in the behaviour manager.

PPPS You said about using child scenes something like:

“It’s a little unreliable as it does seldom break”.

No they do not. This is a key and fundamental aspect to creating maintainable and re-useable modules. It is 100% reliable IMHO.

PPPPS (Final comment) The states all inherit a base class too. So again I only have in those scripts the bits of code that make them unique. It is this mixing of inherintance and composition that gives so much flexibility to Godot. I am loving using Godot. It is genuinely amazing!

2 Likes

I believe the OP’s problem is that once you need to add or remove a specific component from a generic “Enemy”, you’ll need to go through all of the enemies and do that, which is inconvenient and error-prone

2 Likes

If OP has 100 enemies then this is an ideal candidate for making them resources.
A single enemy node with all the component nodes and 100+ data containing resources.

A resource based system doesn’t sound too bad, but I wonder how this would affect the structure. Like for example, I would want my enemy to have a HealthComponent but I also want the HealthBar on my enemy to have a reference to the HealthComponent as well.

This is easy to do with nodes as I can just have an export variable to hold that reference, but with resources I’d need some way to get the same HealthComponent resource from my enemy to my enemy’s HealthBar node. I could just connect it via the enemy node to my HealthBar but then the logic is getting coupled to my enemy which I’d prefer to decouple to my HealthBar.

I just did a little test.

@export var components : Array[GDScript]

func _ready() -> void:
	for i in components.size():
		var c = components[i].new()
		add_child(c)

But it’s not a good solution. You can’t use typed arrays (for example to allow only scripts extending component) if you want to drag scripts into the array.

So far I had no problems using “Inherited Scene”. It worked best for me and was the easiest way to do it.

2 Likes

IMO, the components don’t become a part of the resource.
The way I have used resources is a purely data containers.

For example I would have a base class say Enemy which would have a variable: @export var enemy_data:EnemyData and would contain all the component nodes.
The EnemyData class is the resource:

class_name EnemyData extends Resource     
@export var max_hp:int

Now you want to create a new Enemy ExampleEnemy.
You create a new resource of EnemyData and set its max_hp, SaveAs the resource example_enemy.tres

Then you create a new inherited scene of Enemy (which comes with the components), name it (ex) ExampleEnemy
Then drag over example_enemy.tres to fill out the enemy_data variable.

This process is repeated for each enemy type.

The HealthBar for any individual enemy is either a component or a part of the base class.
Likewise the HealthComponent remains a part of the base class.
Something like current_hp would not be in the enemy_data resource. It would belong to the base Enemy class.

Back to your example of adding squish and flash.
Since they are functions that are shared by all enemies they would belong to the base class (or a component of it) not the resource.
However if there was some kind of threshold to achieve a squish, and that differed between enemy types then the resource would contain something like:
@export min_squish_level:int

1 Like

I come from Unity for games and very OOP/inheritance/interface heavy design for my professional life, and since switching to Godot I’ve been experimenting with composition instead. I encountered the exact same question as the person who asked this one. Without refuting the other suggestions above, because they might be the correct answer in some cases, I want to leave here that the approach I’m going with is broader nesting of the components; i.e. in the OPs example, creating a component called “Enemy” and putting all shared components into that, then right clicking on it and checking “Editable Children” to so that I can modify as I would have otherwise. This is mainly to avoid Inherited Scenes as the OP said, as inherited scenes are not easily found in the Godot docs, which makes me weary of them.

Inherited scenes work just fine.

Nice having that in writing! Most of the top search results have something opposed to it. The only one that stuck out for me thought was the missing documentation, because I couldn’t find it myself. Seemed odd. Might be common with niche features, but I wouldn’t consider this an edge case.

TBH I do wish there was more about it, because if there was I would have learned about it sooner. There’s not really that much to them. Once you learn how to make one, they’re pretty self-explanatory. They are more limited than an instantiated scene added as a component, so mine tend to stay pretty lean.

The only major gotchas is that resources are shared and children of the base scene are not editable. Which goes back to keeping them lean. But in the context of the OP’s question and @pauldrewett 's response, they are the way to go in that instance.

Inheritance is the way to not duplicate work, period. If you really didn’t want to use an inherited scene, you could create one from code with instances of scenes, but you wouldn’t be able to see it in the editor, or edit it in the editor - and then it’d be a huge pain to enhance.

2 Likes

“Inheritance” takes many forms, it doesn’t have to be an inherited scene. My example of nested composition seems to be working well.

Glad it’s working for you. I also use both composition and inheritance in my games. I prefer to use inheritance in my object code. The only time I use inherited scenes so far is as a base for enemies.

As a side note: While one is certainly welcome to have multiple definitions of inheritance, I think it is important for any new programmers reading this thread to know that there is a specific definition of inheritance when it comes to Object Oriented Programming. I only say this because I’ve interviewed literally thousands of programmers over the past 25 years and if a candidate told me inheritance takes many forms they would’ve just tanked the interview.

I won’t get into a pedantic argument here, but just to clarify, the inheritance is of the child node rather than using the inherited scene functionality. By adding a node which is a defined scene (enemy) and then modifying it using the “edit children” checkbox, you get the features of object oriented inheritance through something that feels like composition. (And as a side note, I find it hard to believe you ended an interview with anybody who answered “there are many forms of inheritance” to that question without asking them to elaborate :slight_smile: But I can see you have a general point that it is a well defined term of art.)

1 Like