We’re nearing the end of what I’ll call the prologue of this series. We’ve covered some basic implementation, some basic physics and there’s two or three more things we need to cover before we can go into building this game. Let’s start with talking about the scale of the game.
Given that the goal is to be able to simulate an entire galaxy, with more or less a few hundred systems, each having between four and twenty planets, we’re going to have to deal with very large numbers, just like they do with very small ones. At this point, I recommend you familiarize yourself with how floating point numbers and their big brother (doubles) work and where the problem itself lies. For a quick summary, see the video below, for a more in depth explanation with further links there’s a blog post here.
Now that you’re familiar with the issue it should be obvious that the first thing we could do is to just use doubles instead of floats. Besides that not being a good solution on its own due to the fact that Unity’s physics only works with floats, it has other drawbacks, amongst which their size being double the one of a float.
For most cases out there, floating point operations are more than accurate enough. The thing to keep in mind here is that whilst for “normal size” numbers the error baked into floats is negligible, it goes up as the number increases or decreases. That is to say, you will have an issue with floating point numbers whenever dealing with very small or very big numbers or any combination of the two. In our case, we need the very large numbers. And we also need some very small numbers to add to them.
There is a simple way to make this problem easier. What we’re after is called hierarchical positioning. Essentially, we pick one unit for the positions of the solar systems, say astronomical units, then have the position of all the bodies within that system relative to the system’s center in kilometers. You could keep going and adding levels but for what we’re building, that’s going to be enough. This takes away the need for doubles when dealing with positions but makes calculations about distances and such somewhat more complex when going across solar systems.
On top of that, we need to be a bit more careful as to where the calculations happen. The best way is to apply all calculations related to the movement of the celestial bodies whilst within the solar system so everything is calculated relative to its center.
Even so, we still have a problem. Given that the player should be able to zoom in and out, we need a way to show everything correctly to the user. For this we need to employ two different strategies. First, is to have a scale factor that we’ll multiply all positions from the data layer with before showing it to the user. This way, we can maintain the correct behaviour of the physics whilst showing things at a larger or smaller scale to the user. The second one is the moving origin strategy.
Going back to what I said about floats previously. Since some solar systems would be very far away from the center of the galaxy, all of their planets will also be very far away from the origin. Whilst the physics calculations will be fine given that they’re done relative to the system’s center, the rendering won’t be as we’re very far away from the origin and we’d have to put very large floats into Unity’s transform component. This will cause issues.
To get around that we need a moving origin. Essentially, we store the psotiion of the player in a vector of doubles so that we can use very large numbers. Then, all of the positions of the celestial bodies we’ll be rendering will have to be subtracted from this one so that relatively, they are all still in the right place. Given that updating the origin means changing the relative positions of all the other objects in the scene it isn’t recommended to do this every single frame. Instead, we’ll only be updating the moving origin whenever the player is sufficiently away from the origin. There are a plethora of tutorials and details about how to implement this on the interwebs so I’m not going to go in more detail than that at this time.
Finally, let’s talk about the networking aspect. Initially I had planned to build a bespoke solution but in the end decided to go with Mirror. This way, most of the groudwork is already done. I will talk more about system architecture and microservices in a post closer to the development of that part.
With that out of the way and covered, it’s time to move on. Following this post, the series is going to turn into a devlog more than a tutorial except for the potentially interesting problem I come across.