Skip to main content

📡 Lesson 10.2: Property Replication & RepNotify

Replication is Unreal's magic: mark a property replicated, change it on the server, and every client's copy updates automatically — no networking code from you. Add RepNotify and each client can react to the change. This is step 3 of the networking pattern, and it rides the reflection system you already know.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Enable actor replication with bReplicates
  • Replicate a property with UPROPERTY(Replicated) and GetLifetimeReplicatedProps
  • React to replicated changes with ReplicatedUsing (RepNotify) and an OnRep_ function
  • Explain replication direction and when values arrive
  • Replicate the UHealthComponent and drive the HUD everywhere

Estimated Time: 60 minutes

Engine Version: Unreal Engine 5.8

In This Lesson

Enabling Replication

Two switches turn an actor into a networked one, set in the constructor:

AEnemy::AEnemy()
{
    bReplicates = true;              // this actor exists on clients & syncs
    // For actors that move under physics/movement and must sync position:
    SetReplicateMovement(true);      // replicate the transform
}

📖 bReplicates = "this actor is networked"

With bReplicates = true, the server creates the actor on all clients and keeps replicated properties in sync. SetReplicateMovement additionally syncs the actor's transform (great for simple moving actors; Characters handle their own movement replication via the movement component from Lesson 4.2). Without bReplicates, the actor is server-local — clients never see it.

Replicating a Property

Marking a property replicated takes two pieces: the Replicated specifier on the UPROPERTY, and registering it in GetLifetimeReplicatedProps.

// Enemy.h
#include "Net/UnrealNetwork.h"   // for replication macros (in the .cpp)

UPROPERTY(Replicated)
float Health = 100.0f;

// Override this to declare which properties replicate:
virtual void GetLifetimeReplicatedProps(
    TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// Enemy.cpp
#include "Net/UnrealNetwork.h"

void AEnemy::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    // Register Health for replication. This macro is required.
    DOREPLIFETIME(AEnemy, Health);
}

⚠️ Both pieces are mandatory

A UPROPERTY(Replicated) that you forget to register with DOREPLIFETIME won't replicate — and there's no error, just a value that never updates on clients (a classic silent bug). Include Net/UnrealNetwork.h in the .cpp. Now, whenever the server changes Health, every client's copy updates automatically. You wrote no send/receive code — the reflection system (Lesson 2.2) does it, the fourth payoff realized.

✅ Conditional replication

Variants like DOREPLIFETIME_CONDITION(AEnemy, Ammo, COND_OwnerOnly) replicate only to certain clients (e.g. only the owner sees their own ammo) — a bandwidth optimization. Start with plain DOREPLIFETIME; reach for conditions when you need to limit who receives a value.

Direction & Timing

Property replication flows in exactly one direction: server → clients. Clients never replicate properties up to the server (that's what RPCs are for, Lesson 10.3).

graph LR Srv["SERVER changes Health = 80"] -->|"replicates down"| C1["Client 1: Health becomes 80"] Srv -->|"replicates down"| C2["Client 2: Health becomes 80"] C1 -.->|"cannot replicate up"| Srv
Figure 1: Properties replicate server → clients only. A client changing a replicated property locally is meaningless — it'll be overwritten by the next server update.

⚠️ Only change replicated properties on the server

Setting a replicated property on a client does nothing useful — the value isn't sent up, and the next server replication overwrites it. Always change replicated state inside if (HasAuthority()) (Lesson 10.1). Replication is not instant either — values arrive over the network with latency and are batched, so don't assume a client sees a change the same frame the server made it.

RepNotify (OnRep)

Plain replication updates the value silently. Often you want each client to react when it changes — update a health bar, play a hit effect. ReplicatedUsing = OnRep_Fn calls a function on clients whenever the value arrives.

// Enemy.h — replicate WITH a notify callback.
UPROPERTY(ReplicatedUsing = OnRep_Health)
float Health = 100.0f;

UFUNCTION()
void OnRep_Health();   // runs on clients when Health replicates in
// Enemy.cpp — register it the same way (RepNotify still needs DOREPLIFETIME):
void AEnemy::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const
{
    Super::GetLifetimeReplicatedProps(Out);
    DOREPLIFETIME(AEnemy, Health);
}

// Called on CLIENTS when the new Health value arrives from the server.
void AEnemy::OnRep_Health()
{
    UE_LOG(LogTemp, Log, TEXT("Health replicated to %.0f"), Health);
    // Update health bar, play a hit flash, etc. — reacting to the new value.
    if (Health <= 0.0f) { PlayDeathEffects(); }
}

📖 OnRep runs on clients, not the server

The OnRep_ function fires on clients when the replicated value updates — not on the server that changed it. So if the server also needs to react, call your reaction logic directly there too. A common idiom: the server changes Health and calls OnRep_Health() manually to run the same reaction locally, so both server and clients respond consistently. RepNotify is how replicated state becomes reactive events on every client — much like the delegates from Lesson 5.4, but driven by the network.

Replicating Health

Let's network the UHealthComponent that's threaded through the whole course. Components replicate too — set SetIsReplicatedByDefault(true) and register the property.

// HealthComponent.cpp
UHealthComponent::UHealthComponent()
{
    PrimaryComponentTick.bCanEverTick = false;
    SetIsReplicatedByDefault(true);   // this component replicates
}

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

bool UHealthComponent::ApplyDamage(float Amount)
{
    if (!GetOwner()->HasAuthority()) { return false; }   // server-only (Lesson 10.1)

    CurrentHealth = FMath::Clamp(CurrentHealth - Amount, 0.0f, MaxHealth);
    OnHealthChanged.Broadcast(CurrentHealth, MaxHealth); // local (server) reaction
    return CurrentHealth <= 0.0f;
}
// HealthComponent.h — RepNotify so clients fire the SAME event the HUD listens to.
UPROPERTY(ReplicatedUsing = OnRep_Health, VisibleAnywhere, BlueprintReadOnly)
float CurrentHealth = 0.0f;

UFUNCTION()
void OnRep_Health()
{
    // On clients, broadcast the same event the HUD (Lesson 9.2) subscribes to.
    OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
}

✅ Watch the whole course converge

The server applies damage (authority, 10.1), CurrentHealth replicates to clients (10.2), each client's OnRep_Health broadcasts OnHealthChanged (5.4), and the HUD widget updates its bar (9.2). One value change flows correctly to every player's screen — and you wrote no manual networking. This is the reward for building on the same patterns all along: they compose into multiplayer almost for free.

Hands-on Exercise & Quiz

🏋️ Exercise: Replicate a door's open state

Objective: Replicate state and react on clients.

  1. On ADoor, set bReplicates = true.
  2. Add UPROPERTY(ReplicatedUsing = OnRep_IsOpen) bool bIsOpen; and register it in GetLifetimeReplicatedProps.
  3. The server sets bIsOpen (guard with HasAuthority()); in OnRep_IsOpen, play the open/close animation.
  4. Why put the animation in OnRep_IsOpen rather than in the function that sets bIsOpen?
✅ Answer to Q4

Because the setter runs only on the server, but the animation must play on every client. OnRep_IsOpen fires on each client when the value replicates in, so all players see the door animate. (Call OnRep_IsOpen() manually on the server too, so the host/listen-server player also sees it.)

🎯 Quick Quiz

Question 1: Marking UPROPERTY(Replicated) is enough on its own.

Question 2: Property replication flows in which direction?

Question 3: An OnRep_ function runs on:

Summary

🎉 Key Takeaways

  • Enable networking with bReplicates = true (and SetReplicateMovement / SetIsReplicatedByDefault for movement / components).
  • Replicate a property with both UPROPERTY(Replicated) and DOREPLIFETIME in GetLifetimeReplicatedProps — miss either and it silently won't sync.
  • Properties replicate server → clients only, with latency — change them on the server (HasAuthority()), never on clients.
  • ReplicatedUsing = OnRep_Fn calls OnRep_ on clients when the value arrives — how state becomes a reactive event.
  • Replicating CurrentHealth + broadcasting OnHealthChanged in OnRep drives every client's HUD with no manual net code.

📚 Additional Resources

🚀 What's Next?

Properties get server state down to clients. Now the other direction — a client asking the server to do something: RPCs (Server, Client, Multicast), step 1 of the networking pattern.

🎉 Lesson complete!

State flows to every client. Now let's send commands.