Terug naar Blog

Multiplayer Desyncs: Het oplossen van de Unreal Engine RPC Replication Issue die je states breekt

Gepubliceerd op 3 maart 2026
Multiplayer Desyncs: Het oplossen van de Unreal Engine RPC Replication Issue die je states breekt

Elke multiplayer indie-ontwikkelaar kent het moment waarop hun netcode hen in de steek laat. Je vuurt een Run on Server RPC af om een wapen te equippen. De serverlogs bevestigen dat het wapen is equipped. De collision cylinder van de server laat zien dat je in een aiming stance staat. Maar op het scherm van je client? Je personage staat daar gewoon in een standaard idle pose, volledig ongevoelig voor de state change.

Wanneer je weapon equip logica, aim states, inventory interacties en crafting systemen plotseling stoppen met updaten op de client, slaat de paniek toe. Je ontdekt misschien dat het overschakelen van de RPC naar Multicast de visuele bugs op magische wijze oplost.

Laat het niet op Multicast staan.

Het gebruik van Multicast om persistent state bugs op te lossen is een pleister die uiteindelijk de network performance van je game zal vernietigen en de ervaring voor laat-toetredende spelers (late-joining) zal verpesten. In deze deep dive gaan we de hoofdoorzaak van de gevreesde unreal engine rpc replication issue ontleden, uitleggen waarom je server states je clients negeren, en een kogelvrije, server-authoritative state sync ontwerpen met C++.

De Multicast-val: Waarom het "werkt" (en waarom het je game zal ruïneren)

Wanneer ontwikkelaars deze bug tegenkomen, is het denkproces meestal als volgt:

  1. Client roept Server_EquipWeapon() aan.
  2. Server equipt het wapen.
  3. Client visual wordt niet bijgewerkt.
  4. Verander Server_EquipWeapon() om Multicast_EquipWeapon() aan te roepen.
  5. Client visual wordt bijgewerkt! Bug opgelost, toch?

Fout. Om te begrijpen waarom, moet je het fundamentele verschil begrijpen tussen RPCs (Remote Procedure Calls) en Property Replication.

Een RPC is een vluchtige netwerkgebeurtenis. Het is een schreeuw in de leegte. Als een speler zich binnen de network cull distance bevindt wanneer de Multicast afgaat, horen ze de schreeuw en spelen ze de equip-animatie af.

Maar wat gebeurt er als een speler 10 seconden later de server betreedt? Wat gebeurt er als een speler 5.000 Unreal Units verderop is, binnen het relevancy range loopt en je personage ziet? Omdat de Multicast al in het verleden is afgegaan, ontvangt de nieuwe client de gebeurtenis nooit. Ze zullen je personage zien met een onzichtbaar wapen, rondglijdend in een idle pose terwijl er kogels uit hun borst schieten.

Multicast is voor vluchtige, niet-gameplay-kritieke gebeurtenissen: een explosie-visual, een geluidseffect of een cosmetische particle pop.

Voor alles wat in de loop van de tijd blijft bestaan — zoals welk wapen je vasthoudt, of je mikt, of wat er in je inventory zit — moet je Property Replication gebruiken.

Hoofdoorzaak: Waarom ging het plotseling kapot?

Als je Run on Server RPCs voorheen werkten en plotseling kapot gingen in meerdere systemen (wapens, aiming, crafting), ben je waarschijnlijk het slachtoffer van een van deze drie architecturale verschuivingen in je project:

1. De Listen Server vs. Dedicated Server illusie

Als je voorheen testte in Play-In-Editor (PIE) met een Listen Server, is de host-speler zowel de client als de server. Een "Run on Server" RPC uitgevoerd door de host werkt onmiddellijk de lokale visuele state bij omdat de host de server is. Wanneer je eindelijk overstapt naar Dedicated Server testen (of test als Client 2), valt de illusie uiteen. De server werkt zijn geïsoleerde geheugen bij en de client blijft achter.

2. Gebroken ActorComponent Ownership

Als je onlangs je inventory- of wapenlogica hebt gerefactord naar UActorComponent classes, heb je mogelijk de replication-keten verbroken. RPCs kunnen alleen vanaf clients worden aangeroepen als de client de Actor bezit (owns). Als je component dynamisch wordt gespawned en niet expliciet een eigenaar krijgt toegewezen via SetOwner(PlayerController), zal de server de RPC simpelweg negeren. We behandelen deze architecturale nachtmerrie in onze gids over Multiplayer Inventory Nightmares Fixing Swapped Actorcomponent Owners In Unreal Engine.

3. Omzeilen van lokale state

Voorheen stelde je client-side input event misschien de lokale bIsAiming boolean in voordat de Server RPC werd aangeroepen. Als je je code hebt gerefactord om puur "Server Authoritative" te zijn (wachten tot de server de state dicteert), maar bent vergeten die state terug naar de client te repliceren, zal je client permanent wachten op een update die nooit komt.

Stap-voor-stap Tutorial: Kogelvrije State Replication ontwerpen

Om deze unreal engine rpc replication issue op te lossen, moeten we overstappen van een RPC-gestuurde architectuur naar een State-Driven Architecture met RepNotifies.

Hier leest u hoe u op de juiste manier een server-authoritative weapon equip en aiming systeem implementeert dat de client naadloos bijwerkt.

Stap 1: Definieer Replicated Properties met RepNotifies

In plaats van te vertrouwen op een RPC om animaties te triggeren, declareren we persistente variabelen. Wanneer de server deze variabelen wijzigt, synchroniseert de Net Driver van Unreal deze automatisch met de clients. Door een ReplicatedUsing functie (een RepNotify) toe te voegen, kunnen we de animaties triggeren precies op het moment dat de client hoort over de state change.

In je Character Header (.h) bestand:

UCLASS()
class MYGAME_API AMyCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    AMyCharacter();

    // De persistente state. Gerepliceerd naar alle clients.
    UPROPERTY(ReplicatedUsing = OnRep_EquippedWeapon)
    AWeapon* EquippedWeapon;

    UPROPERTY(ReplicatedUsing = OnRep_IsAiming)
    bool bIsAiming;

    // De RepNotify functies. Deze draaien op de client wanneer de server de variabele bijwerkt.
    UFUNCTION()
    void OnRep_EquippedWeapon();

    UFUNCTION()
    void OnRep_IsAiming();

    // De Server RPCs om state changes aan te vragen
    UFUNCTION(Server, Reliable, WithValidation)
    void Server_EquipWeapon(AWeapon* NewWeapon);

    UFUNCTION(Server, Reliable, WithValidation)
    void Server_SetAiming(bool bWantsToAim);

    // Core replication setup
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};

Stap 2: Implementeer de Server RPCs en Replication Rules

In je .cpp bestand moet je deze variabelen registreren in GetLifetimeReplicatedProps. Definieer vervolgens de Server RPCs om alleen de authoritative state bij te werken.

#include "MyCharacter.h"
#include "Net/UnrealNetwork.h"

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

    // Repliceer deze variabelen naar alle verbonden clients
    DOREPLIFETIME(AMyCharacter, EquippedWeapon);
    DOREPLIFETIME(AMyCharacter, bIsAiming);
}

// --- AIMING LOGIC ---

bool AMyCharacter::Server_SetAiming_Validate(bool bWantsToAim)
{
    // Anti-cheat: Zorg dat de speler mag mikken (bijv. niet dood)
    return !bIsDead;
}

void AMyCharacter::Server_SetAiming_Implementation(bool bWantsToAim)
{
    bIsAiming = bWantsToAim;
    
    // CRITIEK: RepNotifies draaien in C++ NIET automatisch op de server.
    // Als de server een Listen Server is, moeten we dit handmatig aanroepen.
    if (GetNetMode() != NM_DedicatedServer)
    {
        OnRep_IsAiming();
    }
}

Stap 3: Implementeer de RepNotifies voor visuele updates

Hier hoort je animatielogica, UI-updates en mesh-attachments thuis. Omdat dit afhankelijk is van de gerepliceerde state, zullen laat-toetredende spelers deze logica automatisch triggeren zodra je personage relevant voor hen wordt.

void AMyCharacter::OnRep_IsAiming()
{
    if (UAnimInstance* AnimInst = GetMesh()->GetAnimInstance())
    {
        if (UMyAnimInstance* MyAnim = Cast<UMyAnimInstance>(AnimInst))
        { 
            MyAnim->bIsAiming = bIsAiming;
        }
    }

    GetCharacterMovement()->MaxWalkSpeed = bIsAiming ? 300.f : 600.f;
}

void AMyCharacter::OnRep_EquippedWeapon()
{
    if (EquippedWeapon)
    {
        EquippedWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, FName("WeaponSocket"));
        PlayAnimMontage(EquipMontage);
    }
}

De professionele touch: Client-Side Prediction

Zonder prediction heb je last van Input Latency. Bij een ping van 100ms voelt de speler een vertraging van 200ms voordat het personage mikt. In een moderne shooter voelt dit verschrikkelijk.

Oplossing: Client-Side Prediction. De client faket de state change onmiddellijk visueel, terwijl hij tegelijkertijd de server om toestemming vraagt.

void AMyCharacter::StartAiming()
{
    // 1. Lokaal onmiddellijk voorspellen (Nul latentie voor de speler)
    bIsAiming = true;
    OnRep_IsAiming(); 

    // 2. Vertel de server om het officieel te maken
    if (!HasAuthority())
    {
        Server_SetAiming(true);
    }
}

Als de server het er niet mee eens is, zal de gerepliceerde state de client corrigeren. Dit is de basis van robuuste multiplayer-architectuur, zoals besproken in The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It.

Schalen voorbij de match: Persisterende Player State

Replicatie zorgt voor consistentie tijdens de match. Maar wat gebeurt er met de inventory na de match? Om voortgang op te slaan, moet de state Unreal Engine verlaten naar een beveiligde database.

Dit zelf bouwen (load balancers, APIs, SSL) duurt weken. Met horizOn zijn deze backend-services vooraf geconfigureerd. Je kunt de EquippedWeapon ID en inventory direct vanuit je server naar de cloud opslaan via native SDKs.

5 Best Practices voor Unreal Engine Replication

  1. Gebruik nooit Multicast voor Persistent State: Gebruik Replicated Properties. Reserveer Multicast voor visuele effecten.
  2. Roep RepNotifies handmatig aan op de server: In C++ triggeren OnRep_ functies niet vanzelf op de server.
  3. Valideer je Server RPCs: Vertrouw de client nooit. Gebruik _Validate voor anti-cheat checks.
  4. Let op je NetUpdateFrequency: Als de visuele state lagt, controleer dan de updatefrequentie van de Actor.
  5. Controleer Component Ownership: RPCs vanuit componenten vereisen een geldige eigenaar (PlayerController).

Stop met vechten tegen de Net Driver

Het replicatiesysteem van Unreal is krachtig maar onvergefelijk. Als client states niet updaten, weersta dan de drang naar Multicast-spam. Volg het pad van autoriteit: de client verzoekt, de server dicteert en de property repliceert.

Klaar voor het volgende niveau? Maak je geen zorgen meer over infrastructuur. Probeer horizOn gratis voor persistente progressie en naadloze matchmaking.