You’ve gotten a lot of advice, and I’m going to give you some more.
Game Jams
If your game concept is ambitious, I recommend you take a step back and go do some game jams. Spending time creating an entire game in a limited amount of time will help you in so many ways. When it’s over, it’s over. You take what works and leave the rest. It also teaches you the value of execution vs planning. I spent SO much time architecting my first few Godot games before realizing I didn’t know enough about how Godot works to fully architect my system. And I am a software architect - like I’ve been paid professionally to do that. I’ve also worked on video games, and I’ve been a programmer for three decades. Practical experience counts for so much.
Bluntly, you don’t know enough about Godot yet to architect a working system. However, we all start there and that’s the fun of programming! Learning how to do things in new ways!
Kill the Managers
I strongly agree (in part) with @CodeSmile about the GameManager. there was a C++ talk I listened to years ago about eliminating the word “Manager” from all object names. Just doing that remaps the way we think about objects.
Singletons/Autoloads
I disagree that you should have no singletons/autoloads in your game. It is a fun mental exercise, but a poor practical one in a game. Some data needs to be easily accessible.
In my games (I will show you the structure in a bit), I use Main as the node that all my games run from. It contains a state machine. Then I have a Game autoload singleton that tracks things like what level is loaded. I do keep it as bare as possible. Mainly, I use it as a signal bus.
For example:
signal load_level(level_name: String, player: Node, target_transition_area: String)
This signal it emitted whenever I load a level. It can be emitted by a button in my UI, a transition Area2D/3D in my game, or the death state of the player StateMachine to name a few. The loading screen catches this signal and actually starts the level loading. It parses the level name and either finds the scene that matches or the UID if passed a UID. Meanwhile the Gameplay state monitors the level loading process (which is built into Godot) and when it is finishes, launches the level.
When the signal is emitted, the level gets passed the player, allowing me to reparent the node and move it around. If the value is null, the Game object has a reference to the Player scene to instantiate it - typically for a new game. If a transition name is provided, a player is moved to the corresponding node location. Otherwise, the level itself just moves the player to the starting point.
All of this developed over time.
StateMachine and State classes
I developed my own StateMachine plugin. The code is very streamlined. It focuses on the Kanban method of project management - pulling tasks instead of pushing them. The StateMachine is not a “manager”. It is a machine with states. It tracks what state it is in, and activates and deactivates states within itself. It does not decide what state to transition to.
The State class has an _activate_state() function. This is where the stuff that normally goes in the _ready() function goes. This is because when a node is readying itself, it initializes all its children first. The StateMachine activates the states after itself and its owner are fully constructed. It also has a _deactivate_state() class which is rarely needed, but is there for when it is needed.
A State also has an _enter_state() and _exit_state() function. You’ll notice all these fucntions start with undersacores - indicating that they aren’t intended to be called by the user. They are used internally. Instead a State usually turns on one of the process or input functions in _activate_state() (because _ready() turns them all off). Then when a button is pressed, a signal is received, or something happens that is being monitored, the state calls switch_state(). This requests the StateMachine to make the transition.
The StateMachine then calls the current state to exit, and the new state to enter, unless a state is marked as can_transition = false. It which case, nothing changes. This handles things like the player not being able to become Idle in the middle of a Jump.
Game State Machine
So now we get to my game state machine. This is a StateMachine node that is renamed. You can tell by the greyed-out script attached to it that there’s no custom code to it. I then have four states (sometimes more in some games). One to run the Splash Screens at the beginning, which upon completion deletes itself. Then Loading, which as mentioned before, handles loading of levels, as well as the loading screen and progress bar. Gameplay handles actually loading the player into game itself, as well as everything going on in the game. When a level is loaded, it is placed here. Then Main Menu handles the UI (not the HUD, which as discussed in other posts makes sense to be part of the Player).
Here’s the full thing laid out, and as you can see, each part handles its own screens.
If you want to see the whole thing in detail, check out my Game Template plugin, which has 2D and 3D example games and full instructions in the Readme in how to set it up. Y0ou can also check out the many plugins I have and how they work together to create the base for all my games.

