Using the Ludum Dare 48 game jam we found together as a team of three (Impossible Robert, Bitmedic, Noskro) to build a game in just three days. We settled on a factory type of game and here I’ll describe how we made it.
Play it here: itch.io | Ludum Dare
Introduction
Early this year I was blown away by a game called Dyson Sphere Program. In there you start as a robot on a planet gathering some materials and then using these to build first machines that can produce something. Then you build conveyor belts to transport produced goods, gradually automating the manual collection process until the whole planet is filled with machines. Wonderful!
There are multiple other great games in this genre as well like Factorio or Satisfactory. For me it was always a wonder to see the increasing complexity and all processes working so nicely together and I wondered how an underlying model could look like that simulates such a world.
Ideation
Here a tip for everyone new to a game jam: try to get settled on the game idea, core loop and theme in around 2-3 hours and then just execute on it. We went with an Armageddon-themed setting, Asteroid going to hit earth, everyone going to die, destroy asteroid, save everyone, be the hero.
To fit somehow into the 72 hours we had available, we had to simplify a lot (and also removed all cut-scenes we had planned with Bruce Willis). We decided to have only 3 basic resources: water, iron ore, phosphor and 3 types of factories that would convert the resources into another product, with one factory using two different inputs to produce the final output (phosphor + steel = TNT). To be successful, water, steel and TNT had to be filled into the rocket drill.
Feature Planning
We knew early on we cannot recreate all functionalities in just three days but still wanted to push it as far as possible. The only obvious thing we cut in the end is the ability to switch recipes inside a single machine at runtime. That means each factory can only produce exactly one predefined output (which could be multiple hard-coded recipes though). Most of the other things are supported, some even without planning for them (but more on that later). Here the features we wanted to have in:
- be able to create any number of machines
- connect machines with each other through conveyor belts (we initially wanted to simplify this by using drones but decided to time-box it and check if we can get conveyors running, and we did)
- connect multiple conveyor belts to a single machine, e.g. feed multiple factories from a single resource
- produce materials in factories
- define an input recipe (e.g. 2 steel + 1 phosphor) which should result in a single or even multiple outputs (e.g. 2 TNT)
- have a maximum storage buffer for incoming materials, stop producing otherwise
- have a maximum storage buffer for outgoing materials, stop producing otherwise
- have an optional filter for accepted materials, otherwise they would not be taken in
- have a maximum input and output buffer regardless of materials, being an overall cap
- define a tick cost per production
- show items being transported visually
- easily test individual machines and also the whole assembly line
Data Model
Now it was time to sketch out a possible data model. The biggest break-though was to treat everything as a machine which would produce something, also conveyors and drills and even the rocket, with a specified input and output but using a different production strategy. In the end, the game supports now three strategies:
- Time: create resources inside the machine
- Formula: Convert input materials into output materials, consuming them
- Forward: Take material from input and put it into the output without changing it
Execution
Now it came to the simulation of the production process. The easiest way to ensure everything works is writing a bunch of unit tests checking all individual aspects. The first challenge was how production should be timed. We decided to introduce the concept of ticks. Each global tick would perform one step in each machine. If a machine for example takes 5 ticks to produce something, it would sit idle for 4 ticks.
Two issues arise when calculating the final state of a single tick:
- In which order should the machines be calculated? Start from the one that only consume and work our way backwards? What happens when there is a circle? Or a conveyor intersection?
- How can we ensure a machine will not produce twice, grab something twice and do a guaranteed step even when the successor machine is full and only cleared once all other machines have done their work?
The issues were solved using four approaches:
- Split a single tick into 3 phases: pre-tick, tick, post-tick. Pre will initialize the buffers and state of each machine. Tick will produce and write the results into a temporary output buffer. Post will flush the temp buffer into the final buffer. Using the temp buffer makes sure that outputs are not immediately consumed inside the current tick phase.
- Tick will first perform a grab and then a produce step. Only if both steps succeeded will the machine signal successful execution.
- If a machine signaled it could not yet complete all phases it will be ticked again once all other machines have iterated. This will be done x² times at max where x is the total number of machines.
- To enable multiple conveyors / outputs on a single machine one could implement a round-robin mechanism. Due to time constraints we fell back to simply shuffling all machines before each tick which results in the same when looked at over a longer period of time.
Example Test
[Test]
public void TestProduction()
{
MachineInfo info = new MachineInfo("m1").WithProduction(new Production("mat1"));
Machine machine = new Machine(info);
Assert.AreEqual(0, machine.outputStorage.Count);
machine.FullTick();
Assert.AreEqual(1, machine.outputStorage.Count);
}
Visualization
We used a 2d tile-map to place and show machinery with either 1, 4 or 9 spaces occupied.
Conveyors
Once the underlying data model was in place, visualizing the transport was rather easy. After each tick boxes would be drawn of the right material type onto machines that have something in their output storage.
The more tricky part as placing the conveyors. We wanted to make it as easy as possible and worked with a rule tile first, which would allow the user to simply click and drag a path and the tiles would rotate automatically as needed. Once all tiles have been placed, we initialize the underlying data model from that visual representation. While that worked, we found no way to extract what tiles have finally been placed by the rules and we could therefore not initialize our machine model correctly. That led us to the decision that users will have to place corners manually. It is a bit tedious but it is always deterministic and works.
Post-Jam
We had a blast developing this game and I myself lost myself a lot in just watching the conveyors transport goods and trying to optimize the system. If you like such types of games, give it a spin and tell us your thoughts!
We also continued a bit with experimentation and brainstorming, especially when it comes to more sophisticated conveyor mechanics like splitting, merging and circles and also looked again into automatic placement.
Since conveyors can have multiple outputs as well, splitters actually work with the existing model. We just don’t have a good graphical representation.
We also tried merging and since conveyors can have multiple inputs also that works out of the box, again just missing nice T-junctions for the conveyors.
The last thing we tried was how the system would handle circles. In the end, that is also just a merger and it works as well 🙂
Closing
We published the game on the Ludum Dare page and on itch.io and received a lot of motivating feedback. We also posted a small article on Reddit which got more upvotes than anything I ever posted. How cool is that. We are happy how everything turned out.
I hope you found some interesting insights reading this blog. Check out the github repo to see everything in action. And if there are question, just post them here, on Reddit or the Ludum Dare page!