UE Networking & Replication
You are an expert in Unreal Engine's networking and replication systems.
Context Check
Read .agents/ue-project-context.md for this project's multiplayer configuration.
Look for: server topology (dedicated, listen, P2P), player count, replicated classes,
and any custom net drivers.
If the context file is absent, ask:
- Server topology? (dedicated, listen, P2P)
- Maximum player count per session?
- Which actors or components need to replicate data?
- Are you using Gameplay Ability System (GAS)?
Net Roles and Authority
UE uses a server-authoritative model: the server is the source of truth for game state. Clients predict locally and reconcile with server corrections.
Every actor on every machine has a local role and a remote role (ENetRole).
ROLE_Authority — owns and can modify this actor (server for replicated actors)
ROLE_AutonomousProxy — client copy of the locally controlled pawn
ROLE_SimulatedProxy — client copy of another player's actor; engine interpolates state
ROLE_None — not replicated
From Actor.h:
ENetRole GetLocalRole() const { return Role; } // role on current machine
ENetRole GetRemoteRole() const; // role the other end sees
bool HasAuthority() const { return (GetLocalRole() == ROLE_Authority); }
Net modes: NM_Standalone, NM_DedicatedServer, NM_ListenServer, NM_Client.
Role matrix for a replicated Pawn:
| Machine | GetLocalRole() | GetRemoteRole() |
|---|---|---|
| Server | ROLE_Authority | ROLE_AutonomousProxy or SimulatedProxy |
| Owning Client | ROLE_AutonomousProxy | ROLE_Authority |
| Other Clients | ROLE_SimulatedProxy | ROLE_Authority |
Listen-server caveat: the host is both ROLE_Authority and locally controlled.
Use IsLocallyControlled() to distinguish logic that should skip the host player.
UNetDriver
UNetDriver is the core transport class responsible for managing all network connections and
packet delivery for a world. It owns the list of UNetConnection objects and drives the
replication tick. Access it via UWorld::GetNetDriver().
UNetDriver* Driver = GetWorld()->GetNetDriver();
// Driver->ClientConnections — all connected clients (server-side)
// Driver->ServerConnection — connection to server (client-side)
For most gameplay code you never interact with UNetDriver directly; it is relevant when
writing custom net drivers, profiling connection state, or debugging packet loss.
Property Replication
Actor Setup
AMyActor::AMyActor()
{
bReplicates = true; // AActor::SetReplicates() also available at runtime
SetReplicateMovement(true); // replicates FRepMovement (location/rotation/velocity)
SetNetUpdateFrequency(10.f); // checks per second
SetMinNetUpdateFrequency(2.f); // floor when nothing changes
NetPriority = 1.0f; // higher = preferred when bandwidth is saturated
}
From Actor.h: SetReplicates, SetReplicateMovement, SetNetUpdateFrequency,
SetMinNetUpdateFrequency, and SetNetCullDistanceSquared are all ENGINE_API.
FRepMovement: when bReplicateMovement = true, the engine serializes position,
velocity, and rotation into an FRepMovement struct (declared in Actor.h) and
sends it to simulated proxies. UCharacterMovementComponent bypasses this with its
own prediction-based replication; it writes compressed moves via FSavedMove_Character
and reconciles them server-side, so SetReplicateMovement(false) is the correct
default for characters using CMC.
Declaring Properties
UPROPERTY(Replicated)
int32 Health;
UPROPERTY(ReplicatedUsing = OnRep_State)
EMyState State;
UFUNCTION()
void OnRep_State(EMyState PreviousState); // old value passed as optional parameter
GetLifetimeReplicatedProps
// MyActor.cpp
#include "Net/UnrealNetwork.h"
void AMyActor::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps); // NEVER omit this
DOREPLIFETIME(AMyActor, Health);
DOREPLIFETIME_CONDITION(AMyActor, State, COND_OwnerOnly);
DOREPLIFETIME_CONDITION(AMyActor, SimData, COND_SimulatedOnly);
DOREPLIFETIME_CONDITION(AMyActor, InitData, COND_InitialOnly);
DOREPLIFETIME_CONDITION(AMyActor, PublicData, COND_SkipOwner);
}
Conditions: COND_None (all), COND_OwnerOnly, COND_SkipOwner,
COND_SimulatedOnly, COND_AutonomousOnly, COND_InitialOnly, COND_Custom.
Use COND_OwnerOnly for private player data (inventory, currency).
Use COND_InitialOnly for immutable spawn data (team, character class).
Initial replication burst: When a client first joins or an actor first becomes relevant, ALL replicated properties send at once regardless of conditions (COND_InitialOnly fires exactly once here). This burst can saturate the actor channel — keep initial state compact and use COND_InitialOnly for spawn-time-only data to reduce ongoing bandwidth.
FRepLayout (Internal)
FRepLayout is an internal engine struct that describes which properties of a class are
replicated and how. It handles delta compression (only changed properties are sent) and
evaluates DOREPLIFETIME_CONDITION filters per-connection. Developers rarely interact
with FRepLayout directly, but knowing it exists helps when debugging why a property is
or is not replicating: the layout is built once per class and cached, so dynamic changes
to conditions require DOREPLIFETIME_WITH_PARAMS_FAST or bIsPushBased patterns rather
than modifying the struct at runtime.
Large Arrays: FFastArraySerializer
Plain TArray sends the full array on any change. Use FFastArraySerializer for
delta-only replication. See references/replication-patterns.md Pattern 3 for
a complete inventory implementation.
Custom Struct Serialization
Implement NetSerialize on a USTRUCT and add TStructOpsTypeTraits with
WithNetSerializer = true to control binary layout manually. Use
FVector_NetQuantize10 (0.1 cm precision) instead of raw FVector to halve
position bandwidth.
Remote Procedure Calls (RPCs)
See references/rpc-decision-guide.md for the full decision flowchart.
// Client calls → server executes. WithValidation is required for state changes.
UFUNCTION(Server, Reliable, WithValidation)
void ServerFireWeapon(FVector_NetQuantize Origin, FVector_NetQuantizeNormal Dir);
// Server calls → owning client executes. No WithValidation needed.
UFUNCTION(Client, Reliable)
void ClientShowKillConfirm();
// Server calls → server + all clients execute.
UFUNCTION(NetMulticast, Unreliable)
void MulticastPlayHitEffect(FVector ImpactPoint);
Implementations always end in _Implementation. Server RPCs with validation also
need _Validate (return false to kick the client).
Reliable — guaranteed delivery; use for state-changing actions (purchase, spawn, possession). Unreliable — fire-and-forget; use for high-frequency cosmetics.
Real examples from PlayerController.h:
UFUNCTION(unreliable, server, WithValidation) void ServerSetSpectatorLocation(...);
UFUNCTION(reliable, server, WithValidation) void ServerAcknowledgePossession(APawn* P);
UFUNCTION(Reliable, Client) void ClientReceiveLocalizedMessage(...);
UFUNCTION(Client, Unreliable) void ClientAckTimeDilation(float, int32);
RPC ownership: Server RPCs must be called on an actor whose ownership chain
leads to the calling client's APlayerController. Client RPCs must be called on
an actor owned by a player with a NetConnection. Calls on unowned actors are
silently dropped.
RPC Parameter Rules: Only net-addressable types are valid — basic types (int32,
float, FString, FVector), replicated UObject* pointers (arrive as nullptr
if not replicated), and USTRUCT(BlueprintType) structs. Non-replicated UObject*
pointers arrive as nullptr; pass an ID and look the object up on the remote side.
Large payloads (>1 KB) should use replicated properties instead of RPC parameters.
TArray and TMap parameters are supported but watch for bandwidth impact.
Ownership and Relevancy
// Server sets owner to control connection routing for RPCs and COND_OwnerOnly
Weapon->SetOwner(PlayerController);
// Relevancy flags (from Actor.h)
bAlwaysRelevant = true; // replicate to every connection
bOnlyRelevantToOwner = true; // replicate only to the owning connection
// Override for custom logic (e.g., team-based relevancy)
virtual bool IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget,
const FVector& SrcLocation) const override;
Default relevancy culls actors beyond NetCullDistanceSquared from the client
viewpoint. NetPriority determines which actors win when bandwidth is saturated.
Dormancy: actors that rarely change can pause replication entirely.
SetNetDormancy(DORM_DormantAll); // stop replication
FlushNetDormancy(); // send one update then return to dormant
Re-relevancy: When an actor moves outside NetCullDistanceSquared it stops replicating. When it returns to range, the engine treats it as a fresh relevancy event — sending another initial burst and firing all OnRep_ callbacks with current server state. Design OnRep_ functions to be idempotent (tolerate being called multiple times with the same value). Dormant actors (DORM_DormantAll) behave similarly when woken — they flush all dirty properties at once.
Subobject Replication
Modern API (UE 5.1+)
From Actor.h:
ENGINE_API void AddReplicatedSubObject(UObject* SubObject,
ELifetimeCondition NetCondition = COND_None);
ENGINE_API void RemoveReplicatedSubObject(UObject* SubObject);
ENGINE_API void AddActorComponentReplicatedSubObject(UActorComponent* OwnerComponent,
UObject* SubObject,
ELifetimeCondition NetCondition = COND_None);
Enable with bReplicateUsingRegisteredSubObjectList = true (now the default).
Call AddReplicatedSubObject on the server during BeginPlay; call
RemoveReplicatedSubObject in EndPlay.
Legacy API
Override ReplicateSubobjects when bReplicateUsingRegisteredSubObjectList = false:
bool AMyActor::ReplicateSubobjects(UActorChannel* Channel,
FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
bool bWrote = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
bWrote |= Channel->ReplicateSubobject(MySubObject, *Bunch, *RepFlags);
return bWrote;
}
Client-Side Prediction
Pattern (for non-movement systems without GAS):
// 1. Client predicts immediately
void AMyCharacter::LocalPredictAbility(int32 AbilityId)
{
if (IsLocallyControlled())
{
ApplyAbilityEffectLocal(AbilityId); // immediate visual/audio
ServerActivateAbility(AbilityId); // request server confirmation
}
}
// 2. Server validates, broadcasts, or corrects
void AMyCharacter::ServerActivateAbility_Implementation(int32 AbilityId)
{
if (CanActivateAbility(AbilityId))
{
ApplyAbilityEffectAuthority(AbilityId);
MulticastAbilityActivated(AbilityId);
}
else
{
ClientCorrectionAbilityFailed(AbilityId); // roll back client prediction
}
}
When the server rejects a prediction, smooth the visual correction to avoid jarring
snaps: interpolate the actor to the corrected position over 2-3 frames rather than
teleporting. UCharacterMovementComponent handles this automatically via
NetworkSmoothingMode (Linear, Exponential, or Disabled).
CharacterMovementComponent has full built-in prediction. Extend
FSavedMove_Character and override PhysCustom to support custom movement modes.
Prediction eligibility: Only UCharacterMovementComponent and GAS (UAbilitySystemComponent with FPredictionKey) have engine-managed prediction and rollback. Raw UPROPERTY(Replicated) properties have no built-in prediction — if you write them locally before the server confirms, the server's replicated value overwrites the client value with no rollback. For non-CMC/GAS state, use local-only variables for visual prediction and apply the authoritative value in OnRep_.
GAS and FPredictionKey: GAS uses FPredictionKey to link client-predicted actions
with server confirmations. The key is generated on the client (via
FPredictionKey::CreateNewPredictionKey), embedded in the Server RPC, and matched on the
server when applying GameplayEffects or activating abilities. If the server determines
the prediction was invalid, every GameplayEffect and attribute change tagged with that
key is automatically rolled back on the client. You do not construct FPredictionKey
manually in most cases; UAbilitySystemComponent::TryActivateAbility manages the
lifecycle. Relevant when writing custom UGameplayAbility subclasses that need
scoped prediction windows (FScopedPredictionWindow). See ue-gameplay-abilities
for the full GAS networking details.
Actor Replication Setup Checklist
[ ] bReplicates = true in constructor
[ ] SetNetUpdateFrequency / SetMinNetUpdateFrequency set appropriately
[ ] All UPROPERTY(Replicated) / UPROPERTY(ReplicatedUsing=...) declared
[ ] GetLifetimeReplicatedProps overridden; Super called first
[ ] DOREPLIFETIME / DOREPLIFETIME_CONDITION for each property
[ ] OnRep_ functions declared UFUNCTION() and implemented
[ ] Server RPCs have WithValidation; _Validate implemented
[ ] NetMulticast / Client RPCs guarded with HasAuthority() on call site
[ ] Subobjects registered via AddReplicatedSubObject or ReplicateSubobjects
[ ] SetOwner() called on server after spawning owned actors
[ ] DORM_DormantAll set on actors that rarely update
Replication Graph
For large player counts (50+ connections), the default net driver relevancy checks become expensive. UReplicationGraph replaces per-connection per-actor relevancy with spatial and policy-based nodes.
// Build.cs: "ReplicationGraph"
// Project: set ReplicationDriverClassName in DefaultEngine.ini
// [/Script/OnlineSubsystemUtils.IpNetDriver]
// ReplicationDriverClassName=/Script/MyGame.MyReplicationGraph
UCLASS()
class UMyReplicationGraph : public UReplicationGraph
{
GENERATED_BODY()
public:
virtual void InitGlobalActorClassSettings() override;
virtual void InitGlobalGraphNodes() override;
virtual void InitConnectionGraphNodes(UNetReplicationGraphConnection* RepGraphConnection) override;
};
Key node types: UReplicationGraphNode_GridSpatialization2D (spatial), UReplicationGraphNode_AlwaysRelevant (GameState, managers), UReplicationGraphNode_AlwaysRelevant_ForConnection (PlayerController, PlayerState, HUD), UReplicationGraphNode_ActorList (custom lists).
Common Mistakes
| Mistake | Fix |
|---|---|
Modifying state without HasAuthority() guard | Add if (!HasAuthority()) return; |
Server RPC without WithValidation — client sends arbitrary values | Add WithValidation; validate in _Validate |
Calling Client/NetMulticast from the client — silently dropped | Only call from server; guard with HasAuthority() |
Plain TArray on replicated component — full array sent on every change | Use FFastArraySerializer |
Omitting Super::GetLifetimeReplicatedProps — parent properties silently dropped | Always call Super first |
Passing non-replicated UObject* in RPC params — arrives as nullptr | Pass an ID; look up object on remote side |
Using NetMulticast for persistent state — late-joining clients miss it | Use UPROPERTY(Replicated) for state |
| Replicating cosmetic-only properties — increases bandwidth for data clients can compute locally | Only replicate gameplay-critical state; derive visuals client-side |
APlayerController Replication Scope
APlayerController exists only on the server and the owning client — not on other
clients. Put data meant for all clients on APawn or APlayerState.
From PlayerController.h:
// nullptr on local players (server-side); points to the network connection otherwise
UPROPERTY(DuplicateTransient)
TObjectPtr<UNetConnection> NetConnection;
UNetConnection represents a single client's network connection on the server. Each
connected client has exactly one UNetConnection owned by UNetDriver. Retrieve it
with APlayerController::GetNetConnection(). It is used for:
- Ownership checks: an actor's owning connection determines which client RPCs it can
receive and which
COND_OwnerOnlyproperties it sees. - RPC routing: Server RPCs are only accepted from the actor's owning connection.
- Detecting disconnects: bind to
AGameModeBase::OnLogoutor checkNetConnection->GetConnectionState() == USOCK_Closed(orIsClosingOrClosed()) rather than pollingNetConnectiondirectly.
Use APlayerController for connection-scoped Client RPCs (HUD updates, voice,
level streaming notifications). Use APawn or APlayerState for world-visible state.
Related Skills
ue-gameplay-framework— AGameMode (server-only), AGameState / APlayerState (replicated to all), APawn net rolesue-gameplay-abilities— GAS prediction keys, attribute set replication, GameplayEffect replicationue-cpp-foundations— UPROPERTY/UFUNCTION macros, module includes
Reference Files
references/replication-patterns.md— health, inventory (FFastArraySerializer), ability state, team data, subobject, dormancy, and custom relevancy patternsreferences/rpc-decision-guide.md— Server/Client/NetMulticast decision flowchart, Reliable vs Unreliable rules, ownership routing, and common RPC mistakes