Should everything be a node?

Godot Version

4.x

Question

Hello Godot forums!
My background is as a non-game-related software developer, and I have been using Godot/GDScript to make games for about a year now, so I’m fairly familiar with it, and have learned a number of patterns, but I frequently run into architectural problems that I am used to solving one way, but Godot asks I do in another way. This is fine, and is part of adjusting to any new system (though interfaces should be a feature and I will die on that hill!), but it does often make me question if there is a better way to be doing something that it feels I’m brute forcing my old code architectural habits into because I don’t understand what Godot expects me to do.
My question for this post is about how and where I should be using Nodes instead of just RefCounted classes.

The context of the question is that I have a deckbuilding rougelike game where you play cards that summon units (which I refer to as pawns in the code). I have a single generic Pawn scene, and a custom resource called PawnTemplate that allows me to build them. i.e. I have an instance of the PawnTemplate resource for each type of pawn there is, which allows me to specify its name, starting attack and health values, any Attributes (e.g. “lifesteal”) that it has, etc.
My question is focused on the Attributes. At the moment, these are RefCounted objects that live on the pawn, which works great up until I want to:

  • A. connect them to global signals, and
  • B. be lazy about cleaning those signals up

Why is this? An example is that I have an Attribute that says “When an enemy dies, summon a bat” - this requires me to listen to whenever an enemy dies, which for this (and several other purposes) I have a signal in a Global class that all pawns call when they die, and then the Attribute can listen to the signal and if the pawn that died was an enemy, it will summon a bat. Wonderful! When my pawn with this attribute dies though, if I do nothing, bats will continue to spawn (or the attribute will fall over since the pawn it’s connected to is no longer around, the point is-) because I did not disconnect the signal.
Could I manually disconnect the signal for each attribute? Yes
Do I want to? No
Why? Because if I instead made the Attribute script extend Node and free the node, it handles all the signal clean up for me, and I don’t have to write bespoke disconnection code for every attribute! The last bit being my main concern because automatic clean up is less bug prone than remembering to clean it up myself.

This feels really weird though. I would be adding nodes to the tree just because it has one feature that feels unrelated to what Attribute is actually all about. I could maybe generalise the disconnection in code myself, but I’ve struggled to figure out how I would do that when I’ve looked into it (Any suggestions are welcome!). My main question is a bit more general than this specific example though, as I think it’s a part of the core design philosophy of Godot that I might be misunderstanding.
I feel like I have heard in various tutorials I’ve watched/documentation I’ve read as part of my Godot journey, that basically everything should be a node (within reason), but for these components like Attribute this feels out of place to me.

The way pawns take actions is automated but turn-based, and as such I calculate the logic of what pawns will do, then show that output to the player via visual nodes. This logic side feels like it should be a “background” thing, and doesn’t need to/shouldn’t live on nodes in the scene. Attributes fall into the logical, background category.
The turn-based nature means that it IS important to treat the logic and the visual side separately. And so the main question this has all been building up to is:

Should logic only components be added to the scene tree as Nodes?

I would love to hear your thoughts or if you have wrestled with this problem yourself, what you decided on doing.
Or if you have any general feedback on the architecture I described I would be happy to hear it.
Thanks!

(I have read When and how to avoid using nodes for everything — Godot Engine (stable) documentation in English which to some degree indicates that RefCounted does make more sense and I need a separate solution to the signal disconnection issue, but I think my concerns are more on the general philosophy of Godot architecture which that article doesn’t really solve for me)

You can do whatever is convenient for your use case. I’d use nodes if I need to give them a position. The downside to nodes is that you not only instance them, but you also need to add them as a child. Then you need to manage the children. The nodes will start to fire their default signals and might get in the way of user input if you need to communicate up the hirarchy.

If it’s specifically about signals, you can wrap your attribute class inside another class that handles the signal management. There’s also the signals extention like this one: GitHub - TheWalruzz/godot-sx: Signal Extensions for Godot 4
But idk if that’s useful for you.

1 Like

That’s depends by you.
Godot has some logic-only nodes, like HTTPRequest, Timer, EditorPlugin.
But in your case, it’s better to use Object instead of RefCounted. You can free Object manually even there’s references left.

For me basically everything you see in the game world, everything that has a position is a node (2D or 3D) and everything in the background are just RefCounted or Resource classes. There are some exceptions of course, but having nodes that has no point in actually being part of the tree doesn’t feel right to me.

But I’ve seen tutorials that implements character states as nodes, adding and removing “jump”, “run”, “idle” nodes from the tree every time a character does something.

So I guess as long as the performance cost of handling the nodes is not an issue then it really is up to your preference.

2 Likes

RefCounted is the lowest level object you can have in a visual tree in Godot, but it only counts references for releasing memory. So, it only makes sense to inherit from Node if you need signals.

For your “I’m too lazy to remove signals” comment, there is a one-shot signal that will trigger only once. Additionally, if you bind the signal by code, it’s super easy to remove the signal when the delegate is fired.

I see your confusion between composition and inheritance. Think of composition as an engine feature, whereas inheritance is a language feature.

If you want interfaces, go to C#; they’re there :slightly_smiling_face:

1 Like

I go by this rule of thumb,

If that thing doesn’t seems to make sense to be living in your tree/world/environment, then perhaps Node might not be the best choice.

Sure nothing can stop you from concocting whole inventory system for your RPG purely using Node. But sure as heck it will be lots easier if you do it with the help of Resources

1 Like

Thanks for all the responses!
I was surprised to see them generally point toward not using Nodes, since most of the messaging I’ve seen when looking into Godot stuff is that basically everything should be a Node.
Since posting, I had weighed up the options myself and I came to the conclusion that, despite how much it felt weird to me, the better option was to use Nodes for the sake of maintainablity - since I only need to write the code to generate the Attributes as Nodes once, and the code to clean them up can be similarly generalised by using the fact that freeing a Node disconnects its signals.
I’m back to questioning that conclusion now that I’ve seen these responses. And so maybe I do instead just go to the effort of manually disconnecting all the signals that I connect and keep them in the back end.

I think that one of the major contributors to my uncertainty is due to turn-based games needing a different architectural structure to real-time games, but most content focuses on real-time games. But I might be over thinking that, and it’s more the general curse of most tutorials being aimed at people with no experience that want to start quick, where I instead have complex architecural questions that can have multiple valid answers.

@snipercup The Signal Extension library looks interesting, but from what I can tell doesn’t make my life any easier.

@Monday I also saw a tutorial that coded a states with Nodes, which definitely contributed to my initial conclusion of “I guess I should just be using Nodes”

@MidOrFeed Unfortunately, I need the signal to be around an indeterminable number of triggers, so the one-shot stuff isn’t applicable.

@Yes

If that thing doesn’t seems to make sense to be living in your tree/world/environment, then perhaps Node might not be the best choice.

This logic is what I had been working with initially also, but I’ve been worrying is a mindset I have taken from previous game engines/non-game software development that might not be applicable to Godot.

2 Likes

I’m not 100% sure because I didn’t read through every reply, but have you looked into setting up 1shot signals? They basically fire and then disconnect themselves which from what I got is one of the things you want!

signalname.connect( funcname, CONNECT_ONE_SHOT )
One shot signals
Signal flags

I think resources are a GREAT way to keep node clutter out of your scene tree, but they shouldn’t be abused (not saying you are, just something to think about when making a new Resource). In the end each gamedev will decide to use nodes and resources in what feels comfortable to them!

Unfortunately one shot signals connections is not what I need. I need a connection that will be around only while one of the units is alive, so the signal might be triggered 0 times or 100 times during the connections lifetime.

1 Like