Devlog 1
Frantic Factory
Introduction
Welcome to my first update on Frantic Factories.
Frantic Factories is a factory management game I’m developing that will challenge players to fully optimize various small factories; building conveyors and machinery to take inputs and produce the required outputs.
While hardly revolutionary, this idea was born from a desire to combine and simplify aspects of my favorite games. I enjoy the management and problem solving aspects of games such as mindustry and oxygen not included, but usually prefer games like mini motorways that have less of a time sink to get to the fun parts. I don’t usually have hours to dedicate to a single playthrough, so I’d like to reduce time requirements allowing players to build and complete a factory in just 15 - 20 minutes. I also envision each level feeling a bit like a puzzle, which combined with leaderboards, will challenge players to compete against each other to build the most optimized factory possible.
Many details are still up in the air, so stick around for future devlogs to see how the game progresses and changes over time.
Update
So far I’ve been focused on building the core engine that will power the game and handle simulating a factory. Each level in the game will be represented using a grid of tiles, such as conveyors and items. Every second or so I will update the simulation one step, moving items one tile at a time according to the conveyor they are on. These discrete steps will make the simulation fast, but more importantly, repeatable. Players should be able to build an understanding of how each tile works, and get consistent results from consistent inputs.
The basic simulation algorithm is pretty simple. Everytime we want to update the simulation we will iterate over every tile in the level, and store a list of changes made by that tile. For example a conveyor would store a change moving an item one cell in the direction it is facing. After processing all tiles we’ll apply the list of changes to calculate the new state of the simulation.
We could let tiles update the game state directly, but this can lead to some weird situations where an item gets moved twice in one update. There are some other niceties to this approach that will be useful in the future, such as rolling back updates or canceling an update halfway through.
Now that we have a goal, let’s see how quickly we can screw up the initial code.
Code
My first goal is to create the structures for our basic game elements, a tile and tilemap.
At its core a tile is anything that can be updated over the course of the game, such as an item or conveyor. The only real requirement is that a tile implements a tick function, to let us know how to update it when we run the simulation. We’ll give the tile information about the current game state, and it will return a list of changes we should make to update the simulation.
I also require tiles to implement a format function, that’s just useful for debugging. I’ll show you guys what that looks like soon. Keep in mind I have done zero graphical work so far. All the animations were made just for this video.
The tilemap on the other hand is responsible for storing the positions of all tiles in the game. Three hashmaps are used to store tile information, and these let us lookup tiles by position, type, and id. And yes, I’m sure there’s a better way, but sometimes you just gotta go with the first solution you think of and move on. I’ve also gone ahead and added helper functions for adding and accessing tiles, so if I do want to change out the underlying data structures later it shouldn’t affect the rest of the code too much.
Now that the basic building blocks are in place let’s get to the fun part! Making a conveyor actually do something.
Let’s take a look at the tick function for a conveyor.
The logic here is simple. First we’ll check if there is an item on the conveyor to move. Next we’ll calculate the position we should move the item to, and return the position as a requested change to the item’s position.
Ok, after a lot more floundering that you probably don’t particularly care about, let’s see what we’ve managed to build! Oh and just for fun (and not at all because I can’t be bothered to create another animation) lets take a look at that debug view I mentioned earlier.
Each update I display the current tilemap using characters, and add some additional information about how long each update took the computer to process below. Mostly I’m just ignoring that number for now, a decision I’m sure won’t bite me in the ass down the road.
Add optimization to the to do list. Cross out the previous spider todo and add a new one.
While working on this visualization I created two new tile types, a spawner to create items at set intervals, and an incinerator to destroy items. I also set up some code to randomly generate conveyors for easy testing. Here’s a few more examples. It’s pretty satisfying to finally have something working.
Problems
Okay what we have so far clearly works, but it is very dumb. For example the program will happily move two items onto the same cell, drop items off of conveyors, and has no idea how to handle intersections.
Let’s quickly fix most of those issues.
A few changes later and we now have checks to prevent moving more than one item onto a cell and stop conveyors from dropping items on the ground.
I also used this time to finally add some unit tests to the game, something I’ve been wanting to do for a while.
To make testing easier I added an `eq` function to the trait definition that lets us compare tiles for equality. This led me down a bit of a rabbit hole trying to figure out how to use rust’s PartialEq trait with a box… long story short it doesn’t work, and frankly I don’t know enough about rust internals at this point to explain why.
Let’s take a quick look at how the eq function works for a conveyor. Since we are passing around boxed Tile traits here we actually first cast the tile as any and then use `downcast_ref` to cast it to a conveyor struct. This is honestly pretty cool, and doesn’t even require unsafe code. I’m using this same technique in the tilemap to query for specific tile types at a given position. This will return the tile if the cast succeeded, or None.
You may remember that I still haven’t properly implemented intersections. Getting intersections working in a predictable way is the last hurdle I’d like to clear before moving on to graphics. The main issue is determining which conveyor has the right of way. Take this four way intersection for example. We can only move one tile to the incinerator in the center, but which conveyor should get to move its item? Should we alternate between all conveyors like a traffic light? Should “straight” conveyors take precedence?
For inspiration let’s take a look at some other games with conveyors to see how they solved this problem.
Mindustry, a factory management tower defense game, handles this problem quite nicely by simply alternating between conveyors at a 2 way intersection.
One game I thought would be useful to look at is Baba is You. It’s a tightly programmed grid based puzzle game that has conveyors. Turns out though it just cheats! Look at this nonsense, the items just overlap. I hoped a Baba would be more useful since it also updates tiles in a grid, but alas, no such luck.
Other games like satisfactory and shapez.io don’t allow conveyors to be connected to each other directly. Instead they force players to use a dedicated merge building that essentially removes intersections from the game.
I want to limit the number of buildings in my game, so I think the solution that makes the most sense is to alternate between conveyor directions at an intersection.
Solution
To accomplish this let’s go back to our tile definition and modify it slightly, adding a priority function. Each tick of the simulation we’ll order tiles according to their priority and then update them in order.
Here’s the new priority function that handles conveyor intersections. It’s a mess, but basically every tick we change which conveyor in the intersection is updated first.
It might be better to construct a graph to more intelligently process interdependent conveyors, and there is still plenty that could be done to improve this simulation… but it’ll work for now! Here’s how intersections behave after these changes, look how orderly everything is!
Conclusion
My next order of business is to start adding actual graphics to the game, and maybe even a user interface. Let’s not get too crazy though!
Stay awesome.