I came to Unreal's networking with the confidence of someone who has written plenty of multiplayer code in other engines and the humility of someone who has clearly never written it in this one. My first multiplayer prototype worked perfectly in the editor and fell apart the moment a second player joined. The bug was entirely in my head, not the engine.
The thing nobody can quite tell you until you feel it: in Unreal, the server is authoritative, and you have to mean it. I had a health value, I decremented it when a hit landed, and on my own screen the number went down beautifully. On the other player's screen, nothing. Because I'd changed the value on the client, where it is a local lie, rather than on the server, where it is the truth.
replicated does not mean magic
Marking a property Replicated feels like it should be the whole job:
UPROPERTY(ReplicatedUsing = OnRep_Health)
float Health;
void AMyCharacter::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyCharacter, Health);
}
But replication only flows one way: from the server to clients. The server changes the value, and the engine pushes that down to everyone, calling OnRep_Health on the clients so they can react (update the health bar, play a flinch). A client writing to Health directly is writing to a variable that the next replication update will simply overwrite, if it even survives that long. My decrement was being silently stamped out by the authoritative copy.
the client asks, the server decides
So how does a client cause a change? It asks. You send a Server RPC, a function the client invokes but which executes on the server:
UFUNCTION(Server, Reliable)
void ServerApplyDamage(float Amount);
The naming convention Server, Client, NetMulticast finally clicked once I stopped reading it as decoration and started reading it as "where does this actually run". Server runs on the server having been called from the owning client. The server then does the real work, decrements the authoritative Health, and replication carries the new value back out to everyone, my own client included. The number on my screen now comes back to me as truth, slightly later than it used to, and correct.
That round trip is the whole mental shift. My instinct was to make the change locally and tell everyone. Unreal wants me to request the change, let the server arbitrate it, and receive the result like everybody else. The slight extra latency on my own screen is the price of not letting every client invent its own reality, which is exactly the cheating-shaped hole I'd have left otherwise.
what I'd tell past me
Two things. First, build and test with a dedicated server or at least "Play As Client" from the very first commit, not after the prototype "works", because single-player-in-the-editor hides every one of these mistakes. The bug isn't in your code, it's in the gap between two machines that doesn't exist until there are two machines.
Second, when something doesn't replicate, the question is almost never "is the property marked right". It's "which machine actually changed the value". Nine times out of ten I'd changed it on the wrong one. The server owns the truth. Everything else is just asking nicely and waiting to be told what happened.