Ramblings of an aging IT geek
← Ramblings of an aging IT geek
gamedev

the day i actually understood unreal replication

A first real go at Unreal Engine's actor replication, where the surprise is that the server is authoritative and the client is mostly being lied to politely.

A game editor viewport with two client windows side by side

I have been poking at Unreal in the evenings, and this week I finally had to make something work over the network rather than in a single window. Replication is the bit everyone warns you about, and now I understand why. It is not hard exactly. It is just built on a model that you have to actually adopt, rather than nod along to and then fight.

The model, stated plainly: the server is the truth, and every client is being shown a polite approximation of it. A replicated variable on the server gets pushed out to clients. A change made on a client gets ignored, or worse, silently stamped back over the next time the server updates. My first hour was spent confused about why the value I set on the client kept reverting. It reverted because I had no business setting it on the client at all.

A snippet of replicated property and RPC declarations

The mechanics, once it clicks, are tidy enough. You mark a property as replicated and tell Unreal which one in GetLifetimeReplicatedProps:

UPROPERTY(ReplicatedUsing = OnRep_Health)
int32 Health;

void AMyPawn::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const
{
    Super::GetLifetimeReplicatedProps(Out);
    DOREPLIFETIME(AMyPawn, Health);
}

The ReplicatedUsing part is the nice trick: OnRep_Health runs on the client whenever the value arrives, so you update the health bar there rather than polling. The server changes the number, the engine ships it down, the client reacts. You do not write any of the wire code yourself, which after years of hand-rolling protocols feels almost suspicious.

Then there are RPCs, which are how a client asks the server to actually do something. The two specifiers that mattered for me were Server and Reliable. A client presses fire, it calls a Server RPC, the server validates and applies it, and the result replicates back out to everyone including the client that fired. The client does not get to decide it shot someone. It gets to ask, and the server decides.

UFUNCTION(Server, Reliable, WithValidation)
void ServerFire();

The WithValidation half is the part I would have skipped if the engine had let me, and it is the part that matters most. It is the server's chance to say "no" to a client that is lying. That is the whole shape of multiplayer security in three words: never trust the client. Unreal bakes it into the type system, which I appreciate more the longer I sit with it.

What caught me out, beyond setting values on the wrong machine, was authority. HasAuthority() is the guard you end up sprinkling everywhere: do the real logic on the side that owns the truth, do the cosmetic stuff everywhere. Spawn the actor on the server, let it replicate. Play the muzzle flash on the client, because nobody needs the server's permission to draw a sprite.

I am one evening in and I would not call myself fluent. But the mental flip, from "I change the thing" to "I ask the authority to change the thing and watch it come back", is the whole game. Everything else is detail. The detail will, I am sure, ruin a few more evenings yet.