Zurück zum Blog

Unreal Engine RPC Optimization: So verhinderst du Network Flooding bei jedem Tick

Veröffentlicht am 4. Mai 2026
Unreal Engine RPC Optimization: So verhinderst du Network Flooding bei jedem Tick

Kurz und knapp

Dieser technische Leitfaden zeigt, wie man durch effektive Unreal Engine RPC Optimization die Netzwerküberlastung in Multiplayer-Spielen reduziert. Durch die Entkopplung der Network-Sende-Rate von der Framerate mittels des Accumulator-Patterns wird verhindert, dass Server durch redundante Daten geflutet werden. Zusätzlich werden Best Practices wie Struct-Batching, Vektor-Quantisierung und Interpolation erläutert, um trotz niedrigerer Update-Frequenzen ein flüssiges Gameplay zu gewährleisten.

Jeder Multiplayer-Spieleentwickler stößt irgendwann auf denselben Network-Bottleneck: Ein Client, der mit 144 Frames pro Sekunde läuft, sendet seinen Custom Movement-State bei jedem einzelnen Tick an den Server. Innerhalb von Sekunden ist die Network-Queue des Servers komplett mit redundanten Remote Procedure Calls (RPCs) überflutet, was zu extremem Lag, Packet Loss und unvermeidbaren Verbindungsabbrüchen führt. Dein Client führt im Grunde eine Distributed Denial of Service (DDoS)-Attacke auf deine eigene Server-Infrastruktur aus.

Dieses Szenario stellt eine der häufigsten Fallstricke in der Multiplayer-Architektur dar. Wenn Entwickler Custom Player-Inputs, komplexe Vehicle-Physics oder schnelle Feuermechaniken übertragen müssen, erscheint ein RPC innerhalb der Tick()-Funktion als logische Wahl für eine reibungslose Responsiveness. Der Networking-Layer der Unreal Engine verwirft jedoch zwischenzeitliche RPCs nicht automatisch. Wenn dein Spiel in jedem Tick einen RPC pusht, werden alle in die Warteschlange gestellt und übertragen.

Für Movement- und Positions-Updates interessieren dich die 143 Zwischen-Frames fast nie; du benötigst nur den absolut neuesten State, um ihn an die anderen Clients zu replizieren. In diesem umfassenden Guide tauchen wir tief in die unreal engine rpc optimization ein und zeigen dir exakt, wie du diese Tick-basierten Network-Calls drosselst, ein smartes State-Accumulation-System implementierst und deinen Multiplayer-Bandbreiten-Overhead drastisch reduzierst.

Die Gefahr von Tick-basierten Network-Events

Bevor wir eine Lösung implementieren, ist es entscheidend, die Anatomie des Problems zu verstehen. Wenn du einen RPC in Unreal Engine deklarierst – sei es Server, Client oder NetMulticast – anweist du den Network-Driver der Engine, die Funktionsparameter zu serialisieren und sie in die ausgehende Packet-Queue zu schieben.

Das Problem mit dem Queueing

Unreal Engine bündelt ausgehende RPCs in Pakete basierend auf der NetUpdateFrequency der Verbindung und den Bandbreitenlimits. Wenn ein Client einen Server RPC in jedem Tick bei einer hohen Framerate aufruft, versucht die Engine, jeden einzelnen dieser Aufrufe zu verarbeiten.

Wenn der RPC als Reliable markiert ist, ist die Situation katastrophal. Reliable RPCs garantieren die Zustellung und die Ausführungsreihenfolge. Der Network-Channel füllt sich schnell, und wenn der Buffer überläuft, wird die Verbindung von der Engine zwangsweise geschlossen, was zu einem Disconnect des Spielers führt.

Ist der RPC als Unreliable markiert, verwirft die Engine Pakete, wenn die Warteschlange voll ist. Dies verhindert zwar einen harten Disconnect, führt aber zu massivem Rubber-Banding. Der Server empfängt vielleicht Frame 1 und 2, verwirft die Frames 3-100 und verarbeitet dann Frame 101. Das Ergebnis ist eine unregelmäßige, ruckartige Bewegung, die das Gameplay-Erlebnis ruiniert. Dies ist eine häufige Ursache, wenn Teams dabei sind, den Unreal Engine RPC Replikationsfehler zu beheben, der ihre States korrumpiert.

Die Bandbreiten-Kalkulation

Schauen wir uns konkrete Zahlen an. Stell dir vor, du sendest einen einfachen Vector (12 Bytes) und einen Rotator (12 Bytes) via Server RPC. Mit dem RPC-Header-Overhead schätzen wir etwa 32 Bytes pro Aufruf.

  • Bei 30 FPS: 30 * 32 Bytes = 960 Bytes/Sekunde (etwa 1 KB/s pro Client).
  • Bei 144 FPS: 144 * 32 Bytes = 4.608 Bytes/Sekunde (etwa 4,6 KB/s pro Client).
  • Bei 240 FPS: 240 * 32 Bytes = 7.680 Bytes/Sekunde.

Multipliziere das mit 64 Spielern in einem Battle Royale, und dein Server verarbeitet plötzlich fast ein halbes Megabyte reinen RPC-Overhead pro Sekunde – nur für das grundlegende Movement-Tracking. Das ist nicht skalierbar.

Schritt 1: Die Tick-Abhängigkeit mit einem Accumulator-Pattern durchbrechen

Die effektivste Strategie für unreal engine rpc optimization besteht darin, die Network-Sende-Rate von der Rendering-Framerate des Clients zu entkoppeln. Anstatt den RPC in Tick() zu pushen, solltest du in jedem Tick eine lokale Variable aktualisieren und dann einen Timer verwenden, um diese Daten in einem festen, vorhersehbaren Intervall (z. B. 10 oder 20 Mal pro Sekunde) an den Server zu übertragen.

Wir nennen dies das Accumulator-Pattern. Der Client akkumuliert kontinuierlich den neuesten State, überträgt ihn aber nur, wenn sich das Network-Gate öffnet.

Die Ziel-Frequenz identifizieren

Du benötigst keine 144 Updates pro Sekunde für ein flüssiges Multiplayer-Erlebnis. Die meisten modernen Competitive Shooter lassen ihre Server mit 30Hz oder 60Hz ticken. Daher ist das Senden von Client-Updates 15 bis 30 Mal pro Sekunde in der Regel mehr als ausreichend, vorausgesetzt, du verwendest ordnungsgemäßes Client-Side Prediction und Server-Side Interpolation.

Durch die Reduzierung der Sende-Rate von ungedrosselten 144Hz auf gedeckelte 20Hz reduzierst du deinen Network-Traffic für diese spezifische Aktion sofort um über 85 %.

Schritt 2: Implementierung des Rate-Limiters in C++

Schauen wir uns an, wie man das effektiv in C++ umsetzt. Wir erstellen ein System, bei dem der Client in jedem Tick seine gewünschte Ziel-Location und Rotation trackt, aber den Server_UpdateTransform RPC nur basierend auf einer vordefinierten Network-Send-Rate sendet.

Die Header-Datei (.h)

Zuerst definieren wir unsere Variablen und Funktionen in unserer eigenen APawn- oder ACharacter-Klasse. Wir benötigen ein Timer-Handle, eine Update-Rate und Variablen für unsere noch nicht gesendeten Daten.

UCLASS()
class MYGAME_API AMyCustomPawn : public APawn
{
    GENERATED_BODY()

public:
    AMyCustomPawn();

    virtual void Tick(float DeltaTime) override;
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

protected:
    virtual void BeginPlay() override;

    // Der RPC, um Daten an den Server zu senden. Als Unreliable markiert für schnelle, kontinuierliche Updates.
    UFUNCTION(Server, Unreliable, WithValidation)
    void Server_SendTransformUpdate(FVector NewLocation, FRotator NewRotation);

private:
    // Timer-Handle für unseren Network-Flush
    FTimerHandle NetworkUpdateTimerHandle;

    // Wie oft pro Sekunde wir Updates an den Server senden wollen
    UPROPERTY(EditDefaultsOnly, Category = "Network")
    float NetworkSendRate;

    // Flag, um zu tracken, ob wir neue Daten haben, die noch nicht gesendet wurden
    bool bHasPendingNetworkUpdate;

    // Die akkumulierten Daten, die auf den Versand warten
    FVector PendingLocation;
    FRotator PendingRotation;

    // Die vom Timer aufgerufene Funktion zum Senden der Daten
    void FlushNetworkUpdate();
};

Die Source-Datei (.cpp)

Nun implementieren wir die Logik. Wir richten den Timer in BeginPlay ein, aktualisieren unsere Pending-Variablen in Tick und lassen den Timer die eigentliche Network-Übertragung übernehmen.

#include "MyCustomPawn.h"
#include "TimerManager.h"

AMyCustomPawn::AMyCustomPawn()
{
    PrimaryActorTick.bCanEverTick = true;
    
    // Standardmäßig 20 Updates pro Sekunde senden
    NetworkSendRate = 20.0f; 
    bHasPendingNetworkUpdate = false;
}

void AMyCustomPawn::BeginPlay()
{
    Super::BeginPlay();

    // Nur der lokal gesteuerte Client sollte den Network-Flush-Timer ausführen
    if (IsLocallyControlled())
    {
        float UpdateInterval = 1.0f / NetworkSendRate; // z. B. 1.0 / 20.0 = 0.05 Sekunden

        GetWorld()->GetTimerManager().SetTimer(
            NetworkUpdateTimerHandle,
            this,
            &AMyCustomPawn::FlushNetworkUpdate,
            UpdateInterval,
            true // Kontinuierlich wiederholen
        );
    }
}

void AMyCustomPawn::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    // Hier deine eigene Client-Side Movement-Logik ausführen
    // z. B. FVector NewLoc = ...; FRotator NewRot = ...;
    // SetActorLocationAndRotation(NewLoc, NewRot);

    if (IsLocallyControlled())
    {
        // Anstatt hier den RPC aufzurufen, speichern wir nur den neuesten State
        PendingLocation = GetActorLocation();
        PendingRotation = GetActorRotation();
        
        // Markieren, dass frische Daten zum Senden bereitstehen
        bHasPendingNetworkUpdate = true;
    }
}

void AMyCustomPawn::FlushNetworkUpdate()
{
    // Wenn es keine neuen Daten gibt (z. B. Spieler steht still), keine Bandbreite verschwenden
    if (!bHasPendingNetworkUpdate)
    {
        return;
    }

    // Sende den neuesten akkumulierten State an den Server
    Server_SendTransformUpdate(PendingLocation, PendingRotation);

    // Reset des Flags, bis der nächste Tick den State wieder ändert
    bHasPendingNetworkUpdate = false;
}

bool AMyCustomPawn::Server_SendTransformUpdate_Validate(FVector NewLocation, FRotator NewRotation)
{
    // Hier Anti-Cheat-Validierung hinzufügen. Ist die Location plausibel?
    return true; 
}

void AMyCustomPawn::Server_SendTransformUpdate_Implementation(FVector NewLocation, FRotator NewRotation)
{
    // Der Server empfängt die ratenbegrenzten Daten und wendet sie an
    SetActorLocationAndRotation(NewLocation, NewRotation);
    
    // Hinweis: Der Server würde dies dann an andere Clients replizieren,
    // normalerweise über Standard Replicated Properties, NICHT via Multicast.
}

Warum diese Architektur funktioniert

Dieses Setup löst das Network-Flood-Problem elegant. Egal, ob der Client mit 30 FPS oder 300 FPS läuft, der Server erhält garantiert exakt NetworkSendRate Updates pro Sekunde (vorausgesetzt, es gibt keinen Packet Loss).

Darüber hinaus haben wir einen Early-Out-Check (!bHasPendingNetworkUpdate) implementiert. Wenn der Spieler seine Tastatur verlässt, um sich einen Kaffee zu holen, hört der Client komplett auf, RPCs zu senden, und gibt kritische Bandbreite für aktive Spieler frei. Dies ist ein massiver Gewinn für eine konsistente Server-Performance.

Schritt 3: Handling der State-Interpolation auf anderen Clients

Wenn du die Network-Sende-Rate reduzierst, wird die Bewegung auf dem Server – und folglich auf den anderen verbundenen Clients – ruckartig. Wenn du Updates mit 10Hz sendest, wird der Charakter auf einem 60-FPS-Monitor sichtlich 10 Mal pro Sekunde teleportieren.

Um dies zu beheben, darfst du den Charakter nicht einfach auf die neue Location setzen. Du musst Interpolation verwenden. Wenn der Server die NewLocation an die Simulated Proxies (die anderen Clients, die den Spieler beobachten) repliziert, müssen diese Clients über die Zeit hinweg sanft von ihrer aktuellen Position zur replizierten Zielposition mit FMath::VInterpTo interpolieren.

Dies stellt sicher, dass die visuelle Darstellung selbst bei einem sehr aggressiven Rate-Limit (wie 5 oder 10 Updates pro Sekunde) butterweich bleibt. Wenn du Probleme mit fehlerhaftem Snapping während der Interpolation hast, solltest du prüfen, wie man Player Location Desync in UEFN und Unreal Engine Multiplayer behebt.

Schritt 4: Struct-Batching für komplexe RPCs

Wenn dein Spiel das Senden mehrerer verschiedener Variablen erfordert, sende nicht mehrere separate RPCs. Jeder RPC hat einen grundlegenden Header-Overhead (normalerweise mindestens 1-2 Bytes, praktisch aber mehr, wenn man die Payload-Serialisierung berücksichtigt).

Wenn du Server_SendHealth(), Server_SendArmor() und Server_SendPosition() im selben Network-Flush aufrufst, zahlst du die Header-Kosten dreimal.

Erstelle stattdessen ein dediziertes Struct für deine Network-Payloads.

USTRUCT()
struct FPlayerNetworkState
{
    GENERATED_BODY()

    UPROPERTY()
    FVector Location;

    UPROPERTY()
    FRotator Rotation;

    UPROPERTY()
    uint8 CurrentWeaponIndex;

    UPROPERTY()
    bool bIsCrouching;
};

Übergib dieses einzelne Struct durch deinen Timer-basierten RPC. Das Reflection-System der Unreal Engine packt diese Variablen effizient in eine einzige Packet-Payload und minimiert so den Byte-Footprint deiner Verbindung.

5 Best Practices für Unreal Engine RPC Optimization

Um sicherzustellen, dass dein Spiel von lokalen Tests auf Tausende gleichzeitige Spieler skaliert, solltest du diese grundlegenden Regeln für die Network-Architektur übernehmen:

  1. Sende niemals RPCs im Tick ohne Gate: Betrachte dies als harte Regel. Wenn ein RPC in Tick() steht, muss er durch einen Zeit-Check (z. B. if (TimeSinceLastRPC > 0.1f)) geschützt oder über einen Loop-Timer verwaltet werden.
  2. Priorisiere Unreliable gegenüber Reliable: Für Daten, die sich kontinuierlich aktualisieren (Movement, Blickrichtung, kontinuierliche Beam-Waffen), verwende immer Unreliable RPCs. Wenn ein Paket verloren geht, wird das nächste Paket, das einen Bruchteil einer Sekunde später eintrifft, es ohnehin überschreiben. Reliable RPCs sollten strikt für absolute State-Änderungen reserviert sein (z. B. Waffe abgefeuert, Item aufgehoben, Spieler gestorben).
  3. Nutze Quantisierung für Floats und Vektoren: Beim Senden von FVector-Daten benötigst du selten die volle Floating-Point-Präzision. Unreal Engine ermöglicht es dir, Vektoren in RPCs zu quantisieren (z. B. FVector_NetQuantize100), was die Werte auf zwei Dezimalstellen rundet und die für den Versand erforderliche Bandbreite drastisch senkt.
  4. Bevorzuge Standard-Replikation für Downstream-Daten: Während Clients RPCs verwenden müssen, um Daten an den Server zu senden, sollte der Server selten Multicast RPCs verwenden, um kontinuierliche Daten zurückzusenden. Der Server sollte eine UPROPERTY(Replicated) Variable aktualisieren, damit der integrierte Replikations-Manager von Unreal die Bandbreitenoptimierung, Priorisierung und Relevanzprüfung automatisch übernimmt.
  5. Profile frühzeitig und oft: Nutze den Befehl net.DumpRelevantActors und das Network Profiler Tool (NetworkProfiler.exe im Engine-Binaries-Ordner), um genau zu visualisieren, wie viele Bytes deine RPCs pro Frame verbrauchen. Rate niemals bei deinen Optimierungsgewinnen; miss sie empirisch.

Infrastruktur und Backend-Skalierung

Die Feinheiten des Netcodes der Unreal Engine zu meistern, ist ein gewaltiges Unterfangen. Du verbringst Stunden damit, Timer-Handles zu optimieren, Vektoren zu quantisieren und Desyncs zu mildern, nur um deine Dedicated Server reibungslos am Laufen zu halten, ohne ihre Bandbreitenlimits zu sprengen.

Sobald dein In-Game-Code endlich optimiert ist, musst du diese Server noch global bereitstellen und skalieren. Dies selbst aufzubauen erfordert das Einrichten von Fleet-Managern, Load Balancern, Database Sharding und SSL-Zertifikatsverwaltung – locker 4-6 Wochen intensive Infrastrukturarbeit, die dich vom eigentlichen Game Design abhält.

Mit horizOn sind diese Backend-Services bereits speziell für Spieleentwickler vorkonfiguriert. Du erhältst skalierbares Dedicated Server Hosting, Echtzeit-Datenbanksynchronisation und robuste Analytics direkt out-of-the-box, sodass du dein Spiel veröffentlichen kannst, anstatt dich um die Infrastruktur zu kümmern.

Fazit

Der Schlüssel zur unreal engine rpc optimization liegt in der Erkenntnis, dass Network-Bandbreite eine endliche, hochvolatile Ressource ist. Du kannst den Networking-Layer nicht wie einen Standard-Frame-Buffer behandeln. Durch die Abkehr von der Tick-gesteuerten Ausführung und die Nutzung des Accumulator-Patterns erhältst du die volle Kontrolle über den Daten-Output deines Spiels. Du reduzierst die Serverlast, milderst Packet Loss und schaffst ein deutlich flüssigeres Erlebnis für Spieler mit schwankenden Internetverbindungen.

Denk daran, dass die Optimierung deines Spiels ein fortlaufender Prozess ist. Verlass dich nicht auf Standard-Engine-Verhaltensweisen, um dich vor Network-Flooding zu schützen. Übernimm explizit die Kontrolle über deinen Datenfluss. Implementiere diese Rate-Limits in deinem aktuellen Prototyp, überwache die Vorher-Nachher-Metriken mit dem Network Profiler und beobachte, wie deine Server-Performance in die Höhe schießt.

Bereit, dein neu optimiertes Multiplayer-Backend zu skalieren? Teste horizOn kostenlos oder wirf einen Blick in die API-Docs, um zu sehen, wie einfach professionelle Game-Infrastruktur sein kann.


Quelle: Network: How not to send all PRC every tick?