I love space games. I’ve been writing them for 16 years (and not releasing them for 15 of those). Here are some things you may not have considered about space games (especially of the 3D variety).
This article deals with player positions. We’re assuming a multiplayer game, or a game with NPCs that have to move precisely. We’ll deal with what is precise enough as we go.
Space is big
I know, right?
Most of the things you want to see and fly to in space are huge and separated by huge distances.
Space is small, too
Not everything in space is big. Enemies and friendlies and cargo and things like that are much smaller. You might not want to model individual dust grains, but it’s reasonable to think that you’ll have things that are very much smaller than a planet, like the player.
To handle the smaller things, the most convenient unit of distance is a meter. It is usually convenient for artists, too. If you make it something larger, your models lose precision, which can lead to very bad results like intersecting geometry not looking right.
Change in space is gradual
Planets orbit in smooth ellipses. You can fire a thruster and move very slowly. Asteroids and cargo can be nudged out of position. All of these things are small changes in a sea of big numbers.
The simplest coordinate system doesn’t work
The usual way of representing object coordinates is a vertex of three floats — x, y, and z. Floating point numbers have 32 bits to represent the number you put into them. For normal numbers with certain fractions, they are often rounded a little, but usually not in a noticeable way.
The larger the number gets, the less precise a floating point’s fractional part is. The number automatically adjusts to have more binary digits — bits — for the whole number part. It’s done with exponents; you can imagine a floating decimal point.
The practical result of this is that a coordinate system that would work well somewhere near (0,0,0) starts to break down when you are a long way away. How does it break down?
Since it is using more bits for the whole number part, the fractional part is rounded off, to powers of 1/2. When the fractional part is down to 4 bits, the smallest change that can be represented is (1/2)^4 = 1/16. That’s already a big problem.
1/16 = 0.0625, or a little more than 6 cm if each unit is 1 meter. Assuming the best case for operations and rounding, that means that if your object doesn’t move 3.125 cm (to the next rounding point) between times that your engine looks at it, then it doesn’t move at all. And your engine will probably process objects 60 times per second, or more.
So, with 4 bits of fractional precision, the slowest any object can be moving is 60 * 6.25 cm = 360 cm/s, or about 13 km/h. They can have lower velocities, but when you add the speed to their position, nothing happens. The difference is rounded out, and they don’t move.
Imagine trying to drink a soft drink with 6 cm/s precision. Either your hand doesn’t move, or you’re throwing the can at your face.
Is this something you have to worry about in your game? I think it is.
Earth - Sun distance, km: 150 000 000
Earth - Sun distance, m: 150 000 000 000
As a binary integer: 10 1110 1100 1011 0010 0101 1100 0000 0000
Length of Earth - Sun distance integer: 34 bits
34 bits is bad. The regular floating point representation is 32 bits, and of that, 1 bit is the sign and 7 bits is the exponent (which indicates where the decimal point is). You have 24 bits of precision and 34 bits of number, so 10 bits of distance information get rounded off.
That means rounding to the nearest 512 m. Any difference less than a half kilometer in distance is zero. And, at 60 engine ticks per second, minimum velocity is almost 31 km/s. Escape velocity from Earth is about half that.
This is terribly wrong.
What about doubles?
Double precision helps in this case. It means that instead of 1+7+24 bits to express positions and speeds, you have 1+11+52 bits. 52 bits of precision is not almost twice as good; it’s two to the power of eighteen times as good. Awesome!
So, at Earth’s distance, where we know the whole number part is 34 bits, 52-34 leaves 18 fractional bits of precision. Each meter is divided into 262 144 parts. That’s fantastic.
However, we didn’t come all this way just to stay on Earth. Let’s try Pluto this time. Pluto’s orbit is very irregular, but we can use ballpark numbers:
Pluto semi-major axis, km: 5 874 000 000 km
This is almost 40 AU, or 40 times the distance from the Sun to Earth.
Pluto semi-major axis, m: 5 874 000 000 000 m
Pluto semi-major axis, binary integer: 101 0101 0111 1010 0101 1010 1110 1111 0100 0000 0000
Length of Pluto-Sun distance integer: 42 bits
42 bits is fewer than the 52 bits of precision we get in a double-precision number, so it looks pretty good. What kind of precision does 10 bits get us? 1 part in 512, or about 2 millimeters. That’s good positional precision.
The problem is velocity. At 60 engine ticks per second, the slowest non-zero speed is 11 cm/sec.
That’s not very good. Imagine you and your friend want to meet in orbit around Pluto. You show off your piloting skills by braking to park next to your friend. But instead of coming to a smooth stop, you’re slowing down in differences of about 0.4 km/h. If you bump into your friend gently, either your friend won’t move, or your friend will move quite a bit.
Triple precision? Quadruple?
Floating point numbers of arbitrary position exist, but they’re not for games. It’s all about what can be done in hardware.
Most computers have 64 bit processors now; they can do many 64 bit math operations in one step. Obviously, 32-bit computers (or computers with 32-bit operating systems that can’t send 64-bit instructions to the CPU) are going to do these calculations more slowly. Slow is bad, and your game is spending a lot of time adding numbers when it could be doing things that the player would actually appreciate.
But, you can fake extra precision. Faking things is really what space games are all about.
Space Faking using Frames of Reference
Reconsider this scenario: Two players are out by Pluto, trying to move slowly and finding that there’s insufficient precision. Where would there be sufficient precision?
Closer to the Sun, of course. We already saw that 40 times nearer to the sun, everything is very precise. In fact, as we approach (0,0,0), we have up to 52 bits of precision, which is enough precision to store the size of individual protons. Yeah, seriously.
Having the Sun be the center point of our position calculations makes calculating orbits very easy. But what if we kept two sets of coordinates?
Keep one set where the Sun is at (0,0,0). Keep another set where the player is near (0,0,0).
That way, the player’s coordinates are broken into two parts. One part is huge numbers that say, I’m near Pluto. Call this the reference frame. The other part is small numbers that say, I’m really close to my friend. Call this the local position.
When the local position starts to get large, all we do is change the reference frame, by adding the local position to it. Then we reset the local position to 0, and we have 52 bits of precision again.
At any point, a player’s position is their reference frame position plus their local precision. It doesn’t matter if your friend’s reference frame isn’t quite the same as your own when you’re trying to park next to each other, because all the changes are being applied to the local precision, and if you subtract each other’s reference frames first, all of the subsequent calculations deal with reasonably small numbers. When you separate and fly off to explore the universe, there’s no change required in each reference frame until you’re far apart — say, as far as the Earth is from the Sun. There might be a tiny jump in position at that time, relative to each other, but you’ll never see it.
Some of these problems can be avoided by making your solar systems smaller or by choosing a game type that avoids small movements. But if you make those decisions, you’re accepting certain limitations going forward, and you have to remain conscious of them.
Ignoring floating point numbers and how they work is a good way to walk straight into a jitter problem, where you know you’re doing the math right but the ship is jumping around from frame to frame.
There is a big issue, not addressed here, related to the fact that most 3D rendering is only single-precision; 32-bit. But we can deal with that in another article.
Good luck with your space game!