The future’s premiere Factories in Space game has made great strides this past month. It’s inching ever closer to its debut release in Alpha form to the select few waiting with white knuckles in the discord server.
- Basic networked inputs to move ships or players
- Physics for ships, including collisions
- Began streaming my work
- Embraced a more generic approach to syncronising from Rust (Specs) to Godot.
Conclusion of the Asteroids Arcade Game
Between 25th Oct and 3rd Nov I hacked up a top-down arcade asteroid breaking game. It’s got a ship model! It’s got particles! Laser physics! Giant Space Rocks!
The theory seemed simple enough. A wave shooter where you upgraded a mining ship and blasted asteroids (which came down the screen) to deliver these to your waiting mother-ship at the end of each wave. Sound familiar?
I wanted a simple platform to hone my action game development skills, since I need to integrate similar elements into the full TRS games when players pilot their ships.
However, I noticed my motivation waning. Natural motivation is a very valuable resource for me — more important than learning the action elements. Making a top down shooter was a task that anyone else can do, even better than me. The game I want to make is the full (and complex) Recall Singularity. So I put this project aside early Nov.
Don’t worry, you’ll be breaking asteroids in your little mining ships soon enough. It will be the first “gameplay loop” of the larger game.
So, if I’m not working on an action spaceship game, I better start turning TRS into the action game it’s supposed to be! Ships that move! Other things! Exclamation points!
Godot has a perfectly good physics engine. But I cannot use that — My pure rust server requires matching physics to maintain the world state. I needed a rusty physics engine. Enter nphysics and the specs ECS integration aptly named specs-physics.
After integrating these I wrote code which would create “colliders” based on ship shapes (those colliders are why the ships edges hit above). Then I realized I needed to refine the management of my Specs worlds. we’ll come back to the ships shortly.
From Many Different Worlds
In The Recall Singularity, open space is split into “Sectors” which are discrete units of gameplay, consider them maps. Many ships and players might visit a sector and travel between sectors the player uses jump gates.
Each sector is split into two types of worlds:
- A Star World, which contains ships, players, asteroids and anything action oriented.
- Many Production Floor worlds where each one manage the factory INSIDE a specific ship. It might be necessary to break very large ships into parts.
I spent a week (5th..11th) ensuring there was a nice way to manage each of these different types of worlds in code. Each one needs its own list of “Components” and logic which will run every tick. I also plan for them both to have different network models.
The Production Floor is a complex simulation that does not require low-latency inputs (because I keep the player in the star world). So it can use a pure Lock-Step simulation where each player waits for actions from the server and then simulates exactly the same world changes as the server because the simulation is deterministic.
Actions could be one of
- Place a module (conveyor, scoop, box, etc)
- Remove one of these modules
- Set the current recipe on a refinery / fabricator
- Take items from a box
Only when a player gets near a specific spaceship will the server share details about its internals. The Player’s computer only simulates ships near themselves —wondering not what fantastic and complex things happen in a space-station far away. This allows sectors to be more complex than any normal client can simulate.
The Space world you see has a different goal. I want flying the ships to feel like an arcade game or an Action RPG — fast, responsive, fun. This requires a different networking model and approach to work.
Fortunately for the Star World the amount of actual content is quite low. There will be a small enough amount of stuff here (asteroids, ships, etc) that the player can just be told about them as they are encountered. We could also handle a much more complex multiplayer model — Lockstep with prediction and lookahead.
So this week was spent ensuring that the Production floor world inside a ship managed all the conveyors, storage and so on and updated those. The star world needed different components — the SpaceShips (which each track a specific ProductionFloor world), physics and a basic player.
Adding Ship Physics Cont.
With the world initialization split, I could now begin running the physics loop inside just the star ship world. I got the basics up and running and quickly hacked up a simple integration of Physics positions over to Godot. At this point I was able to record a GIF of ships drifting under momentum calculated by physics when given initial motion.
Streaming my Development
It was around here that I decided to start streaming the development of The Recall Singularity on Twitch and also to YouTube. For instance, you can watch me begin attempting to port my code to the Laminar UDP library. I’ve done most of my development on stream for the rest of November.
I’m not able to share my code open-source, but I want to do what I can to support the Rust, Godot and GameDev communities. That’s why I’m writing tutorials, why I’m sharing my progress in such detail. Streaming seemed like a natural extension of this — perhaps you could pick up things from my process you didn’t know? It’s also motivational. It sure is easier to stay on track and avoid randomly browsing Reddit when there are dozens of people watching over my shoulder.
Will I keep doing it for the rest of the project? We’ll see…
A Detour into Laminar
When I thought about how hard it was to synchronize my Specs simulation with the Godot visuals I knew I needed to move the ownership of the Sector object (which contains a HashMap of Worlds) from the network thread into a function where it could be owned by Godot (running in the Godot thread).
When researching alternatives to the now unmaintained cobalt UDP library, I thought that Laminar was ready to use. It’s certainly looking promising. Laminar is designed around the concept of the network thread doing just networking and sending messages back over Crossbeam queues.
I spent a week moving my existing Client and server across to Laminar on stream. When I finally got it mostly running I began running into issues where Laminar internally seems to be blocking outgoing packets while it waits for acknowledgement or similar. Struck by “why the hell am I replacing working code with something new??” I’ve switched back to my previous Cobalt implementation for now. Painful to say the least!
So for now I’ve put the Laminar work onto a feature branch to collect dust. Will it sit there until I’m insane again? Until the library has matured some more? Time will tell.
Instead I took the cobalt implementation out of the network thread and had it run from the Godot “Fixed Physics Frame.” At 60 fps we poll the network and update the world, inside the Godot Thread. Mission accomplished.
Networked Player Input
I’ve already quickly hacked up experimental client-side player movement totally within Godot (done late October) and this allows you to move your little player but no-one else can see you do it.
While streaming, created a new “Action” (to be sent via the networking) which contains PlayerInput. This is sent to the server each frame and populated with player keyboard inputs. Like every Action in TRS it’s also forwarded to every other client. This means you can move the player on one computer and see the changes on every client. Exciting!
Via hard-coding I can also have it move an entire space-ship, which is how we created that wonderful crash shown at the top of the article. Soon to be added is the ability to interact with objects. When you interact with a console, the player will be able to take control of a ship and have their input directed to flying that.
Generic binding between Godot and Specs
I began to notice a repetitive pattern where I was continually adding a feature to my game logic (and so to the Specs world) and then needing to write some synchronization logic in my Godot bindings specifically to load the correct scene and copy specific data into the scene matching the new game entity.
Given this issue, I’ve decided to move from hand-written bindings per object type to a more generic approach. I’ve added a system for each type of thing (Ship modules, Ships, Bipeds) which converts them into a Godot template (scene or mesh) along with a 3D position. Then a standard system (GdEntityCreation) makes sure a Spatial exists. Finally GdEntityTransform will put it in the right place each frame.
This is still a work in progress as of today (3rd December) but I hope to write a tutorial and worked example next month once I’ve got a refined version working in my own game.
Thanks for reading!