Advertisement

What is the proper math to compress properly player position in a best possible way?

Started by June 08, 2023 07:34 PM
10 comments, last by hplus0603 1 year, 5 months ago

Hi everybody!
I always sent the whole float or compress using the scene AABB and send int16_t instead of float:

int16_t compressedX = static_cast<int16_t>((x / halfSizeAABB.x) * 32767.0f);

And to uncompress:

constexpr float scaleFactor = 1.0f / 32767.0f;
float uncompressedX = (static_cast<float>(x) * scaleFactor) * aabbHalfSize.m_x;

But I guess it's not good enough and better math is possible.
How to improve it properly?
Thanks a lot!

You could combine delta encoding with the 16-bit quantization to reduce the size more. If the position has changed by less than [-128,+127], then you can send the difference as a int8_t, and since they are integers you will get the same result as if you sent the 16 bit position twice. That way you only need to send the full position once, then the rest of the time you only send the 8-bit deltas. If position changes too much you can send the full position as a fallback, or limit movement speed.

You should also be using something like zlib to compress the whole packet (of multiple players/objects positions) which can result in more savings.

Advertisement

Alundra said:
int16_t compressedX = static_cast((x / halfSizeAABB.x) * 32767.0f);

Let's verify you do not exceed precision range, which then is -32767 to 32767. Signed int 16 has range -32768 to 32767, so you're fine.
But you may want to clamp (x / halfSizeAABB.x) to be within range -1 to 1 anyway, so in case some object is slightly outside the AABB the error is bound. If such cases can eventually happen.

Aressera said:
You should also be using something like zlib to compress the whole packet (of multiple players/objects positions) which can result in more savings.

I'm using facebooks open source zstd. Never used a compression lib before, but it seems very good. Often close to Oodle / Kraken in results, and fast too. Way better than zlib for sure and similar interface.

I saw Unreal uses a technique to compress position with single decimal precision.
Apparently it sends the number of bits that was needed with the value using maximum 20 bits.
Is it a better solution?

Is it a better solution?

It should be sending a large amount of data through network. Moving overhead from network traffic to CPU de/compression, I would say yes and normal.

None

Alundra said:
I saw Unreal uses a technique to compress position with single decimal precision. Apparently it sends the number of bits that was needed with the value using maximum 20 bits. Is it a better solution?

Unreal provides some general purpose serialization functions. For position it has FVector_NetQuantize type which as you wrote is up to 20 bits, so 60 bits max per vector, they also have FVector_NetQuantize10 (up to 24 bits each / 72 bits combined max) and FVector_NetQuantize100 (up to 30 bits / 90 bits max) that round to one or two decimal places, and FVector_NetQuantizeNormal for normalized vectors with a max of (16 bits / 48 bits max). You lose some precision but gain network performance, a generally acceptable tradeoff when the clients aren't in lockstep.

Unreal has specialized classes for character movement in addition to general purpose vectors and tracks differences between past updates. A typical ServerMovedPacked is 250 bits but may be a little different, a ClientMoveResponsePacked is 55 bits but may be a little different. It includes the packet ID, a timestamp for the update, acceleration, location, rotation, and may include some optional values related to player movement (e.g. walking state, character dual motion data, pending motion, etc.) values if the character has them. It has a bit of logic around sending the relative versus absolute values, etc.

Advertisement

Alundra said:
How to improve it properly?

Not like this. You should think on it more deeply how to avoid this bandwidth issue, rather than trying to solve the problems with muscles and compression. Your problem is: why your protocol wastes so much bandwidth. Your problem is not how to compress this and that. You are trying to solve the wrong problem.

Is it a better solution?

I think it depends on details of the data that are sent. Say a moving object, its speed limitation is 5m/s. Say the frame rate is 50 FPS, then at most 0.1m moving happens in every frame. If single decimal precision is enough for the accuracy, only one bit is required to store the moving distance. For moving direction, if the number of directions is set to eight, then three bits are for the moving direction. If so, only total four bits can hold sufficient data for calculating the position after moving. For a real design of this approach, more bits for moving and directions would be taken for better data-lost tolerance.

None

In general, you should know what you have previously sent to the client, and you can base the next update based on that.

If you're using a lossy channel, then you can instead know what the client has last acknowledged, and send updates based on that. This needs you to number packets and keep some maximal acknowledgement window, and each packet needs to include a signal of what the last acknowledged packet seen was.

Another alternative is to just send inputs, and use deterministic (-ish) simulation/physics, and only send occasional baseline/checkpoint updates – say, once every 5 seconds or so. Plus send corrections when non-predictable events happen on the server, such as server-side collisions between players.

However, this is a huge space of design possibilities, and which method is “best” depends a lot on what the specifics of your game are. A twitch shooter with line-trace weapons and limited number of players per level, like Counter-Strike, is very different from a social RPG MMO with a largely fixed terrain like World of Warcraft, is very different from a racing/driving simulation like Forza, is very different from a virtual world with user construction like Roblox. Targeting mobile phones or worldwide play is very different from targeting local LAN and geographically near hosted servers. You need to make it clear what your gameplay demands, and why, and then design your physics/simulation, rendering, and networking, to cooperate to generate the experience you desire.

enum Bool { True, False, FileNotFound };

Thanks for all the answers.

The goal is to understand a proper or acceptable default for a game engine.

This topic is closed to new replies.

Advertisement