Share Wars - Have fun bankrupting your friends!

Thank you!

While I work on the mentioned tasks for the next update, I thought since there are other developers here, I would like to document some of the underlying architecture and data flow.

For context, I started programming in 1989, when I was 10 years old. My father taught me BASIC, and we started to make games together. Since then I have worked on about 30 games, that are released, close to 80 if we include all the prototypes and abandoned projects. Even though every game is different, I still think deeply about architecture and data flow in hopes of finding an architecture that I can bring over to new projects. I might be a dreamer. :slight_smile:

Architecture
With Share Wars, I have so far, landed on a quite simple architecture.

The Main node is responsible for holding a reference to the UI and the game board (which is the playing field). It also loads two config files, one for system settings such as volume control and screen resolutions, and one for altering the behaviour and rules of the game. They are both saved in the TOML format, which are loaded via a custom C++ GDExtension I made. Once the extension gets mature enough, I’ll release it for everyone to use.

Main is also responsible for the startup sequence. Preloading data and showing the splash screen. When all is ready, it can instantiate a new Game.

The Game class does not need to live in the scene, and is therefore of type Object. The class is only responsible for instantiation of the core game classes, in addition to saving (serializing the current game to files on disk) and loading.

State is a pure data class that holds every piece of info that the game needs in order to function. The reason for sentralizing all game state in one class, is that it makes it very easy to find the data (locality is no longer a challenge), and it’s a lot easier to serialize and deserialize the data.

The Rules class is responsible for maintaining the game’s rules. It consists of functions that do and decides things.

Game mode is responsible for handling a specific way of playing the game. Currently I only have one mode, the single player mode. Later a mode for hot-seat multiplayer will be added, and lastly one for LAN party.

With a setup like this, I can combine game modes with different rule sets and configs, to give the player many different ways of playing the game. For myself, it’s an architecture that is simple enough to keep it all in my head at once. I can easily navigate the code and know where to put new code, and find old code.

Data flow
I’ve been through many projects with variying degrees of control over data flow. For Share Wars I wanted to enforce a more strict flow, in hopes of staying in control over the code for as long as possible.

The main guidelines behind the setup are:

  • Never use signals for decoupling modules that are critical for the game to work
  • Use direct function calls to push data
  • Listen for signals to react to data

The reason for sticking to direct function calls between critical modules, is to clearly see where data flows, and where responsibilty is delegated to. Many years ago I used Unity to make a game called Deflector (GitHub - mandarinx/Deflector), in which I extensively used ScriptableObjects for handling game events. It was a trend back then. It was such a horrible experience that I vowed to never decouple critical game modules using assets. But, it also led me to become more strict and explicit in my game code.

Mode calls functions directly on board and rules. It dictates, based on user input, what is supposed to happen to the game. The board never writes to state, only reads it. Rules can both read and write, and is the only one who can update the state. Rules can also use the UI to ask for input from the player. State emits signals when parts of the state has changed. The UI listens to the signals and updates when needed. The UI also picks up user input, such as button presses, and calls functions directly on mode.

With a setup like this, it’s easy to reason about the game. As state changes only happens in two places, any bugs related to game state are easy to find. And it’s very easy to follow the code.

We’ll see how well this architecture holds up. :smile:

5 Likes