Anyone who likes $NodePaths all over their code is new to Godot. I don’t say this as a shaming thing. If you look at my early code, it’s there, because it is magic. But, as soon as you start moving nodes around, you now have tons of references to fix.
To coin a term here, $NodePath notation links are Magic Node Paths. They are fancy Magic Strings.
@onready variables are the solution to that. The only time they don’t work is when you need to reference them inside _ready(), in which case you can do:
var timer: Timer
func ready() -> void:
timer = $Timer
timer.start()
Potentially, yes. But my job for yeras has been to go into companies and train development teams, including architects and senior devs how to improve their SDLC. Which means if they’re not linting for that stuff in the CI/CD pipeline, we are gonna have a talk.
But my point is that 95% (SWAG number) of Godot developers are hobbyists with no team. So they often adopt style guides that are: “This is what I did in my last language.” The only people they collaborate with are other Godot hobbyists - either when looking for help, or when doing a game jam.
The style guide is also used by the dev team, so if you follows it, your code looks like theirs and is easy to read. It’s like teaching someone grammar. They will use it without fully appreciating how much better it makes little things. Having bad grammar won’t necessarily affect their communication with others, but it can make them hard to understand.
So yes, in a corporate structure it’s different. But when you’re using a FOSS tool, it’s best in the long run to use the tool like everyone else.
@normalized would say you didn’t. We have different opinions on them. I do think they need to be used carefully and can lead to bad design. But they are not inherently evil - and as long as they are kept lightweight, they are very useful.
Which is why I have over a dozen plugins and some have dependencies. To let people choose what they want to use without bogging them down with unnecessary weight.
I do not remember having a problem with GDUnit and autoloads. They’re easy enough to reference in a test I would think.
Iterators and math operands get a pass. Just yesterdays I gave a pass to stats in a reply to someone. But I suggest they pick that or statistics and not mix both in the same file.
Heh. We all have things in our past.
GameStateMachine In games, that’s the naming standard. It says “This holds my game states”.
I’m sure @normalized has some thoughts. Mainly cause he feels different about OO than I do in many areas.
I’ve never used ECS. So I can have feelings about the architecture, but I haven’t mucked around inside code. My feeling is based on its history, is it didn’t gain much popularity until Apple implemented it in a toolkit in 2015, and then really took off when Unity implemented it in 2018. And like most complex architectures, I think a lot of hobbyists apply it to problems when it is not the hammer for that screw.
As for using ECS in Godot, here’s an article about why Godot doesn’t implement ECS. Here’s an ECS Solution by GDQuest. There was also a 3.x implementation called Godex that looks like it was never ported to 4.x and was abandoned 3 years ago. Emi addressed ECS in Godot in one of the recent Godot Tomorrow streams. He basically said, “We’re not doing that, but you can do it in Godot if you want.”
My personal feeling is that it’s not a good fit. The more you use Godot, the more you’ll see the benefits of using Inheritance AND Composition. I’m a huge fan of inheritance. But Godot really made me re-evaluate the usefulness of components as nodes. I discussed it at length in this post: A discussion about composition and code complexity - #14 by dragonforge-dev Including at the end, where I discussed my approach to my Camera3D and Camera2D plugins, and why I went from a primarily inheritance approach to a component-based approach.
The simple answer is Nodes. It’s why my StateMachine is Node-based. I used to think I needed to do everything purely code-focused to keep things optimized. I learned two things. First, this post talks about the size of Nodes in memory. Second, I learned that Godot only has performance issues if you are pushing 10s of thousands of 3D objects to the screen, or you’re using it crunch numbers for random world generation.
While yes, Node objects do take up a little more space - that space is not going to affect the performance of your game. But what you get is something that’s MUCH easier to use in building games. I’ve been using Finite State Machines since the 80s. When I started using Godot, I followed the examples of using simple Enum-based state machines.
Then I followed this excellent class from GameDev.tv: Godot 4 C# Action Adventure: Build your own 2.5D RPG IO used it to make my game Dash and His Ghost Girlfriend for a game jam. (The reason I don’t use C# with is after 5 minutes, Mono Godot is still loading. and luckily I don’t have to compile it) Here’s what the State Machine looks like in that project:
StateMachine.cs
using Godot;
using System;
using System.Linq;
public partial class StateMachine : Node
{
[Export] private Node currentState;
[Export] private CharacterState[] states;
public override void _Ready()
{
currentState.Notification(GameConstants.NOTIFICATION_ENTER_STATE);
}
public void SwitchState<T>()
{
CharacterState newState = states.Where((state) => state is T).FirstOrDefault();
if (newState == null) { return; }
if (currentState is T) { return; }
if (!newState.CanTransition()) { return; }
currentState.Notification(GameConstants.NOTIFICATION_EXIT_STATE);
currentState = newState;
currentState.Notification(GameConstants.NOTIFICATION_ENTER_STATE);
}
}
IdleState.cs
using Godot;
using System;
public partial class PlayerIdleState : PlayerState
{
public override void _PhysicsProcess(double delta)
{
if (characterNode.direction != Vector2.Zero)
{
characterNode.StateMachineNode.SwitchState<PlayerMoveState>();
}
}
public override void _Input(InputEvent @event)
{
CheckForAttackInput();
CheckForBombInput();
CheckForDashInput();
}
protected override void EnterState()
{
characterNode.AnimPlayerNode.Play(GameConstants.ANIM_IDLE);
}
}
I liked it, but there were parts I didn’t like. For example, you had an @export Array for all the states in the machine. Just use the nodes under it and be done. There was also a Resource-based Stat system.
So when I ported it back to GDScript, I tried a Resource-based state machine to “save memory”. I also thought I was being clever, because they would be available in the Inspector, but not clutter up my scene tree. I discuss it more here.
I ended up just finding that the Node-based approach makes a lot of sense for a State Machine. It’s so much easier to test and modify. It’s so much easier to see all the states very clearly in your editor.
I found the same thing with the idea of a health component. When I first saw these being used, I hated them. At some point, someone asked how you would make one, so I replied: Am I doing Components/composition right? - #3 by dragonforge-dev As you can guess, I ended up making a long post and providing code. More recently, I started using it. I updated the code a bit.
health.gd
@tool
@icon("res://assets/textures/icons/heart.svg")
## A Health Component to add to players and enemies.
class_name Health extends Node
signal damaged
signal healed
signal zeroed
@export var max_health: float = 1.0:
set(value):
max_health = value
health = max_health
@export var health: float = 1.0:
set(value):
if value <= 0:
zeroed.emit()
value = 0
elif health > value:
damaged.emit()
elif health < value:
healed.emit()
health = value
func damage(amount: float) -> void:
health -= amount
func heal(amount: float) -> void:
health += amount
I’ve found that the more I use components and Node-based architecture, the easier making games in Godot becomes.
TLDR: I think ECS is overkill unless you are making a game with a company.

