Skip to content

RepNotify variables and local presentation

RepNotify is the normal beginner bridge from replicated gameplay state to local UI, audio, materials, and visual effects. The variable is the truth. The OnRep path is each machine reacting to that truth arriving or changing.

The one-minute version

  • Replicated sends a variable's value from the authoritative server copy to client copies.
  • RepNotify does that too, and also gives you an OnRep_<VariableName> function/event to run local follow-up work when that copy observes the replicated value change.
  • Put durable gameplay facts in the variable: health, ammo, door open state, team score, ready state, equipped item ID.
  • Put local presentation in the OnRep/apply path: update widgets, play sounds, start/stop Niagara, change material parameters, play a door timeline.
  • RepNotify is not client-to-server communication. Clients still need Server RPCs or server-owned gameplay paths to request authoritative changes.
  • RepNotify order across different variables is not deterministic. If two values must be interpreted together, replicate one struct or one state enum instead of racing two OnRep functions.

What RepNotify actually does

Replication sends changed state from the server's authoritative actor to client copies. RepNotify adds a named hook that runs when the local copy handles that replicated value.

pseudocode of the relationship - not engine source

Server:
    Health = 60
    Health is marked RepNotify

Network replication:
    Health is considered for each relevant client
    changed value is sent when the actor replicates

Client receives update:
    local Health becomes 60
    OnRep_Health runs locally
    local widget/material/audio reads Health and updates

The important part is the direction: the server changes authoritative state, then clients react when state reaches their local copies. The OnRep function is not a permission check and not a request path.

How to create it in Blueprint

For a replicated actor variable:

Actor class defaults:
-> Replicates = true

Variable:
-> select variable
-> Details panel -> Replication -> RepNotify
-> Blueprint creates/uses OnRep_<VariableName>

Server gameplay:
-> set the variable on the server

OnRep_<VariableName>:
-> call Apply<Thing>Presentation

Use a separate helper for the visible work:

SetHealthOnServer(NewHealth):
    Health = clamp(NewHealth, 0, MaxHealth)
    ApplyHealthPresentation()   // keeps server/listen-server view current too

OnRep_Health:
    ApplyHealthPresentation()

ApplyHealthPresentation:
    update local health bar if one exists
    set local dynamic material parameter
    play local low-health cue if this is the owning player

That helper avoids two common traps: duplicating visual code in two places, and forgetting that the server copy may also need to update a listen-server host's view or world presentation.

Blueprint RepNotify examples often show the generated OnRep path running on both authority and remote machines when the value changes. C++ ReplicatedUsing is usually treated as a receive hook on clients, with server-side parallel work called manually after the server changes the variable. The helper pattern works for both, which is why it is the best beginner default.

Replicated vs RepNotify

Setting What clients get What extra code runs Use when
Replicated The current value Nothing automatic beyond the value update Other code already polls/reads the value, or no immediate reaction is needed.
RepNotify The current value OnRep_<VariableName> on copies that observe the change A local response should happen when the value arrives.

Both are server-to-client state replication. RepNotify is not "stronger replication"; it is replication plus a callback.

What belongs in the variable

Replicate state that still matters after the packet is gone:

Good RepNotify state Local presentation derived from it
Health = 45 Health bar percent, blood vignette, low-health sound gate.
bDoorOpen = true Door mesh timeline, collision mode, opening sound.
AmmoInClip = 3 Ammo text, reload prompt, dry-fire click eligibility.
TeamId = Blue Nameplate color, outline material, scoreboard grouping.
EquippedItemId = Rifle_01 First-person mesh swap, HUD icon, equip sound.
MatchPhase = InProgress Timer widget, announcer cue, ready button visibility.

Do not replicate the widget, sound component, dynamic material instance, or Niagara component itself as the main truth. Those are local presentation objects. Replicate the compact state that lets each machine create or update its own presentation.

What OnRep should do

Good OnRep jobs:

  • copy the replicated value into a widget through a stored local UI reference;
  • update a progress bar, text label, nameplate, or inventory slot view;
  • play a one-shot local sound if the transition matters;
  • start, stop, or retarget a Niagara effect;
  • set a dynamic material parameter;
  • call an idempotent ApplyState helper on an actor or component;
  • rebuild local presentation after the actor becomes relevant or a late client receives the current state.

Poor OnRep jobs:

  • deciding whether damage is valid;
  • subtracting ammo or currency;
  • spawning authoritative gameplay actors;
  • calling Server RPCs just because the value arrived;
  • assuming another replicated variable's OnRep already ran;
  • doing expensive world searches every time a value changes;
  • mutating widgets as if the widget were the source of multiplayer truth.

OnRep is the "state arrived; update this local copy" hook. Authority decisions still belong on the server.

Ordering and missed intermediate values

Replicated properties are not a frame-by-frame event log. They are state updates. If the server sets:

Health = 90
Health = 70
Health = 40

before the actor's next replication update reaches a client, that client may only need to learn the current value, 40. That is good for health bars and bad for systems that wanted three separate hit reactions. If every hit reaction matters, replicate durable hit state or use a carefully chosen RPC for the transient cue.

Different RepNotify variables also do not have a deterministic order relative to each other. This can break graphs like:

OnRep_CurrentWeaponId expects CurrentAmmo to already be updated
OnRep_CurrentAmmo expects CurrentWeaponId to already be updated

Better shapes:

RepNotify struct:
    FEquippedWeaponState
        WeaponId
        AmmoInClip
        ReserveAmmo

or

RepNotify enum/state:
    InventoryPresentationState

If values must be interpreted together, replicate them together.

Patterns that work

Health bar

Server damage path:
    Apply Damage / authoritative hit
    -> HealthComponent clamps Health
    -> Health = NewHealth  (RepNotify)
    -> ApplyHealthPresentation on server copy if needed

Clients:
    OnRep_Health
    -> ApplyHealthPresentation
    -> local widget updates from Health / MaxHealth

The widget is not replicated. Each client owns its own widget and reads the replicated health value on its local actor copy.

Door state

Client interact request:
    ServerTryInteract(Door)

Server door:
    bOpen = true  (RepNotify)
    ApplyDoorState

Clients:
    OnRep_Open
    -> ApplyDoorState
    -> play timeline to open pose
    -> set local sound/VFX/materials

Late or newly relevant clients receive bOpen = true and can apply the open pose instead of needing the original "open door" multicast.

Match phase

GameState:
    MatchPhase = Waiting / Countdown / InProgress / Finished  (RepNotify)
    RemainingTime replicated separately or in one struct if phase/time must align

Clients:
    OnRep_MatchPhase
    -> local scoreboard and HUD choose visible controls

The GameState owns public match state. Each client's UI presents it locally.

When it fails, and what failure looks like

  1. The actor does not replicate. A RepNotify variable on a non-replicated actor is just local state for that actor copy.
  2. The server never set the value. A client changed its local copy, but the authoritative copy did not change, so other machines never learn it.
  3. The value did not change meaningfully. Setting the same value again is not a reliable event source. Use an explicit sequence/count/state if repeats matter.
  4. The actor is not relevant yet. A client cannot receive property updates for an actor it has not received or is not currently relevant for.
  5. You raced two variables. Separate OnRep calls can arrive in an order your graph did not expect.
  6. You updated UI on the wrong machine. The RepNotify ran on a copy without that local player's widget, or on a dedicated server with no UI.
  7. You put authority inside OnRep. Clients ran code that should have been server-only, then the server corrected or ignored it.

Debug by printing actor name, net mode, authority/local control, old value if you store one, and new value inside the OnRep/apply helper. If the print appears on the wrong machine, solve role/ownership first.

Lookalikes - which one do I want?

Tool Use when Not for
Replicated variable Clients need the current value and no immediate callback is needed. Local presentation that must update exactly when the value arrives.
RepNotify variable Clients need the value and should react locally on arrival/change. Client requests or server validation.
Run on Server RPC Owning client asks the server to perform gameplay work. Durable state that clients should be able to read later.
Run on Owning Client RPC Server sends private local feedback to one client. Public match or player state.
Multicast RPC Server sends a transient event to relevant clients. State that late clients/relevant-later clients must reconstruct.
Event Dispatcher Local UI/component listeners need a local event. Crossing the network.
Widget binding Tiny local display expression with no heavy work. Polling replicated actors every frame because OnRep was skipped.

The quick path is to Multicast every visible change. The concrete cost is that late joiners and newly relevant clients miss the original event. A RepNotify state value gives them something durable to apply.

Going deeper