Page 1 of 1

Strategies for improving Unity networking performance

PostPosted: Mon Apr 17, 2017 4:13 pm
by BenChang
If you're finding lag and choppy performance in a networked game, there are a few things you can try.

First, look in the NetworkManager component, look at the "Advanced Configuration" and "Use Network Simulation" checkboxes. The Network Simulation will let you simulate different conditions for ping and packet loss while running locally, which will help identify problems earlier. Another first diagnostic step is to see how the lag changes when different objects are in play; does it increase proportionately to the number of moving things, or is it just consistently bad? Is the lag different for player objects and for other NetworkTransform objects? Try timing RPC calls or SyncVar changes and see what their latency is. All this can help narrow down where the issue is, whether it's with specific networked objects or components or if it's at the lower transport layer.

Quality of Service (QoS) Settings
In NetworkManager's advanced config, you can set up QoS channels. By default there are two: Channel 0 is "Reliable Sequenced" and Channel 1 is "Unreliable". This makes Channel 0 analogous to TCP/IP (guaranteed delivery of packets, in order), and Channel 1 analogous to UDP. Under the hood, all of UNET uses UDP; these different channel modes are a finer-grained set of variable performance and reliability that in theory should let you have lots of control over exactly how your game behaves.

This blog post explains the different QoS settings:

All About the Unity Networking Transport Layer

The problem is, as far as I can tell you can only set channels explicitly when using the low-level transport layer, not the HLAPI (things like NetworkTransform, etc). Total speculation: it's possible that everything in the HLAPI just uses channel 0, so you can try changing its mode and see what happens.

NetworkTransform tweaks: Send Interval and other parameters

Take a look at these parameters in the Network Transform:

  • Network Send Rate (sendInterval): number of updates per second. The default, 9, means the apparent network frame rate will never be faster than 9 FPS.
  • Movement threshold: how much the object needs to move to trigger a sync update. Small values would seem to equal more accuracy, but may not really be necessary - consider if you really need millimeter precision. Making this larger can cut down on total updates, reducing bandwidth.
  • Interpolate movement: (1=true, 0=false) if updates are only coming 9 times per second, the movement will always look choppy. Interpolation lets the transform smoothly move (LERP) towards each updated position. Think of it like tweening in animation, the sync updates are key frames and interpolation is the tweens.
  • Snap threshold: with a fast-moving object, the new updated position may be so far that interpolation will take too long. Movements with distance greater than this threshold will just snap to the new position instead of LERPing. For fast objects like projectiles, balancing the send rate and snap threshold can sometimes help.
  • Rotation Axis: if your object will have rotation constrained to only one axis, or won't rotate at all, change this setting to reduce bandwidth.
  • Interpolate rotate: like interpolate position, LERP to the new rotation on updates
  • Compression: compress the rotation data to reduce bandwidth, but has some cost on send and receive

Roll Your Own Network Transform!

If nothing else works, you can work at a lower level. For player sync, the built in HLAPI components are usually fine, but if you're having a hard time with a lot of other stuff moving around you may need to get lower level control.

In the end, smooth network behavior comes down to prediction. There will (almost) always be lag, but what matters is disguising it from the player. With prediction, you make a guess about the future state of an object, and display that to the player even if you haven't gotten an authoritative update yet. Yes, your games are lying to you. :o :o :o

With a good prediction algorithm you'll actually be right most of the time, so the next thing is to gracefully handle situations where the prediction is wrong.

Prediction for player movement is a little tricky, and this is where techniques like data-driven machine learning come into play. For more predictable objects, like projectiles, it can be a little easier.

Here's a basic idea for a projectile with deterministic motion, like something just moving linearly with a constant velocity.

  • Spawn the projectile using the usual instantiation and spawning
  • Instead of using the NetworkTransform for updating position, we should actually be able to predict the position at any frame because the motion is deterministic, e.g. x(t) = x[0] + t*v where v is a constant velocity.
  • So, we could break the rule about only moving things on the server and sending updates, because the server and all clients should be able to run their own copy of the simulation and it should remain in sync
  • Collision detection code DOES run only the server, so when the projectile hits something the resulting events, despawning, etc are propagated using ClientRPC calls.
  • For projectiles that will be around for a while, you can still send periodic sync updates from the server just in case of some drift. SyncVar, ClientRPC's, or if you want, LLAPI transport layer calls can be used for this.

When all else fails, read the source

Unity makes the source code for the UNET DLL available under an MIT/X11 license, so you can inspect it to figure out how it works since the documentation is pretty poor. And, you can modify it however you like and use the modified version with your game instead.

When all else fails, use a different engine

"When the only tool you know is a hammer, every problem looks like a nail"

This is a use-case scenario for both UE4 and for Amazon Lumberyard. Unreal is built with multiplayer as a core feature of the engine and makes it relatively transparent through Blueprint. Lumberyard has a sort of in-between approach using the GridMate Gem, where a lot of high-level functionality is provided out of the box but you have access to a lot of low-level customization. Of the three, UE4 is probably the easiest to get working reliably right away, though Lumberyard's idea is that you can then massively scale up using cloud-backed services like GameLift when you need to go from a few concurrent players up to thousands.