I've been poking at Unreal Engine in evenings for a while now, mostly single-player tinkering, and I finally hit the thing everyone warns you about: networking. Specifically, replication. I wanted two clients to see the same door open, which sounds like it should be a five-minute job and instead became an education in everything I didn't understand about how Unreal thinks about authority.
The short version of what I learned: replication in Unreal is not "sync these variables between machines". It's a specific, opinionated model about who is allowed to decide things, and once that model clicked, most of my confusion evaporated. Before it clicked, I was fighting the engine and losing.
the server is the only one who's right
The foundational idea is that the server has authority, and clients are, broadly, displaying a prediction of what the server believes. A variable marked for replication flows from the server to the clients, not the other way around, and not between clients. The first hour of my confusion was entirely down to expecting a client to be able to just change a replicated value and have everyone see it. It can't, and it shouldn't, because that's how you build a game where one player edits a number and suddenly has infinite health.
In C++ you mark a property to replicate like this:
UPROPERTY(ReplicatedUsing = OnRep_DoorOpen)
bool bDoorOpen = false;
// and you must register it, or it silently does nothing
void AMyDoor::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyDoor, bDoorOpen);
}
That GetLifetimeReplicatedProps registration is the first trap. If you forget it, the UPROPERTY looks correct, the code compiles, and absolutely nothing replicates, with no warning. I lost a good twenty minutes to a property I'd marked Replicated that was registered nowhere. The macro is the actual switch; the property specifier is just a declaration of intent.
client wants something, client asks nicely
So if clients can't change replicated state, how does a player open the door? They send a request to the server, and the server, being the authority, decides whether to honour it and changes the state. That's a Server RPC:
UFUNCTION(Server, Reliable)
void Server_RequestOpenDoor();
void AMyDoor::Server_RequestOpenDoor_Implementation()
{
// we are now running on the server, with authority
if (!bDoorOpen)
{
bDoorOpen = true; // this change will replicate out to clients
}
}
The mental model that unlocked it for me: clients request, server decides, decisions replicate back. The client pressing E doesn't open the door. It asks the server to, the server changes the boolean, and the new value flows back to every client, which is what actually opens it on their screen.
the RepNotify gotcha that ate my evening
Here's where I genuinely lost an evening. I'd used ReplicatedUsing, which means when the value changes on a client, a notify function fires. Perfect for triggering the door animation. So I put my animation call in OnRep_DoorOpen:
void AMyDoor::OnRep_DoorOpen()
{
PlayDoorAnimation(bDoorOpen);
}
This worked beautifully on the clients and not at all on the listen server's own player. The door opened for everyone except the host. The reason is obvious in hindsight and infuriating at 10pm: the RepNotify fires when a replicated value arrives. On the server, the value never arrives over the network, the server set it locally, so the notify never fires for the host's own pawn.
The fix is to call the notify yourself on the server after you set the value:
void AMyDoor::Server_RequestOpenDoor_Implementation()
{
if (!bDoorOpen)
{
bDoorOpen = true;
OnRep_DoorOpen(); // server doesn't get the RepNotify, so call it
}
}
This is apparently such a common confusion that it's almost a rite of passage. The server is authoritative and therefore never receives its own changes back, so anything you hang off a RepNotify has to be invoked manually on the authority side. Once I understood that, a whole category of "it works for clients but not the host" bugs became predictable rather than mysterious.
what I'd tell myself a week ago
Replication isn't variable syncing with extra steps, it's an authority model, and you fight it until you adopt its worldview. The server owns the truth. Clients render a copy and politely ask for changes via RPCs. RepNotify fires on arrival, which means it doesn't fire on the machine that originated the change. Almost every bug I hit was me forgetting one of those three facts.
It's also, once it clicks, genuinely elegant. The same authority model that stops a client editing its own health is the model that makes the door consistent for everyone. I'm a long way from anything I'd call a networked game, but I no longer feel like the engine is being deliberately obtuse, which after that first evening is real progress. Next up: figuring out why my replicated movement looks fine on the host and like a slideshow on the client. I suspect I've more rites of passage ahead.