I’m pretty curious what kinds of best practices and patterns other developers benefit from in using Godot. I’ve used Godot for a little over two years now and I still find it really difficult to say what kind of approaches work particularly well in the engine.
Here’s a few general best-practices that have seemed to work well for me:
Make every scene independent on its own. You should be able to run every scene without any errors. If you need to send a signal to a parent, expose a signal at the root for a parent to connect to. Doing this makes your scenes more encapsulated, which makes them easier to work with when included in other scenes.
Avoid inheriting scenes or enabling editable children. By inheriting a scene or enabling editable children, you make yourself dependent on the internal structure of the scene staying the same. This becomes a problem if the internal structure changes. It’s easier to funnel all of the interaction through a set of properties on a script on a root node, which then defines how those properties are mapped to its internal structure.
If you can afford it and the situation calls for it, implement more rigorous systems in a different language. GDScript is not a particularly good choice for writing systems in due to its lack of type safety, its relative maturity compared to other languages, and its inability to parse instead of validate. You’ll find yourself spending a lot of time working around the limitations of the language and you’ll end up with a lot of bugs. Use languages like Rust or C# instead, which have mature and extensive language features that make it easier to write systems code. However, this does mean giving up on things like web builds (for now, at least), so it’s not always the right tradeoff to make.
Embrace duck typing. As a person who loves functional programming this hurts to say, but duck typing works quite well for implementing what would otherwise be traits or interfaces in other languages. It certainly seems to work better over inheritance, since you cannot in GDScript use diamond inheritance when you would need to in order to make it work.
Do you disagree with these practices? Do you have different practices that seem to work well for you? Maybe you have some specific code patterns that you like to use? Let us know!
Most of what you said sounds legit, though the duck typing one gave me pause… coming from a JS background where that used to be the norm, I hated it haha. I’d rather put something together like this to avoid it until Godot has proper interfaces, if ever.
I haven’t used Godot long enough to establish those kind of higher order patterns, but I do have some thoughts about things people are doing that I would recommend against in most languages.
Don’t hook up signals by strings names, string are brittle, can get mistyped, makes it a pain to replace later, etc. Hook up signals directly via the signal object on the other class. Ex: other_instance.some_signal.connect(my_method). Another alternative is to put the string names in variables, but that is still weaker than just referencing the real signal directly.
For one-off signals you can just await them instead of having to split out a method hook it up via connect. Ex with HTTPRequest: var result = await http_request.request_completed. You get back an array of all the params that would’ve been passed to the method you’d have connected, which sucks because the types will be all mixed up unlike a method’s params, but otherwise it can be much more concise and localized to the relevant code.
Maybe I’ll add more later after I’ve played with Godot more and have more opinions. I don’t mind counterpoints or counterarguments either, I am just one person working on stuff in isolation
Big pretentious post, mostly because I have a lot on my mind lately, and typing it down helps. I’ll probably recycle this into a blog post.
Godot Core is :
You call children and use signals on children
When you call children, you mostly pass data around.
This leads to a couple of patterns:
Godot wants you to use Resources. Typed, they contain all your data, unlike Dictionaries. You’re supposed to setup scenes, with resources, using the editor to pass the data the scene needs.
Godot Editor punishes you for putting your data in your scene directly, by sometimes wiping it, regenerating references, or just being a dumdum (although this is less a problem as 4.2.2 for my case.)
As such, for scene independence and testing, most objects will have a field about their data, and some way to set themselves based on that, either in the _ready, or in a dedicated setup functions. I end up a lot with below:
Using @tool a lot to trigger stuff from within the editor (this comes with issues).
But @tool for testing comes with a problem: Children need it too to run correctly. So you either put it everywhere, and have your whole app running in the editor at all times, use biggish scenes, or just run it normally with F6. AFAIK you have no other way to have a “button that triggers stuff” than doing the trick with the setter, even by using remote.
@export var somedata: Resource
@export var test: bool:
set(_value):
setup(self.somedata)
func _ready():
#Do Stuff
func setup(data: Resource):
#Do Stuff
About Signals:
Big Orchestrator Parent You can have some top/big parent, that is in charge of pretty much nothing but wiring stuff. This works for medium-sized Scenes. As a general rule, I try to abide by: Parents only know their children. Their grandchildren are estranged, except when I’m lazy. I find it too hard to refactor references and signals across 3 generations when I have to change something.
Event Bus I keep using a small event/message bus loaded as Singleton once my scene becomes complex enough. It’s just a node with signals. This allows my nodes to scream their message in the void, without having to care if there are listeners. This decouples my scene greatly, but it also calcifies my whole code alongside the EventBus, making it the spinal cord of a big Scene.
About Composition:
Godot is weird about that. It makes you believe it can compose, but doesn’t give you tools to do so? You’re kinda forced to roll your own thing, using some interface, duck typing, or putting Variant everywhere? Maybe I didn’t get illumination yet, but I didn’t figure out how Godot wanted me to do that.
Let’s take the example above where I have an object that given a bunch of requirements, I want to have a bunch of effects:
I just gave up entirely and have two untyped Arrays, containing whatever. All my elements have the same interface anyway. I feel that more than Godot, something hasn’t click in my brain yet.
About folder structure:
Very funnily, the tree structure and scene independence make you want to mimic your Godot Tree, as a folder structure. A scene should be its little domain and folder. If you don’t rely on Singletons, you can copy that folder and use it for any Godot project and I find that great.
About IDs and Enums:
I have a bunch of weird files, where I define keys. For example, let’s say translations:
And so on and so forth. I do that because it allows me to have a global way and unique way to reference something (for example: TRANSLATION.STRINGS.CHARACTER_DIALOGUE_1). I can use that in the editor, it’s unique, and it’s cool. However, godot assigns uuids to each resource/reference, and I wish I could also use that.
As it is, I end up with a lot of code that takes enums and maps it to correct stuff. I just invented serialization again; m ;.
I would have probably more to rant but it’s enough for today:'D
Yes, I like this pattern! I didn’t think to mention it because I’ve used this organizational principle widely, back when I still used Unity and even for code repositories that have nothing to do with games.
Gabby wrote a great blog post about why this organization style (vertical organization) is great. The blog post is about Haskell, but it also mentions the same benefits, which I’ll quote below:
Vertically-organized modules are easier to split into smaller packages.
Vertically-organized modules tend to group related changes.
However, at least for games, I do find that horizontal organization can be useful sometimes. For example, in no signal, we have a folder filled with a bunch of assets that we will put into other scenes. In this sense, these asset folders are kind of like “libraries” and we don’t have to worry too much about making sure they are always next to the scenes that they are used in. We either use them, or we don’t, and we can use them to build as many different scenes as we would like without having to reorganize the assets as scenes change shape.