I have been poking at Unreal Engine 4 in the evenings, and this week I tried to make something actually multiplayer. Replication is where a lot of first-timers come unstuck, myself very much included, and the reason is that you cannot bolt it on at the end. It changes how you think about every line of gameplay code.
The mental model that finally made it click: the server owns the truth, and clients are politely lying to themselves until the server corrects them. Nothing a client decides is real until the server agrees. Get that the right way round and most of the confusion evaporates. Get it backwards and you spend an evening wondering why your beautifully working single-player logic does nothing on a client.
In practice you mark a property with UPROPERTY(Replicated) and list it in GetLifetimeReplicatedProps, and the engine quietly keeps it in sync from server to clients. That part is genuinely lovely. The part that bites is actions. A client cannot just change game state, it has to ask the server, and that is what Server RPCs are for:
UFUNCTION(Server, Reliable, WithValidation)
void ServerFire();
The WithValidation is not optional boilerplate. It is the engine reminding you, every single time, that a client is untrusted and you must check that what it is asking for is actually allowed before you act on it. Skip the validation and you have built a cheating-as-a-service API. The naming convention catches people out too: you implement ServerFire_Implementation and ServerFire_Validate, never the bare function, and the engine wires up the magic in between.
The thing that cost me the most time was authority. HasAuthority() is the question you end up asking constantly, because the same code runs on both server and client, and you have to be deliberate about which side is allowed to do what. Spawn an actor on a client without authority and it simply will not replicate, it just sits there locally, a ghost only that one player can see. I lost a good hour to exactly that, convinced replication was broken, when in fact I was spawning the thing in the wrong place.
For all the swearing, it is a well-designed system once the model lands. The trick is to stop thinking "I will change this variable" and start thinking "I will ask the server to change this variable, and trust it to tell everyone." Once that became the default shape of every gameplay function, the whole thing got a lot calmer. Slow going, but the good kind of slow, where you can feel the understanding settling into place.