Project Overview

Project Overview

Simulation games offer a unique challenge. They rely on complex entities that appear to work in tandem using intricate rules and operations.

Suppose every object that’s part of the game world knew about everything around them and polled for that information all the time in code. In that case, you’d have a messy spaghetti of relationships and dependencies that would make extending the game with new objects difficult.

We want to code the game so that every entity (machine, tree, wire…) in the world can function independently without knowing about any others around them. Whenever they need to interact, such as power traveling down a wire from a generator, we’ll use a higher-level system to handle this communication and update everything.

Entities themselves should remain naive, lightweight, and easy to create. This can also help with performance as entities do not need to poll other entities around them.

Note that general game engines like Godot or Unity have a limit on the low-level performance optimization they can perform. Implementing some algorithms or code structures tailored to massive simulations can require specialized engine features.

If you want to create a game with tens of thousands of entities updating in real-time all interacting, you may need to either modify Godot’s source code or write a custom engine.

How we’ll structure the code

Here’s a birds-eye view of how we’ll structure our simulation game’s code. This diagram uses the battery entity we’ll implement by the end of the series as an example. We didn’t include all components to keep the graph readable. But the relationships of composition you can see above will apply to other entities as well.

As we discussed above, we want our simulation’s entities to work independently. They should not connect or know about one another.

At the same time, they need to communicate with one another. To solve this problem, we can use a top-level parent node that coordinates the simulation. It’s the Simulation class in the above diagram.

The Simulation node will delegate work to other sub-systems. It will do little work itself.

We’ll coarsely split the rest of the project into modules:

  1. Entities.
  2. Systems.
  3. User Interface components.

Entities

Entities are all the objects that are part of the game world and the player can interact with: machines, trees, branches, and ores. They exist as “entity” scenes in the world.

They also exist in the player’s inventory as “blueprint” scenes. You can place or remove them from the game grid, or generally interact with them.

We’ll code an object to place entities, the EntityPlacer. Its role will be to manage the grid and keep track of the game world’s entities.

The player’s representation will also live along the entities to control the camera and keep track of the range at which the player can place and interact with objects around them.

Systems

Systems will keep track of the position and layout of relevant entities and update themselves accordingly. They will then be prompted by the Simulation to update all relevant entities.

For example, we’ll explore how to build a power system to provide electricity to machines connected via wires, and in the next chapter, a “work” system that will allow our furnaces to smelt ore into ingots.

User interface components

We’ll separate the User Interface (UI) from the game world by having it on its own canvas layer.

It’ll contain the inventory grid and the quick-access toolbar, among others. There, we’ll also draw specific interfaces, for instance, for smelting.

Having the UI on a dedicated layer helps to:

  1. Always keep it in front of the map. It can also intercept mouse clicks and other inputs that way.
  2. Prevent you from coupling UI code to other game systems, which removes an important source of bugs.

It’s generally a good practice, although it comes with its challenges, as you’ll see in the second chapter, where we’ll work on the user interface.

Community Discussion