MINIMIZING BANDWIDTH

MINIMIZING BANDWIDTH

It is important to think about what kind of data you are sending across the network. Ideally, you should be sending the absolute minimum amount of data needed to make your game work correctly on all clients.
There are several reasons for minimizing bandwidth in a video game.
  • Clients could be on low-bandwidth networks (wireless, modems, etc).
  • A large part of the costs for hosting multiplayer game servers comes from bandwidth.
  • The less packets that are sent, the less re-sends will have to be done. This reduces the network load on the server, which normally is the bottleneck, especially when you use reliable RPCs and reliable state-syncs.

How data is synchronized

How you decide to synchronize data affects how much bandwidth you use. You have a choice of using either unreliable, reliable or reliable delta-compressed synchronization mode in each network view.
Unreliable means everything is sent during each iteration of the network update loop. This update loop is executed according to what is set by uLink.Network.sendRate, by default 15 times per second. By using unreliable synchronization you ensure frequent updates of information and you don't care if updates are dropped or delayed. Dropped or delayed packets are ignored. For objects that frequently change state this might be the best way of synchronizing.
Bear in mind the amount of data that is constantly being sent. For example, if you are synchronizing just one transform between two computers using unreliable state-sync, you are sending 9 float values, which is 36 Bytes, plus about 10 bytes of uLink packet header, summing up to 46 Bytes per update. With a send rate of 15 packets per second, this amounts to 690 Bytes of data per second. For the server, when running as authoritative with 40 clients, this means sending 27.6 KBytes/s to clients.
This kind of bandwidth calculation is very good to do when noticing that the game lags on a normal connection. Also if the game server uses too much bandwidth, the hosting cost might become a problem when releasing the game. The bandwidth for incoming traffic to a server is usually small when comparing to the outgoing traffic; it is seldom a problem. A rule of thumb is to put your effort into minimizing the outgoing traffic on the server side.
You can have a dramatic effect on the network load by decreasing the send rate of the network updates. For fast-paced games this should be quite high (like 25), but for slow-paced games it can be lower. You should experiment to find a reasonable tradeoff between update frequency and network load for your game.
When using reliable state-sync traffic the bandwidth and the CPU load will increase. The additional bandwidth comes from handling sequence numbers in acknowledge messages. On the other hand, this send mode guarantees that state-sync updates do arrive and are all applied at the destination.
Reliable delta compressed can be used for state-syncs to save a lot of bandwidth. Here, only the difference between the last sent state and the current state is sent. If there is no difference then nothing is sent. This greatly decreases the bandwidth used by objects that do not change much during a game. A completely static object send no updates at all. Because of some extra header data, using reliable delta-compressed state-sync for objects that continuously change state is inefficient. The exact impact has to be measured when you test your game.
Keep in mind that reliable traffic can affect the latency of the state synchronization in a negative way, packets might arrive delayed because they needed to be resent. In this case no state-sync is received at all until the dropped packet arrives.

What data is synchronized

You should think creatively about how your game is to be synchronized so that it appears to be identical on all clients. It might not be necessary for everything to be exactly the same, a good example is synchronizing animation. Imagine that you have a character with many different animations, like walking, running, jumping etc. You want the character to appear the same to all clients, within reasonable bounds. If you put an Animation component directly as an observed component in a network view then it will be exactly synchronized across the network. The network view will send time, state and whether it is enabled or not. This generates a lot of traffic as time constantly changes between state-syncs. There are another easy and efficient alternative to doing this.
The very best way for minimizining bandwidth is not to send animation states at all. This is what we recommend. Animations like standing still, walking and running can be calculated in each client based on the velocity. And other animations like jumping, kicking and crouching can be activated when RPCs for these actions are received. This way the bandwidth need for synchronizing animations will be minimal.
Another tip is to take a look at your transform synchronization. For example, in some cases the rotation floats can be replaced by a single angle, which can save a lot of bandwidth. Imagine a game where the player's avatar can rotate left and right, but not along any other axis. In this case you could replace the three rotation floats with one float (4 Bytes) or a short integer (2 bytes) indicating the rotation angle. You should always make considerations like this to reduce your network traffic.
In uLink it is possible to implement your own BitStreamCodec class and put all the serialization logic for a network object (a prefab) there. This is a good place to put the code for bandwidth optimizations.

Culling - when to synchronize data

Is it really necessary to always keep a game completely in sync on all clients? Think about a game where there are large buildings all over the place or big race tracks that go into forests or over and under hills. When one client is out of sight from another client there is no need to keep them synchronized with each other. This can greatly decrease the load on the server as it doesn't have to send full updates to every client. This is sometimes called using relevant sets. You have a set of relevancy for each client which defines what data it needs to receive. Usually, commercial multiplayer games have some kind of distance culling, which means that the game server only sends state-syncs for a certain game object to the players that are close enough. This can save a lot of bandwidth if players and NPCs are scattered over a large area in the virtual world.
Culling also plays an important role to prevent wall hacks and making sure that the client only get information that it is entitled to. The client doesn't need the position of a cloaked enemy or any other entities that are not displayed.

Network Level of Detail

The general way to handle bandwidth restrictions is by Network Level of Detail or in short Network LODing. This determine the frequency of network updates a client get about an object's position. Depending on the objects importance, speed, distance and if it is an ally or an enemy, among other things, a value for the update rate is calculated and is balanced against the bandwidth limitations for the client.

Culling and LODing with Pikko Server

When using uLink with Pikko Server, you get very powerful tools for handling network culling and LODing. The design of how it will work in uLink alone is something we have very interesting ideas about and we are updating this section with the information when it is available.