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.