I've been poking at Unreal in the evenings, and this week I hit the wall that every newcomer hits eventually: multiplayer. Specifically, the moment you realise that getting a variable to show up correctly on another player's screen is not a one-liner, and that everything you thought you knew about "just set the value" has to be unlearned.
The thing I got wrong, repeatedly, is thinking of replication as syncing. It isn't. The server owns the truth. Clients receive copies of properties the server decides to share, and only the server's copy is real. The moment that landed, half my confusion evaporated. My problem was that I kept changing values on the client and wondering why nobody else saw them. Of course nobody saw them. I was scribbling on a photocopy.
Marking a property to replicate is the easy half:
UPROPERTY(ReplicatedUsing = OnRep_Health)
float Health = 100.f;
void AMyCharacter::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyCharacter, Health);
}
ReplicatedUsing gives you a callback that fires on clients when the value arrives, which is where you update the health bar rather than ticking and polling. That GetLifetimeReplicatedProps boilerplate is mandatory and I forgot it the first three times, which produces no error and no replication, just a variable that stubbornly stays at its default on everyone else's machine. Cheers, Unreal.
The bit that actually took the evening was changing the value. A client pressing a button to, say, take damage cannot just set Health. It has to ask the server, and you ask the server with a Server RPC:
UFUNCTION(Server, Reliable)
void ServerApplyDamage(float Amount);
void AMyCharacter::ServerApplyDamage_Implementation(float Amount)
{
Health = FMath::Max(0.f, Health - Amount); // runs on the server
}
So the flow is: client calls ServerApplyDamage, the function actually executes on the server, the server lowers Health, replication carries the new value back out, and every client's OnRep_Health fires to update the UI. The client never touches the real number. It only ever requests and then waits to be told.
Once you internalise that, the authority macros stop being magic words you copy from a tutorial. HasAuthority() is "am I the server, am I allowed to change real things here". Reliable versus Unreliable is whether the RPC absolutely must arrive (a death) or can be dropped if the network's busy (a cosmetic effect that'll be corrected next update anyway). These are design decisions, not incantations, and treating them as decisions is what got my prototype actually working across two clients.
It is more ceremony than single-player. There's no pretending otherwise. But the ceremony is the whole point: it forces you to be explicit about who is allowed to change what, and in a networked game that question is the entire game. I spent an evening losing to it and came out the other side genuinely respecting the model. Annoying, yes. Wrong, no.