Looking for general code/scene organization tips for game logic

Godot Version

4.4

Question

Hello, I’m new to Godot and I’m trying to create a simple incremental Idle Game to learn it. It will consist of different systems working together to create always bigger numbers, nothing fancy. It will be mostly UI based, with each system having its own UI section etc. All systems will mostly run concurrently, with let’s say a bunch of Timer nodes handling the generation of currencies or whatever these systems do. My current setup looks like this (simplified):

RootNode:

  • Some global managers (currencies, save system…)
  • UI (with a tab node):
    • System 1 UI
    • System 2 UI

My question is, where is in your opinion the best place to put the systems’ logic in? Taking an example, my first system will be a simple battle loop, where the player character auto battles enemies, giving rewards upon defeating them. It will then have to include some sprites/animation/UI logic. The game logic in this case will be things like handling the attacks from both parties with timers, consequences of a battle, progression in this system etc. From what I’ve gathered, I can either:

  • Have all the logic directly in the relevant UI nodes (but I’m not sure if having UI/game logic fully mixed is a great idea)
  • Have the logic in a singleton in my game root and have the UI respond to it through signals (one or more per system)
  • Have a mix of both, for example handling the characters attacks and their direct consequences in their nodes and having the more global logic in a system manager singleton

For now I’m thinking that generally the third option might be the most practical, and maybe the second one in some specific cases. If I understood correctly, it’s not an issue having some game logic in the UI in my case as long as all my systems’ scenes are loaded, so they can run in the background.

To continue with the same example, it would be something like this:

RootNode:

  • Some global managers
  • System 1 global manager(s)
  • UI (with a tab node):
    • System 1 UI
      • Some UI
      • Player (Sprite, Timer, Stats, Animation…)
      • Enemy (Sprite, Timer, Stats, Animation…)

I would love to get some perspectives on this as I’m mostly designing this on the top of my head based on docs/tutorials. Let me know if anything isn’t clear. Thanks!

It’s really going to depend on your personal tastes, as well as things like how independent the components are. If you have a game where there’s a lot of communications between components, you’ll probably find things easier if they’re grouped more. If your components can mostly operate independently without communicating much, spreading them out will probably make things easier.

There’s a signal mechanism that will let you spread things out, but be a little careful of it for the usual reasons; async messaging can be handy, but it can very easily lead you into a nightmare of spaghetti code where flow control becomes impossible to trace.

Personally, I’ve got a bit of a split in my project; I’ve got a main “game level” file that has a lot of the basic logic in it, but also a lot of nodes that have their own scripts, particularly for things like enemy mobs that have situation-dependent behaviors.

Thanks for the answer. I think I’m leaning toward what you are describing, with the equivalent of the “game level” file being split into multiple scripts in my case. I’m trying to have individual component fully independent from each other, as I don’t know yet what future systems I might want to implement but at the same time having some global managers for the common important parts of the game (currencies for instance, or anything that could be used by multiple components regularly), which handle the global game state. I hope that by doing so I will be able to integrate new individual components easily later on.

I’ve set up some save/load system, and I’m thinking of making it so, as a rule of thumb, I only update saved data from those global managers, while local components only handle their local, non saved, state and eventually read/write to the global manager using their defined functions or signals.

That seems like a fine place to start.

As for game logic in the UI, don’t. At least my preference is not to.

For me the UI gets a signal that a value has changed, and changes it on the screen. Or it accepts an input somehow, and emits a signal saying the input has happened. Nothing more than that. Then you are free to fiddle with your UI all you like, reskinning it, laying it out differently, changing the way you display values from text, to progress bars, to sound, to whatever. All the logic is elsewhere.

My game files use to look something like this at the highest level:

Scenes
Scripts
Assets
Autoloads
Components
Actors
Modules
Utilities
…etc

But recently, and I mean a day or two ago, I watched a video about why this was the case. The arguement being made that your highest levels should reflect your game.

I spent most of today and yesterday trying out a new way and my highest level folders now look more like this:

Ecosystems
GameWorld
Creatures
Plants
Player
Maps
Systems (A boring one slipped in there)
Behaviours
LifeCycles

So far the re-organisation has left me feeling so much better about the structure. Although I had some tough questions about where to put things sometimes and it took a couple of tries and reworks. Basically, if I find I cannot find something naturally with this structure, then something is in the wrong place.

But as @hexgrid rightly pointed out:

And depend on your situation too, if you are solo or in a team. I mean if you have a team of artists, then I can see why all your art assets should be in one folder, same for sound and scripts if you have sound teams and development teams etc.

But for me, I really like the game orientated folders, as opposed to function. Yes my catalogs or classes are all over the place, but honestly, when was the last time you needed to have them all in one place? If I am working on a spider, everything related to that spider is in the spider folder.

Works for me at least. There is not right way.

PS at the lowest levels, say the spider example, there would be folders for art, scripts, sounds, resources, etc. in the spider folder.

PPS I realised I didn’t really answer your question. The battles you mentioned would be in a stand alone (not autoload) script called BattleManager. Your power ups would be in the PowerUPsManger. Your turns decided in the TurnManager. These could be nodes in your tree or just classes you reference. Your UI does not really sound like a UI, some of it sounds like a viewport on say a battle scene. I try to severely limit the use of Autoloads unless they are absolutely necessary, as I used to abuse them endlessly.

As for game logic in the UI, don’t. At least my preference is not to.

That was my initial idea too and started with this approach, but my problem is that all of my game will be based on UI. Like the combat system will just be some animation inside the UI, with not direct interaction with the player. And later on, I would like to implement more complex systems, things like minigames or such. If I do all the logic outside the UI, I fear having to listen to UI events from the managers, then do some logics, then have the UI listen and update itself will become an overly complex spaghetti. My idea is having a Manager for each (sub)system which is called by the UI only for things that actually change the global state of the game (gaining some currency, buying an upgrade…), and the local logic is in its related component, in the UI (might not be UI nodes though, idk). For example, the UI handle a complex minigame locally, and only at the end call one or more Managers to create coins, update the highscore or whatever it needs to do.

I agree though that mixing seems strange and can potentially lead to similar if not worse spaghetti code depending on how its implemented, especially for finding who does what, debugging etc, or so I think at least. Sadly I did not manage to find a lot of examples of such UI based game (especially complex games), not yet at least, so I’m struggling a bit on this. The fact that I’m trying to dev something relatively complex compared to my experience with godot is not helping me too.

Also about the autoload, I’m currently not using it. I’m using a singleton pattern I saw on some tutorial that I think is practically the same thing for any of these global Managers. No idea if its a good practice or not but it seems to work pretty well for now in my case. Basically I just do this for all those managers:

class_name Manager
extend Node

static var ref: Manager

func _init():
    if not ref: ref = self
    else: queue_free()

#Then Manager specific code

I place this script in a Node somewhere inside my main scene , and I can then access it anywhere using Manager.ref, with the same instance everywhere. Might have to use autoload soon as I’m beginning to make some global enums though.

But recently, and I mean a day or two ago, I watched a video about why this was the case. The arguement being made that your highest levels should reflect your game.

I think I saw the same video, or a similar one at least (mine was from ‘DevDuck’) and I completely agree too. Feels way more natural, easy to understand and even plan the project as you think of the file structure. Completely switch to this, and I’m happy that I learned this early on.

Rather than doing singleton shenanigans, you can just create a global script. Global scripts are always loaded and always available by name. For example, I have a PlaySound.gd script that’s loaded as a global, which means in any code I can PlaySound.explode() or PlaySound.MouseClick() and it just works.

@hexgrid has a point. It can become a pain and there is virtually no overhead with global singletons.

For your UI with minigames, that is not really a UI IMHO. It is a viewport on a scene running a minigame (even if that means with no user input). So your actual UI box that contains it would just load up a scene, say “battle_minigame”, the battle will tell the UI with a signal that it is finished and the UI element would return to the default state, say a blank box.

Now you can develop your minigame just like any scene. Load in two creatures (again independent creature scenes or just creature resources) and let it run. When it completes it emits signals for “coins earned” or “life modified” etc and finally “minigame_complete”. Your UI element then unloads it ready for the next one.

Now your UI is light, and just does what it is told.

Your actual workings are contained in separate scenes you can develop individually for minigames and for whatever other things you have in mind.

The player state would be in its own resource file with a manager to access it. Like ‘total coins’ or ‘agression levels’ or whatever.

As far as I can see that keeps it all modular, easily customisable but more important, easy to maintain.

Anyway, good luck with it, I hope it goes well.

PS I suppose there is no rule about this, but a UI is an interface to something else. It is like the users graphical interface to game systems. Jamming your game systems into your UI just feels wrong.

For your UI with minigames, that is not really a UI IMHO. It is a viewport on a scene running a minigame (even if that means with no user input). So your actual UI box that contains it would just load up a scene, say “battle_minigame”, the battle will tell the UI with a signal that it is finished and the UI element would return to the default state, say a blank box.

PS I suppose there is no rule about this, but a UI is an interface to something else. It is like the users graphical interface to game systems. Jamming your game systems into your UI just feels wrong.

That’s about what I though of doing, but I think I was kind of confusing pure UI and actual game elements, the might be build through 2D or UI nodes. I guess I will see as I implement it how well I manage to keep it clean.

Thank you both for the inputs!

2 Likes