Replication is one of the most important concepts in Unreal Engine games as it is the basis for keeping game clients synchronized with the game server in network games.
While details have slightly evolved between engine generations, the basic principles are still the same. Generally there are three major types of replication: actor replication, variable value replication and function call replication. Replication can only go from the server to one or more clients, or from one client to the server, but never from one client to another directly. Replication does not happen immediately, but is delayed until the end of the tick, when things are replicated to the remote side. At the start of the next tick, things received from the remote side take effect.
Actor replication
Before any other kind of replication is possible, there needs to be a common ground the server and client can base their communication on. This common ground are actor instances. Variable values and function calls can only be replicated if both sides know which actor instance the replicated value or function call belongs to.
Actor classes that have bStatic or bNoDelete set to True can neither be created nor destroyed at runtime. We'll call these "persistent actors" from here on, as opposed to non-persistent actors that have both bStatic and bNoDelete set to False. Persistent actors make replication pretty simple as they already exist when the level is loaded and they will continue to exist until a different level is loaded or the game exits.
Non-persistent actors, on the other hand, may initially exist in the level but could get destroyed at runtime. Or new instances of a non-persistent actor class are spawned at runtime. Imagine a non-persistent actor that initially existed in the current level, but gets destroyed on the server. Now if a client connects, what will happen to the clientside version of this actor? The Unreal Engine takes a conservative approach here: When a level is loaded on a client, all non-persistent actors are removed there. Then the server will replicate non-persistent actors back to the client without making a difference between initially existing actor and actors spawned at runtime.
Actors can't be replicated all the time. Especially when a client connects the server would have to send a great number of actors, taking up valuable bandwidth. The trick is, that the server will only replicate an actor to a specific client if the actor is "relevant" to that client. Many actors are completely excluded from replication because clients simply do not need them. Usually, when an actor is no longer relevant, it is removed from the client again, but there are also exceptions. If a replicated actor is destroyed on the server, it will also be destroyed on all clients it is currently relevant to.
The most important exception are "net-temporary" actors, i.e. instances of actor classes that have bNetTemporary set to True. A net-temporary actor is replicated to any client it becomes relevant to, but after the initial data the client takes full control of the actor, including its eventual destruction. This is mostly used for simple projectiles that fly in a straight line and explode on contact with an object.
Another exception are torn-off actors in Unreal Engine 2 and later. These start out as regular non-persistent actors, but at some point the server sets the actor's bTearOff property to True. This cuts off the connection to clients after a final network update for this actor. When an actor is torn off, it will behave like a locally-spawned, non-replicated actor on the server and all clients it was relevant to at the time of tearing off. Unlike net-temporary actors, a torn-off actor can not become relevant to other clients that didn't know the actor before it was torn off. An example of actors that are torn off at some point are the xPawn class from UT2003 and UT2004. When an xPawn dies, it is torn off and clients independently handle the ragdoll physics, while a dedicated server gets rid of the xPawn actor pretty quickly.
Variable value replication
Values stored in class variables can be replicated from the server to any client if the actor is either persistent or currently relevant to the target client. Variable replication from client to server is possible (but discouraged) in Unreal Engine 1, but not in any later engine generation. Apart from the initial data when an actor is replicated to a client, variable replication does not happen instantly when a variable changes. Only once in a while, as specified by the NetUpdateFrequency property, the server checks for changed variables and replicates their new values.
Before a variable value is replicated, the client assumes it to have the default value as specified in the defaultproperties block. This also goes for actors that initially exist in the map because the mapper added them in the editor. Generally, variable values are only replicated after they were changed for that actor instance at runtime. Values of configurable or localized variables don't count as "changed" just because their configured/localized default value is different from their original defaultproperties value!
The exact conditions for when to replicate a variable's value can be specified in the replication block of the class declaring the variable. It is not possible to change this replication condition for subclasses. The reliable and unreliable keywords that need to be specified in the replication block of Unreal Engine 1 and 2 have no meaning for replicated variables.
If the size of their values allows it, multiple changed variables of a single actor instance are replicated together. Elements of a static array variable are replicated individually and only if they actually changed. The value of a struct variable is always replicated as a unit. If the value is too large to fit into a single packet, it will never get replicated! Similar rules apply to strings, which can only be replicated if their length doesn't exceed the packet size. Dynamic arrays do not support replication at all. Attempting to replicate a dynamic array variable will have no effect, trying to replicate a dynamic array as part of a struct will result in an array empty on the receiving side.
Function call replication
Function calls can be used for a more responsive and structured communication between the server and individual clients. Whether calls to a function are replicated must be specified in the class that originally declared the function. Subclasses can neither change existing replication declarations, nor can they make overridden functions replicated. Also, in order for a function to actually get replicated to the remote side, the actor mus be directly or indirectly owned by the client involved in the replication, i.e. the client's PlayerPawn/PlayerController is either the actor itself or can be reached by traversing the actor's Owner "chain".
Like replicated variables, the replication of a function call can be tied to certain conditions. In Unreal Engine 1 and 2 this condition is specified via the replication block and typically involves comparing the actor's Role to the value ROLE_Authority
, Unreal Engine 3 provides the special function modifiers client and server instead. Additionally a function can be replicated reliably or unreliably, which is specified by the keywords reliable and unreliable in front of the replication condition or as additional modifier in the UE3 function declaration.
Function calls are always replicated asynchronously. As a result, a replicated function call returns immediately. If the function has a return type, the corresponding type's null value is returned locally. Whatever the function returns on the remote side (i.e. where it is executed) will be discarded there. If the replication conditions for a replicated function call aren't met, the function is executed locally as if it was a non-replicated function. You can use this fact to check whether a function was actually replicated or not:
reliable client function bool DoSomething() { return true; }
if (DoSomething()) { // function returned True, so it was executed locally } else { // function returned the null value (i.e. False), so it was replicated }
Note that regular calling restrictions apply for replicated functions. For example if a function is replicated to a client where the actor's local Role is ROLE_SimulatedProxy
, but the function is not declared as simulated, it will not be executed. Conveniently the UE3 modifier client already implies the simulated modifier, so this is more of a problem in earlier engine generations.