This is to be expected. ![]()
I assume that ācompleting tutorialsā means youāre generally new to game development, or just this particular engine, therefore roadblocking yourself is normal.
Whatās a āgame stateā? This could be which menu screen you are in, what game mode you play, what level the player is currently playing, or a collection of numerous values that indicate progress. Thereās a lot of individual states to be found in a game.
You need to be precise including to yourself what you mean by that, otherwise you will build the same obstructions.
Exactly, because that explanation forces you to think of these things and unless you do that and have a clear understanding of what these things are and how they relate to each other, you will continue to write code where architecture evolves by chance and split-second decisions.
You need to create a software architecture first, then the implementation becomes a lot easier. However thatās easier said than done if youāve never done that before. So just try to improve one thing at a time and consider how it connects back to the overall structure.
Your sample shows youāve only considered very few, very broad aspects of what your game is to become: Story, Play/Pause state, and some UI screens/overlays. What you need to think of is systems - what advances the game UI? What makes the player interact with the world? What makes enemies tick? What makes the story progress? And so forth. Those are individual systems you ought to define.
One of the guiding principles of software architecture is to find dependencies and define the interaction between systems. Because the last thing you want is a situation where itās not clear whether the player tells the UI to do something, or whether the UI keeps responding to signals from the player - and worst of all: both at the same time (thatās called a circular dependency and singletons make those especially easy to set up).
.. is the anti-pattern for designing a proper architecture. ![]()
Think of whatās in the name: it implies itās the code that manages the game, in its entirety. That is too many concerns for a single object/class to take on. Itās like in Godot you wouldnāt have various nodes but rather a single class called āGodotā that encompasses all of its functionality.
And no, Iām not suggesting to implement the same deep OOP hierarchy - that too is an anti-pattern.
Hereās a food for thought and an exercise for you: design your game to deliberately avoid any singleton (or static). This forces you to consider who initializes/constructs what, and who owns what, and in which ways information flows.
Start with coming up with a single, central system that doesnāt use the word āmanagerā (itās too broad) which owns the state of a small, confined, independent aspect of your game. Say the UI screens: Main Menu - Level Selection. Just those two, managed by āMenuScreenStateā which is a Node in the startup scene (not a singleton) and that allows you to toggle between two fullscreen menus - whichever way you do so but it must happen in a manner where MenuScreenState does not go out of scope.
Next you design how to transition into the gameplay scene - which unloads the menu and loads a game scene, where you do the same with a āPlayerStateā node that determines whether your player is moving and interacting, or perhaps frozen in place due to a dialogue or cutscene.
Then to persist state, you need a mechanism - the simple stupid solution is to write a file every time you transition between menu or game (or next level). But how can you keep that state in-memory without a singleton?
That requires a new solution: you no longer swap out Menu with Gameplay scenes but rather have a single, overarching āgameā scene that never unloads. You simply load and unload the scene that is the menu into that SceneTree, and the same with the Gameplay scene. This means anything in the overarching āgameā scene persists in your game automatically. And thatās where you will have your data-holding nodes for āProgressā and āSettingsā and so forth. Since they only initialize once when the game launches, itās also the ideal place to load the persisted game state from files in their _ready method.
And thatās how you flesh out a design by thinking about the technical aspects that need to happen, not focusing on what you āseeā on the screen - thatās the result you want to get. Diving into that requires experience and you will get it wrong for a while, but once you make the transition from thinking of your game visually to how it operates technically (what data exists and how it is modified), youāre halfway there. ![]()