Hey guys! Lead engine programmer John Pollard has put together a new blog post for you all about Network Tips and Tricks.
Basic Property Replication
I wanted to talk about a few tips that are good to know about when dealing with replicated properties in native code.
The full explanation of how to replicate a property is somewhat out of the scope of this article, but I can quickly go over the basics here.
To replicate a property you need to do a few things:
In the header of the actor class where the property is defined, you need to make sure you have the âreplicatedâ keyword as one of the parameters to the UPROPERTY declaration:
class ENGINE_API AActor : public UObject
{
UPROPERTY( replicated )
AActor * Owner;
};
In the implementation of the actor class, you need to implement the GetLifetimeReplicatedProps function:
void AActor::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{
DOREPLIFETIME( AActor, Owner );
}
In the actorâs constructor, make sure you have the bReplicates flag set to true:
AActor::AActor( const class FPostConstructInitializeProperties & PCIP ) : Super( PCIP )
{
bReplicates = true;
}
Thatâs about it. The member variable âOwnerâ will now be synchronized to all connected clients for every copy of this actor type that is currently instantiated (in this case, the base actor class).
Conditional Property Replication
Once a property is registered for replication, you canât unregister it (thatâs where the lifetime part comes from). The reason for this is because we bake in as much information as possible, so we can take advantage of sharing work across many connections for the same set of properties. This saves a lot of computation time.
So how does one get more fine grain control over how this property replicates? Thatâs where conditional properties come in.
By default, each replicated property has a built-in condition, and that is that they donât replicate if they havenât changed.
To give you more control over how a property replicates, there is a special macro that allows you to add a secondary condition.
This macro is called DOREPLIFETIME_CONDITION. An example of its usage can be seen below:
void AActor::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{
DOREPLIFETIME_CONDITION( AActor, ReplicatedMovement, COND_SimulatedOnly );
}
The âCOND_SimulatedOnlyâ flag that was passed into the condition macro will cause an extra check to be performed before even considering this property for replication. In this case, it will only replicate to clients that have a simulating copy of this actor.
One of the big benefits of this is that it saves bandwidth; since weâve determined that client that has the autonomous proxy version of this actor doesnât need to know about this property (this client is setting this property directly for prediction purposes for example). Another benefit is that for the client not receiving this property, the server wonât step on this clientâs local copy.
Here is a quick glance at the list of conditions currently supported:
⢠COND_InitialOnly - This property will only attempt to send on the initial bunch
⢠COND_OwnerOnly - This property will only send to the actorâs owner
⢠COND_SkipOwner - This property send to every connection EXCEPT the owner
⢠COND_SimulatedOnly - This property will only send to simulated actors
⢠COND_AutonomousOnly - This property will only send to autonomous actors
⢠COND_SimulatedOrPhysics - This property will send to simulated OR bRepPhysics actors
⢠COND_InitialOrOwner - This property will send on the initial packet, or to the actors owner
⢠COND_Custom - This property has no particular condition, but wants the ability to toggle on/off via SetCustomIsActiveOverride
So far weâve talked about conditions that are based off of state that is already known. This makes it easy for the engine to make the necessary optimizations while still giving you enough control over property replication.
But what if this isnât enough control? There is one more thing to talk about on this subject. There is a macro called DOREPLIFETIME_ACTIVE_OVERRIDE, which gives you full control over when a property does and does not replicate, using any custom condition you want. The one caveat is that this is per actor, NOT per connection. So in other words, itâs not safe to use a state that can change per connection in your custom condition. An example can be seen below.
void AActor::PreReplication( IRepChangedPropertyTracker & ChangedPropertyTracker )
{
DOREPLIFETIME_ACTIVE_OVERRIDE( AActor, ReplicatedMovement, bReplicateMovement );
}
The property ReplicatedMovement will now only replicate if bReplicateMovement is true.
Why not use this macro all the time? There are two main reasons to avoid this method:
⢠If custom condition value changes a lot, this can slow things down.
⢠You cannot use condition that can change per connection (donât check RemoteRole here).
Property replication conditions give a nice balance of control vs. performance. They give the engine the opportunity to optimize the time it takes to check and send the properties for many connections, while still giving the programmer fine grain control over how and when properties replicate.
Questions about Johnâs post? Please ask away and let us know what you think below!