When to Node, Resource, and Class in Godot
Choosing between many options can be hard! Let's make it easy...
Intro:
Fair warning:
It’s important to note early on regarding all “answers” in this post, and all kinds of algorithmic approaches that I’ll present, there’s never any hard-and-fast rule that is always true for all usecases. Self-serving bet-hedging aside…let’s gooooo!
Before I get into it, please take a minute to subscribe - it really helps me out!
A number of people have asked questions about Godot lately (especially new folks coming from Unity) regarding when to use Nodes, Resources, or simple Classes in GDScript. I’m going to try to give you the list of heuristics that I use to make that decision, and it has served me extremely well.
There’s also always the eternal sub-question: when should I use singletons? I’ll answer that, too!
Nodes:
The following is essentially my algorithm for picking my nodes. Keep in mind that nodes are entities with logic (functions), data (variables), and typically have visual representations, although not always. That can be in the form of a sprite or mesh, or even just something which only shows up in the editor like a Marker2D node or a Path2D, something you need to see rendered in the editor, but not something you probably need to see in the game.
Default to a node most of the time
If it needs some kind of visual representation, prefer to use a node
If it is required to be in the scene tree to receive a delta, prefer a node - including components: see
Anything that deals with physics should prefer a node
Anything directly UI-related should almost-certainly be a node
Why:
I default to a node most of the time because Godot is designed to work with the scene tree. This implies that it should be the most common usecase, and in my experience, it is.
If it needs a visual representation, the only way to cleanly get that is through a node. This might be just in the editor, or in the game, but either way, it’s far more customizable to use a node than something like a resource, because resources don’t inherently have a visual representation and they can often have problems with exporting their representations - see below for more.
For the next two reasons, requiring delta or dealing with physics, they are essentially the same and the reason is I’ve seen some baffling decisions by famous people in Godot that utilized resources completely incorrectly, and had to work around the fact they were missing a scene tree and had to go get delta manually (among other serious problems with that codebase). It was an extremely ugly approach to handling the problem they were solving, which would have been incredibly simple to solve via nodes, without any struggling whatsoever. I won’t name names, but ultimately it taught me the lesson that if I need for any reason to get access to the delta value, it is likely much better to just use a node.
And the last reason should be somewhat obvious once you’ve studied enough, but UI/control-related entities should be nodes, so that you don’t have to replicate as much of the functionality that UI nodes already come with in Godot. Storing nodes in resources or classes is horrible, and control nodes are even worse than normal nodes for that.
Why not:
People generally will claim that nodes are “too heavy,” without realizing that that phrase is pretty much meaningless in Godot (as it is in basically all languages; it comes from a time when that person thought optimization was more important than usability). A Node (the base class) is so light it barely even registers in memory. A valid reason you might not want to use a node is when an entity doesn’t really have any sort of visual representation, but does have valid variations that can be customized and known ahead of time. I’ll speak more about this later, but another time you might not want to use a node is when it is *purely* logical. In those cases, I have found classes to be the right choice.
Resources:
Resources are an interesting concept in Godot. They are customizable in the editor, but exist on the hard-drive as a sort of known-quantity. They can contain code/logic/functions, which can awesomely be overridden in derived types, and they likely will contain data though technically they do not have to. In general, you’ll see most people using them as pure data containers, and that’s perfectly valid to do, but it’s also wise to have functions on them that act upon that data, just like you learned in your standard object-oriented programming class in school. However, there’s absolutely zero visual aspect to them: they do not care what they’re supposed to look like - indeed, “look like” has no meaning for resources - only what their functionality and data are. A really important aspect of resources that most people that are starting off in Godot do not realize is that resources handle inheritance perfectly fine: if you add some functionality to a resource named, say, `ArtificialIntelligence`, and that has a function, `pick_next_target`, and you make another resource named `AIHealer` which extends `ArtificialIntelligence`, you can actually override what `pick_next_target` does for any instance of `AIHealer`, thus changing the functionality of the serialized resource instance of type `AIHealer`, but not for `ArtificialIntelligence`. This is huge, as it means you can have sub-types of resources that very clearly define their own functionality on the data, but you get to share the data types and functional interface to those resources.
To choose a resource, my algorithm is as follows:
Resources should be something that does not need access to the scene tree at all (see all of Nodes above), including exports for nodes or packed scenes in my opinion, but do need to have something that you can change quickly in the editor without having to look at or change code
It’s better to think of a resource as a sort of pure interface (in C++ terms, or just interface in C# terms): it is only the specific versions that exist on the disk that will end up being what you use. This is great, though, because it helps us decide when to use one: when a set of data is not uniform across the domain, but the functionality and the data types are uniform across said domain.
Resources are great for designers. If there’s a piece of your game where the coder isn’t necessarily the designer and the coder wants (or the designer wants!) the designer to be able to change some values that aren’t a visual thing easily (think player base hit points, experience level scaling, jump height, and so on), then you would usually want to pick a resource.
Resources are loaded as flyweights; this means one instance of it is shared across all instances that use that resource. This can have two side-effects, which can lead to both intended and unintended consequences: for one, a change you make to a resource in one place can cause a change in another place, and for two, it can become much harder to debug when it’s not clear what specifically is making the change if a lot of things use that resource instance. That said, they can have `duplicate()` called on them so that they are considered unique, and won’t update anywhere else that uses them. You’ve seen this occur when, for instance, you’ve defined a CollisionShape2D as a rectangle in the base class, made changes in an inherited node, and then those changes showed up both in the base class as well as all the other inherited nodes: the shape value in CollisionShape2D is a resource. At the same time, there can be issues: if one node uses a resource in a game and makes changes to it, then that node is discarded, and then another node comes along and uses that resource again, it will not have the changes from the previous node. That’s because it was not serialized back into the specific resource instance, and so reloading it from the disk will use the initial values.
Why:
Resources are absolutely great when you need data that is specific to a usecase and when it doesn’t have any sort of visual representation. They are by far the best for designer-required changes. If I find myself using them across several different places (think a Stats resource for a character that the UI uses, as well as the actual character node), then I will put that Stats resource instance into an autoload singleton in order to use it.
Why not:
I’ll say it again: resources should not have exported PackedScenes or any sort of reference to a node, in general. This is because it implies they actually need access to the tree somehow - outside of the fact you cannot export nodes on resources (Godot prevents it), the most common problem I’ve seen with this is someone modeling a behavior as a resource that had no - or very little - data exported, while having a call in the base class to get the `MainLoop` out of the `OS` and casting it to a tree (i.e. what a node simply gets from `get_tree()`). At the same time, it is *very* easy to accidentally get a circular reference when you have a packed scene which uses the resource and that resource exports the packed scene, and that will always cause issues in Godot. If you ever need access to the tree or a delta value for anything physics-related, for the love of all that is good in this world, please use a node. If you ever need access to the same resource from multiple places because it’s needed for separate functionality in different ways, don’t export a packed scene nor export the resource, but rather put the resource in a common, autoload singleton to use.
Nodes are also configurable for designers in a nice way, while having access to the tree automatically, as well as groups, which is nice. Given that, one of the other notes about this is that resources cannot be gotten easily in a “mass” way, as in getting a bunch of them from the tree. The reason for that is that they aren’t even connected to the tree, and therefore can’t have groups (it also wouldn’t make much sense to have a group on a resource conceptually). Outside of code that loads (for instance) a folder full of resources (see: FolderListResource), there is no simple way to get all resources in a project, as far as I know.
Classes:
A lot of people - even experienced developers in Godot - don’t even know about classes in GDScript, and certainly don’t use them with any frequency, even though they absolutely should. In your GDScript files, just like in your cs files (C#), you can actually just add a plain old class to them and have it have functionality and utility and data. This is often best for something which isn’t super-configurable in the editor (think algorithms), but is likely to always require a code change when it needs to be changed (unlike a resource, which is likely to have different data, and that’s why the data is typically exported). With classes, you don’t get the export functionality because they don’t really show up in the editor directly, so there would be no way to see/modify an exported value. If you need that, use a resource. But classes are awesome in that they are fairly low-level, giving you as the developer powerful control over them, and allow you to define the equivalent of a constructor, which is nice (_init() is the function to which I’m referring; it’s not exactly the same, but it’s close, and takes a variable number of arguments, so you can put whatever you want into an instance of the class, just not save that class off to the hard-drive easily).
I typically use classes when doing something like building a complex type that is derived from something like Dictionary - say I’m returning multiple values from a function but don’t want to rely on the Dictionary class’ interface, and would prefer to supply my own, much cleaner version. A specific instance where this recently came up is in a plugin I’m working on, where it had the ability to bundle together several different errors and format them for easy reading and placing into Firebase. I didn’t want to just return a Dictionary with completely unknown-to-the-caller keys in it, so I wrapped a Dictionary in my own class and gave it the functionality I needed. This is a great way to add functionality to built-in types that can’t normally be inherited. Plus, as with the other two options, it supports inheritance, giving you some great flexibility with how they’re used. Another way I’ve used them is by creating ephemeral instances of states that are pushed into a push-down automata (slightly-advanced state machine). This made it so instead of storing the current state and having to reset it every time I went back to the state, it would just have the initial values after being created the first time every time, which comes with positive and negative aspects.
To choose a class, my algorithm is thus:
Anything not covered by the above restrictions
Anything that is code which is likely to change, but not frequently (I usually store things in my node scripts if it’s going to change frequently) - frequently would imply to me something that a designer should tweak, and hence should fall in one of the previous two
Algorithms that don’t rely on being able to select multiple versions of them - this is slightly harder to explain, but if you know you’re only going to have one specific AI implementation (for instance, you’re implementing GOAP and all enemies use it and no other algorithms that can be swapped in), that’s the perfect type of thing for a class.
Any return type that ends up being a built-in type that either a) may not end up telling the full story for what’s being returned, or b) needs to have multiple things being returned at once, a class is perfect for that. Godot itself typically returns integers that represent errors, and I find this to be particularly ineffective and is one of my biggest annoyances with the codebase. I wish they could just return more data to me and tell me more about the errors!
Similarly, any function that ends up taking in a lot of arguments for me ends up having a single class that loads up those arguments and passes an instance of the class into the function instead. The reasons for this are more to do with my own personal issues because I rely a lot on my public interfaces, and if the function definition changes regularly, I don’t necessarily want to have to update every single place that uses it, but it’s still a really valuable tool to have access to so that you can change the class however you want without necessarily having to update a ton of places.
Why:
A class is very low-level and gives the developer excellent control over how something is going to be used. This can be both a benefit and a problem, but in my experience, it’s got way more benefits than problems. You will frequently find me using them as temporary holders of data to ferry it around my codebase in a way that makes the code easier to reason about, while hiding away some details about how it’s being used, which also further makes my codebases pretty testable. This is stuff I really love, so it helps me to write clean code, but not everyone needs or wants that, and I get it. As such, classes tend to be lowest on my list of items. Note that a class is defined this way:
```
class ClassName extends RefCounted: # I believe you can omit extends RefCounted, but you need the colon
var internal_variable(s)
func some_func(param : int) → bool:
pass
```
As just a simple example. You can inherit anything that’s also a class, and while it defaults to extending `RefCounted`, I believe, I prefer to explicitly specify that so the intention is clearer.
Why not:
One of the problems that comes along with using a lot of classes is that of maintainability. It can blow up the number of files you use if you like to keep classes in separate files, and at the same time even if you don’t keep them in separate ones, then you have the problem that they’re all in the same place and it can feel overwhelming. I like to keep them in sort of “namespaces” that I create simply by having related things in the same files but then still having multiple files each with orthogonal concerns, but anything is doable here, and nothing truly keeps them all separate in the GDScript parser.
Another problem I’ve run into is that, because they are not easy for designers to update, especially obviously the actual code if an algorithm is changing or you have a bug, even if it’s data-related, then the maintenance of them comes down purely to the developer(s). In general, that’s a good thing for developers, but sucks when there are deadlines, because you want to rely as much as you can on designers being able to customize things so that you can focus on developing features while they tweak gameplay through resources/node exports. It’s for this reason that, on my list, classes tend to be the least used, with nodes being most and resources being second-most.
The only other problem I can think of at the moment is a counter to a specific argument I made for them earlier, which is that of not having to change a public interface. In this case, you’re really just delaying the inevitable, because if the class is primarily used for data, then a constructor is likely to have to change as well, so by using a class over a resource, the changes are going to propagate to other places in the codebase anyway, so why not just rely on placing a bunch of parameters into the relevant function? My main argument against this is just that with constructors, while I can’t define multiple _inits, I can just define my own versions of them and multiple versions that take different data, so it’s possible that if something has a touch of overloaded responsibilities, I can just make a new ctor and use that in the new place, rather than changing all the other places. But that gets into too many responsibilities and starts to get ugly, so I can see the person’s point re: just collecting arguments to a function that is used in many places. I still tend to do it, because this hasn’t truly been a major issue for me.
Singletons:
Singletons are a unique beast in Godot. Well, not exactly unique, but very different. They are loaded at the start of the game running in the order they’re listed in the autoloads tab in your project settings (keep that in mind, as many people come to me complaining about one singleton having errors after referencing another singleton, and the reason is because the former is defined _before_ the latter, and hence Godot cannot find the second when working in the first). They are powerful in that they only are loaded the one time and can contain a lot of stuff that your game needs right at the beginning of running, like database information/connections, loading textures in advance, handling scene transitions in a way that anyone can use, and so on.
Which brings me to my first point in favor of singletons: they can be used anywhere, by anything. This is both in favor of and against, actually, because it means if you need somewhere convenient to store some data, like a player stats resource, a singleton is a great place to do it. But then you get a lot of places in the code getting its grubby mitts on that singleton for stats, and when that singleton changes, everywhere else breaks and that isn’t fun to fix! But in general, unless you’re truly abusing singletons, they are a great tool and make your life a lot easier. Examples of where I’ve used them: my entire GodotFirebase plugin is basically one big singleton; I have a scene changer in my game template found here: https://github.com/PotatoSackGames/PSGodotTemplateRepo/blob/main/toolkit/singletons/SceneChanger.gd
Note that, one of the really interesting, and somewhat hard to understand, things about singletons is they can be either a script _or_ a scene. This causes some confusion around when someone has a script and they are trying to get access to the scene tree but cannot, and the reason is that if you make a scene and attach a script and the scene has multiple nodes in it and the script references them with @onready, but then you accidentally load only the script, Godot will _create_ an instance of whatever the node that the script inherits and use that instead of the one to which you explicitly attached said script. The reasons for this are sort of esoteric, but basically there’s no way for Godot to know you intended that script to be exclusive to that node - it is actually somewhat common to have the same script on different nodes doing the same thing, in my experience, so how could Godot even know it? In this instance, you will always want to load the scene rather than the node, and where I’ve seen this come up most often is music managers that contain a pool of AudioStreamPlayer nodes in them, and then loading the script and for some reason getting errors about it not being able to access any of the AudioStreamPlayer nodes, i.e. having no children to use to play audio.
So for singletons, it’s best to use them when there’s something that is likely to need to be accessible to the whole codebase, but isn’t likely to change all that frequently. As such, it is a very good place to put class instances that you need to keep around, while at the same time it’s a little bit worse for things like resources and worse still nodes. A common tactic I’ve seen people do is try to remove a node from a scene, stash it in a singleton, then add it to the new scene after the scenes have changed. This is fraught with danger, not to be overly dramatic, and it is not a pattern I recommend at all. If the node is static and part of a singleton scene, then that’s generally fine, but swapping things between scenes in this way is really, really dangerous and you’ll run into a lot of issues if you’re not very careful.
If you’re just storing scenes and “registering” them when they are created, it is generally far better to use groups to go get the nodes from the entire tree directly. When that’s not possible, then a singleton can work, you just have to be careful again.
Alright! That is all for now. I’ve had this in the works for a while and just hadn’t quite finished it and hence didn’t publish it. All things are debatable, despite my speaking with a feeling of authority, so let me know what you think!
This is great! I have to share it in my Godot Weekly newsletter:-)