Hi all, not sure if this is the right place to post but I’ve personally been really struggling with organizing my projects in a way that’s simple, readable, and understandable – for example, I’m currently attempting to recreate chess inside Godot, but I’ve reached the point where everything is a mess, I have no idea what’s going on, and debugging is a nightmare.
Part of the problem may be that I heavily rely upon autoloaded scripts in order to access various useful functions and variables, e.g. I have a global script called “GameManager” whose sole purpose is to keep track of game metadata, such as which chess pieces are on what tiles etc. How would I accomplish this without global scripts? This is information about the game that a lot of different scripts need to know about, e.g. a piece needs to know if a tile is occupied or not when considering movement.
I am pretty comfortable when it comes to individual programming tasks, but making them all work together in a cohesive way is something I struggle with for sure. Are there any resources out there that could fill in these gaps in my knowledge?
One tip for making any source code readable, is to use comments. You place comments in your source code that explain what each part of the code does.
In Godot the comment tag is ‘#’ This tells the compiler to ignore anything after that symbol.
A well commented source code, is a well documented source code.
I think for this kind of project you should create a chessboard scene, which has the knowledge of the chess board, e.g. it saves the chess position.
Then you give the chess board the method to calculate all possible moves for a given piece. Then i guess you create a player scene which will recieve the chessboard as a variable through the game_scene in its ready method. this way the players can access all the chessboard methods, but dont have to store any knowledge themselfes.
A lot of it comes through experience but i think especially for board games: Implement the things like they are in the board game. Think about what thing needs what knowledge and how can i distribute that knowledge. So i would usually create one board script through which the players can interact with the board
There’s a discussion to be had here about “Good code should explain what it’s doing” but yes, when starting out, comments are useful, especially if you’re not the only person working on a project, but imo writing documentation is better, as some people see comments as “code smell”.
So the best practice would be to simply “load” scenes into other scenes in order to access data, e.g. a reference to “board.tscn” is created in the form of a variable in the player script? Sorry if I’m not understanding, but wouldn’t this eventually create a lot of dependencies between scenes?
Object Oriented Programming was created to solve just this kind of problem. Organize your code by behavior, not by functions (the two will frequently overlap, though).
You can read about program structure, but there is no replacement for getting your hands dirty and experimenting. I have gone through several structural arrangements over the years, and find something useful in most of them.
You’re going to run into dead ends before you get a feel for a good structure. Each dead end will teach you something.
At some point, some parts of your game are going to have to know about each other. There is no such thing as complete separation, but I follow the “call down, signal up” pattern to encourage reuseability.
I have reusable utility classes as autoloads, and those utility classes form an important foundation.
Unfortunately there’s no substitute for experience and some things just take a while to get a feel for, but I found the book Clean Code by Robert C Martin to be a very useful resource for learning how to improve code organization.
I too have tried to reduce the amount of code in global/autoload files in my projects, it just feels wrong to have so much stuff there. I may be straying from the intended question and I know there are many ways to solve problems but here’s an approach that has helped me.
I have got round some of the autoload challenges by simply putting my equivalent of your chess GameManager with all the game metadata in a script with a class_name which extends Node. My main game scene then has a node of this type alongside all of the UI and Node3D objects that actually get rendered.
I’m working on a warehouse packing kind of game and at the moment I’m trying to build a level/scenario designer. The various panels of my UI control such things as the different types of objects I’m allowed to place in my level and the metadata has counts of what is already there etc. The UI is mostly child scene instances and for me the big breakthrough has been the use of exported variables that easily link up all those ui objects with the game metadata. I no longer need global variables etc. because all the relevant components now have easy pointers to each other that can be set in the editor.
Here are the first few lines from my metadata manager script:-
###
# LevelDesignManager.gd
# handles all the metadata and object lists and links during Level Design
###
class_name LevelDesignManager extends Node
#region exported variables
# these panels point to the parents of the various listboxes
@export var object_list: PanelContainer
@export var object_properties: PanelContainer
@export var buildable_objects: PanelContainer
# the Node3D parent for all game objects
@export var game_objects: Node3D
#endregion
Here is the node tree from my main designer scene showing where I put this data manager alongside the various UI panels:-
Here are the editor settings for the LevelDesignManager node:-
Each of the scripts for the UI panels has an exported variable for the metadata node:-
Here are the editor settings for the ObjectList node in the main scene node:-
The data manager can now populate the UI panels with e.g. object_list.AddStuff() and the various UI panel scripts can now easily get to the metadata by calling e.g. ldm.FetchStuff().
I haven’t ended up with less code than I used to have in a global file, but I feel much happier about the organisation of my files and controlling the scope of things that used to be global. I certainly intend to reuse this structure in other situations.
There have been a number of good suggestions already. Here are mine, with hopefully some more concrete suggestions on how to organize your code.
It sounds like you are suffering from a lack of understanding about the Object-Orient Programming concept of Encapsulation. Basically, the objects in your game should deal with their own data. If someone else needs to know about that data - ask the object responsible for it.
With a chess game, you have 8 pieces. Each piece has its own rules for how it can move and attack. Each piece should know that about itself. The chessboard itself is the object that needs to know where all pieces are at all times. It should also probably keep track of the pieces that have been eliminated.
None of this requires anything global.
Your players (real and AI) can then interface with the pieces. Just as in a real game, if they wanted to know what the board looks like, they would look at the board.
One thing that’s helped me a lot is the understanding that refactoring your code is an essential part of the process. When I’m trying to get something to work, it’s usually a messy process of trial and error which results in code everywhere (this is becoming less of a problem as I write more code). Because of this, I dedicate time after I get a feature to work to go back through my code and separate things out into functions, or in some cases new files entirely so that the messy code becomes
More readable
Less prone to weird, hard to trace bugs.
Ultimately, this is a time consuming process and it’s completely okay to say “OK, for the next hour or two I’m not going to work on adding anything new, I’m going to organize what I already have.”
This is likely not a direct answer to the question, but I thought of just weighing in because I developed a prototype chess game using UE Blueprints in conjunction with a hook to the Stockfish engine via Python.
One of the reasons why it was manageable using UE’s BPs was because Stockfish was doing all the chess management work in the ‘backend’. The other component was the ‘frontend’ whose responsibility was communicating with Stockfish about user input, and receiving the result back from it, and adhering to the results.
(I’ve made a number of other action/adventure/rpg prototypes. If I hadn’t tried to make a chess game myself I might suggest principles I’ve learned making them: to split things up. But I couldn’t suggest it. )
If applied to a bespoke chess game in Godot, I would likely go with the same idea: I’d split the chess framework (engine) separately from the ‘visual’ representation of it. I.e. I’d reduce the problem to making sure that I can issue questions to the chess engine and get answers, e.g. what is the current board position?; available moves for this piece?; etc. Then let the chess engine accept, validate, and execute movement commands and update the board positions.
Try to validate/debug your chess framework separately from all other implementations you’re doing in Godot. Don’t let the two mix in together. Ideally, you should be able to ‘play’ a chess game by issuing commands in the chess framework. The visual representation is just ‘icing on the cake.’
(If you’re not already using Stockfish, or don’t intend to, you can still learn about the scope of what your chess framework would need by studying what’s available.)
Addendum: that said, in practical terms, the chess framework in Godot would be composed a singleton that holds the board position data. Query funcs that are needed would be static funcs that will likely accept the entire board data as first input. Action funcs (movement), will modify the board position data. Again, my first step would be the ‘play against’ this chess framework, validating all the query and action funcs run against it are actually correct. If the framework is sound, then the visual implementation would simply be making sure it adheres to the board data.
I have yet to use autoloads or globals, I am sure at somepoint I’ll find a use for them. However, in your case it would be simple enough if the gameboard is made up of tiles like a chessboard, each tile has a coordinance. The player doesnt need to know if a tile is filled. The player queries the gameboard and the gameboard returns which tiles the player can move to.
This could be done with having a dictionary with each tile coordinance as the key, and either the object occupying it or null to indicate if it is occupied or not.
There are 1000 ways to go about it, I find once I have the code working I generally end up rewriting it a few times in the process of improving and decoupling.