Dec 2, 2020.Knowledge
Are there any general best practices or networking optimizations that should always be utilized?
- This article offers some methods and advice that may be useful, but ultimately, what works best will vary from game to game. Profiling will be necessary to determine where optimizations are necessary and which methods are most effective.
How can you achieve a high number of replicated actors in a multiplayer game?
- Any optimizations that can be made to how the actors are replicated will help increase the number of replicated actors that are supported:
Performance and Bandwidth Tips | Unreal Engine Documentation
- Dormancy is likely going to be the biggest help in this situation, especially if these actors are statically placed and are not changing often. Setting these actors to be initially dormant means that they won’t replicate until they’re set to awake or FlushNetDormancy is called on them (ForceNetUpdate will also flush dormancy if needed). If an actor’s properties change very infrequently, it can remain dormant and only call FlushNetDormancy when those changes happen. These optimizations make sure that inactive actors incur no networking cost.
- If your map is large enough, you can also adjust each actor’s NetCullDistanceSquared to tune distance based relevancy for the awake actors, ensuring that distant replicated actors are not incurring a cost as well.
- You can also adjust the NetUpdateFrequency for each actor, so less important actors are updated less often. This frequency, set by default to 100 times per second, is capped by the framerate, so actors will attempt to replicate every frame if the net update frequency is greater than the framerate. Most gameplay actors will likely not need to update that frequently. For important property updates, you can call ForceNetUpdate to force an update immediately regardless of the frequency.
What’s the difference between creating an actor with a dormancy of initial vs dormant all?
- Initial dormancy is only compatible with statically placed replicated actors, and has no overhead in the replication system until the first time it is replicated. Dormant all is for any other dormant actor, such as a dynamically spawned actor or an initially dormant actor that was woken up and then set back to dormant.
What are the recommended practices in order to support a high player count?
- A big help in this situation will be the replication graph, but the optimizations mentioned before that can help with achieving a high number of replicated actors will also help with a high player count as well. In fact, you may see larger performance improvements from utilizing dormancy and other optimizations than from using repgraph, and it may be easier to utilize these optimizations than to implement repgraph in your game.
- Repgraph can be used to coordinate replication, giving you greater control over when actors are replicated to a connection. By using persistent, shared nodes which quickly generate replication lists for each client, the replication graph can eliminate the common bottleneck of checking every actor for each connection, allowing for higher player and replicated actor counts.
- The ShooterGame sample project (available through the Epic Games Launcher) provides an implementation example of the replication graph, and you can find more info in ReplicationGraph.h/.cpp and ShooterReplicationGraph.h/.cpp.
Can it help to split an actor into a collection of multiple actors in order to optimize for different update frequencies?
- In general, if you really need to do that, then this can help.
- Push Model Replication, added in 4.25, may also be helpful in this situation.
What does Push Model Replication optimize?
- Currently, the engine checks replicated properties each net update in order to determine if the property has changed and needs to be replicated. Push model skips these comparisons for properties that are not marked as dirty, saving time by not needing to compare every property every net update.
- It’s worth noting that internally, we haven’t seen many performance gains from enabling push model, although this may vary for different projects.
How and when should you use fast array replication?
- Fast array replication optimizes the replication of TArrays of UStructs, and offers performance improvements for large data sets with infrequently changing elements, optimized removals from the array, and client-side events for adds and removals.
- A detailed walkthrough with an example can be found in NetSerialization.h, but here is a summary:
- Make your struct inherit from FFastArraySerializerItem.
- Wrap your TArray, which must be named “Items,” in another struct that inherits from FFastArraySerializer. This struct must include a NetDeltaSerialize function (see NetSerialization.h for example).
- The struct wrapping the TArray should have WithNetDeltaSerializer=true in its TStructOpsTypeTraits.
- Finally, declare a UPROPERTY for your struct wrapping the TArray.
- There are a few things to keep in mind when using fast array replication. You need to mark elements of the array as dirty when changed, and you have to mark the array as dirty when removing an element. Marking the elements as dirty helps reduce overhead when it comes to the number of comparisons performed, but if the elements of the array are changing frequently enough, you may lose the benefits gained from marking the elements as dirty. Also, the order of the elements is not guaranteed to be consistent between the client and server.
Do you have any tips on how to combat latency spikes when spawning new replicated actors on a large scale?
- Profiling will be important to determine the source of the spike, and there are a number of approaches that may help depending on the cause:
- The engine default bandwidth limit may be too low and can be raised. The default was raised to 100KB/sec for 4.26, which should handle most use cases.
- Simplify the actors, such as by reducing the number of components, to speed up spawning.
- Spread out the spawn operations on the server to prevent using up too much bandwidth or CPU time on the same frame.
- Pool the actors by statically placing them in the map, and then using dormancy, “spawn” them by waking them up and “destroy” them by setting them as dormant.