Skillshub ue-networking-replication

UE Networking & Replication

install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/quodsoler/unreal-engine-skills/ue-networking-replication" ~/.claude/skills/comeonoliver-skillshub-ue-networking-replication && rm -rf "$T"
manifest: skills/quodsoler/unreal-engine-skills/ue-networking-replication/SKILL.md
source content

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:

  1. Server topology? (dedicated, listen, P2P)
  2. Maximum player count per session?
  3. Which actors or components need to replicate data?
  4. 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:

MachineGetLocalRole()GetRemoteRole()
ServerROLE_AuthorityROLE_AutonomousProxy or SimulatedProxy
Owning ClientROLE_AutonomousProxyROLE_Authority
Other ClientsROLE_SimulatedProxyROLE_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
GameplayEffect
s 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

MistakeFix
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 locallyOnly 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_OwnerOnly
    properties it sees.
  • RPC routing: Server RPCs are only accepted from the actor's owning connection.
  • Detecting disconnects: bind to
    AGameModeBase::OnLogout
    or check
    NetConnection->GetConnectionState() == USOCK_Closed
    (or
    IsClosingOrClosed()
    ) rather than polling
    NetConnection
    directly.

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 roles
  • ue-gameplay-abilities
    — GAS prediction keys, attribute set replication, GameplayEffect replication
  • ue-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 patterns
  • references/rpc-decision-guide.md
    — Server/Client/NetMulticast decision flowchart, Reliable vs Unreliable rules, ownership routing, and common RPC mistakes