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!

1 Like

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