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)