🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Need help optimizing the bandwidth

Started by
7 comments, last by TufanMeric 5 years, 2 months ago

I made a diep.io-like game but i think it uses way too much data, 30 seconds in battle (dozens of bullets flying around) means 10MB download for client. I only send entity updates when they are changed 60 times a second, the only thing I don't send in game loop is attack animation, I send attack animation packet to all players instantly after player starts attacking.

I'm using Binary WebSockets with Node.js ws library, I tried making packets as small as possible. Is this normal for a game or am I doing something wrong?

Advertisement

What is normal depends on the game.  What are you sending?

When possible, try to send events with timestamps.  Avoid sending things that can be calculated.  For example, knowing when a bullet starts and it's initial parameters is all you need to know where the bullet is, you don't need to update it's position every frame.  When items are stationary or slowly moving based on unchanging physics information it has no need for network updates.

Player information that is constantly changing does need to be updated frequently. What you update depends on your implementation details, but for diep.io, you'll likely be sending data frequently.

Are you sending events like "bullet fired", or are you sending updates like "bullet moved, bullet moved, bullet moved..."?

4 minutes ago, frob said:

What is normal depends on the game.  What are you sending?

When possible, try to send events with timestamps.  Avoid sending things that can be calculated.  For example, knowing when a bullet starts and it's initial parameters is all you need to know where the bullet is, you don't need to update it's position every frame.  When items are stationary or slowly moving based on unchanging physics information it has no need for network updates.

Player information that is constantly changing does need to be updated frequently. What you update depends on your implementation details, but for diep.io, you'll likely be sending data frequently.

Are you sending events like "bullet fired", or are you sending updates like "bullet moved, bullet moved, bullet moved..."?

I'm currently sending events like [bullet moved...x60/second], will try to change that. Except that, at this point I can't add any client side prediction without making huge changes.

52 minutes ago, frob said:

Are you sending events like "bullet fired", or are you sending updates like "bullet moved, bullet moved, bullet moved..."?

I managed to make it 936KB/30Sec, I lowered update frequency from 60/sec to 30/sec, made bullets move client-side, disabled updates for inactive entities and bam! 15MB/30s to 1MB/30s. My wallet and players' internet connections thanks you, kind sir.

7 hours ago, TufanMeric said:

I'm using Binary WebSockets with Node.js ws library, I tried making packets as small as possible. Is this normal for a game or am I doing something wrong?

You might potentially see some gains through compression as well, depending on the exact data and how much you can group together (one frames worth?). Even with binary, it is unlikely the data is essentially random with no patterns to compresss, but it depends on how you structured the data exactly (e.g. lots of 32bit ints that are probably less than 65536 or even 256 / signed equivalents?).

Hi TufanMeric,

I’ve been working on a game with some similarity to the one you mentioned.

Here’s some things to consider:

1. 60 updates per second seems excessive. Even 30 might be more than necessary if your players don't change velocity quickly. I send 20 per second, i.e. one every two physics frames. Instead of trying to send an update for every rendering or physics frame, send fewer updates and smooth/interpolate/extrapolate the motion client-side.

2. Don’t send clients irrelevant data. The client does not need to know about things that are out of its view. For my game, players only receive updates for other players and events within a distance of 1000 units, i.e. just further than the player can see. Things that are further away but that the player would still hear (e.g. gunshots and explosions) are sent as 3-bytes (1 byte for the id of the sound to play and 2 bytes for an approximation of the location).

3. Each packet you send comes with significant overhead, so try to reduce the overall number of packets. For instance, my client, rather than sending a separate packet to the server every time the player fires a bullet, appends the necessary information about the bullet to the outgoing movement updates. Since the client is sending movement updates 20Hz and the game’s physics run at 40Hz, my players can only fire bullets on even numbered physics frames, but in-game this restriction isn’t very noticeable.

4. Try to pack your data densely. Often, data does not need to be sent in the same format or resolution in which it is stored locally. For example, my client sends position data as well as input data. It sends the player’s last four X/Y positions (two of which will be new for the sever and two for the purpose of redundancy to reduce the effect of lost and out-of-order packets). Locally, these values are stored as floats. That’s eight floats, or 24 bytes. However, as the maximum play area is 7500x7500, I send the player’s current position using 2-byte integers and thereby achieve a resolution of about 0.11 – the inaccuracy is totally invisible. Then, because players can move at most 3.5 units in a single physics frame, the other three positions are sent as 1-byte relative values (which provides a resolution of about 0.01). So instead of sending 24 bytes of positional data, I only send 10. Ditto for input: all the player’s input can be packed into a single byte rather than sending a separate byte for every key.

My examples mainly concerned client-to-server communication, but naturally, the same applies to data flowing the other direction.

What do your update packets look like?

Some other simple ideas:

If you extrapolate entities from the last update (something like timepos = updatepos + updatevelocity * (nowtime - updatetime)) then you can calculate this value on the server, and only send an update if the extrapolated position would be significantly different from the actual position. (If you're using UDP, you'll want to send occasional snapshots as well, in case packets are lost, but it sounds like you're using websockets?)

If you're using websockets, consider writing a binary serializer (on top of Uint8Array) instead of sending JSON encoded objects.

Consider sending all events in a single packet each network tick. Even if people fire bullets, enqueue a message to be included in the next packet, rather than sending it directly.

 

enum Bool { True, False, FileNotFound };

Thank you all for the help, it's a lot better now.

This topic is closed to new replies.

Advertisement