Around The World, Part 22: Dropping the ball
I’ve done something that common wisdom in software development says you should never ever do: I started over. Let me explain why. This might get a bit technical…
Stumbling around
Progress on the game had been rather slow for a few months. This was in part due to lack of time, but it was also because of the code itself, and my feelings about it. I was getting increasingly annoyed by the fact that everything is harder on a sphere. The spherical shape of the world permeates every aspect of the code, from procedural generation to physics to navigation to rendering. It meant I had to develop bespoke solutions everywhere. Even if each of those solutions individually didn’t take up a lot of time, it was starting to add up, but I was nowhere near done dealing with this. And because the world geometry is so invasive, it’s hard to get rid of it without at least a partial rewrite.
I also realized that I don’t like C#. I started out using it because I needed something more performant than GDScript, and also because I wanted a statically typed language to keep the codebase maintainable. C# is supported as a first-class citizen in Godot, and I had a little prior experience with it (though outdated), so it seemed the obvious choice. Even though I’d never been particularly fond of the language, I figured it would let me get the job done, and the language might grow on me.
It did the opposite. Working in C# on the game, while using Rust for contract work, made me realize that I just like Rust much better. Despite Rust’s reputation of being hard to learn, the core language is actually simpler than C#, with fewer features that work together better. Moreover, C#’s documentation turned out to be mediocre at best – exceedingly verbose, but often failing to mention important details. Language features like inline arrays, which can be a huge performance boost in tight loops, are incompletely specified and the specification is still in draft after the feature has shipped. And the API docs don’t even consistently use a fixed-width font for code!
A third factor that was holding me back was the migration away from my custom ECS, which I never completed because I wanted to continue working on features. The codebase was messy and needed a significant amount of tedious work to get back to a good state. I would even get the occasional segmentation fault – something that should have been impossible using a managed language. (This very same error happened later with Rust as well, and turned out to be something that I thought was thread safe in Godot not actually being thread safe, so C# isn’t to blame here. But I didn’t know that at the time.)
All this was making me rather demotivated. If this were paid contract work, I would just power through, but for a hobby project that I’m doing for fun, it’s killing. If the fun is gone and unlikely to come back soon, the project is as good as dead.
Rust + GDScript = profit?
I eventually decided that the effort I was spending on the spherical world shape wasn’t worth it, considering that the improvement to gameplay would be small at best. I would be better off with a rectangular world, wrapping around at the east and west edges – essentially, a cylinder. But it was hard to make this happen without rewriting significant parts of code, and the game would not be runnable while I was working on this migration. It was an all or nothing affair.
Also, the landscape has shifted since I started working on this incarnation of the game about two years ago. For one, the Rust bindings in the form of the godot-rust library have matured, and are quite stable and usable these days. On top of that, static typing in GDScript continues to improve, with features like typed dictionaries being added to close the gaps.
All these factors led me to the decision that a clean start would be a good idea. I started working on this on Valentine’s day this year, using Rust for the heavy lifting, and GDScript for glueing everything together. So far, this has been a productive combination, playing off each language’s strengths: Rust is performant (or rather, gives the programmer full control over performance) and scales well to complex codebases, whereas GDScript is quick to iterate on, and interfaces well with the engine.
State of the world
Since I didn’t want to end up with a messy codebase once again, I spent the past three months working on a solid technical foundation. This effort is about halfway, but I want to finish it before building out more gameplay and content on top of it. That said, I couldn’t resist adding some nice new procedural generation features while porting, most notably erosion – but that’s still incomplete at this point, and warrants a blog post all of its own.
Here’s a screenshot of the current state of things:
Procedural generation at the global scale is mostly done, with only tectonic hotspots and polar ice caps remaining on the list. Adding local noise and erosion to each chunk still needs to be done as well, as you can see from the grid artifacts. Other main missing features are ports and maps, both so essential to gameplay.
I’m holding off on porting my fancy water shader until I figure out the art style, since painterly rendering turned out to be a non-starter. I think it’s going to be something more low-poly.
But I do now have a nice interactive in-game world map viewer, which is a huge help for inspecting and debugging:
Because this tooling is written in GDScript, it’s really easy and actually fun to quickly throw something like this together. Previously, I had built such tooling as an editor add-on. Having it in the game allows for interaction with the game itself, such as showing the ship’s position on the map, and right-clicking to teleport the ship to another location.
And since the world is no longer spherical, the map is actually accurate and not distorted anywhere. (You might have noticed that the world wraps around north/south as well. It was just easier to code this way, and it won’t matter once I add impassable ice sheets to the poles.)
With this reduction in scope, maybe, just maybe, a third-person 3D perspective is back on the table? I haven’t decided yet…