Retour au Blog

Multiplayer Desyncs : Résoudre l'Unreal Engine RPC Replication Issue qui casse vos états

Publié le 3 mars 2026
Multiplayer Desyncs : Résoudre l'Unreal Engine RPC Replication Issue qui casse vos états

Tout développeur indie de jeux multiplayer connaît le moment exact où son netcode le trahit. Vous lancez un RPC Run on Server pour équiper une arme. Les logs du serveur confirment que l'arme est équipée. Le cylindre de collision du serveur indique que vous êtes en position de visée. Mais sur l'écran de votre client ? Votre personnage reste planté là dans sa pose idle par défaut, totalement insensible au changement d'état.

Lorsque votre logique d'équipement d'arme, vos états de visée, vos interactions d'inventaire et vos systèmes de craft cessent soudainement de se mettre à jour sur le client, la panique s'installe. Vous découvrirez peut-être que passer le RPC en Multicast corrige magiquement les bugs visuels.

Ne le laissez pas en Multicast.

Utiliser le Multicast pour corriger des bugs d'état persistant est un pansement qui finira par détruire les performances réseau de votre jeu et gâcher l'expérience des joueurs qui rejoignent en cours de partie (late-joining). Dans cette analyse approfondie, nous allons décortiquer la cause profonde du redoutable unreal engine rpc replication issue, expliquer pourquoi vos états serveur ignorent vos clients, et architecturer une synchronisation d'état server-authoritative robuste en C++.

Le piège du Multicast : Pourquoi ça "marche" (et pourquoi ça va ruiner votre jeu)

Quand les développeurs rencontrent ce bug, le raisonnement est souvent le suivant :

  1. Le client appelle Server_EquipWeapon().
  2. Le serveur équipe l'arme.
  3. Le visuel du client ne se met pas à jour.
  4. On change Server_EquipWeapon() pour appeler Multicast_EquipWeapon().
  5. Le visuel du client se met à jour ! Bug corrigé, non ?

Faux. Pour comprendre pourquoi, vous devez saisir la différence fondamentale entre les RPCs (Remote Procedure Calls) et la Property Replication.

Un RPC est un événement réseau transitoire. C'est un cri dans le vide. Si un joueur est à portée de network cull distance lorsque le Multicast est déclenché, il entend le cri et joue l'animation d'équipement.

Mais que se passe-t-il si un joueur rejoint le serveur 10 secondes plus tard ? Que se passe-t-il si un joueur se trouve à 5 000 Unreal Units, entre dans la zone de pertinence (relevancy range) et voit votre personnage ? Comme le Multicast a déjà eu lieu, le nouveau client ne reçoit jamais l'événement. Il verra votre personnage tenir une arme invisible, glissant en pose idle tout en tirant des balles depuis sa poitrine.

Le Multicast est réservé aux événements transitoires non critiques pour le gameplay : un visuel d'explosion, un effet sonore ou un effet de particules cosmétique.

Pour tout ce qui persiste dans le temps — comme l'arme que vous tenez, si vous visez ou le contenu de votre inventaire — vous devez utiliser la Property Replication.

Cause profonde : Pourquoi cela a-t-il soudainement cassé ?

Si vos RPCs Run on Server fonctionnaient auparavant et ont soudainement cassé sur plusieurs systèmes (armes, visée, craft), vous êtes probablement victime de l'un des trois changements architecturaux suivants :

1. L'illusion Listen Server vs. Dedicated Server

Si vous testiez auparavant dans le Play-In-Editor (PIE) via un Listen Server, le joueur hôte est à la fois le client et le serveur. Un RPC "Run on Server" exécuté par l'hôte met immédiatement à jour l'état visuel local car l'hôte est le serveur. Lorsque vous passez enfin aux tests sur Dedicated Server (ou en tant que Client 2), l'illusion s'effondre. Le serveur met à jour sa mémoire isolée, et le client est laissé pour compte.

2. Ownership d'ActorComponent rompu

Si vous avez récemment refactorisé votre logique d'inventaire ou d'arme dans des classes UActorComponent, vous avez peut-être rompu la chaîne de réplication. Les RPCs ne peuvent être invoqués depuis les clients que si le client possède (owns) l'Actor. Si votre composant est spawn de manière dynamique sans propriétaire assigné via SetOwner(PlayerController), le serveur ignorera simplement le RPC. Nous traitons ce cauchemar architectural dans notre guide Multiplayer Inventory Nightmares Fixing Swapped Actorcomponent Owners In Unreal Engine.

3. Contournement de l'état local

Auparavant, votre événement d'entrée côté client définissait peut-être le booléen bIsAiming local avant d'appeler le Server RPC. Si vous avez refactorisé votre code pour être purement "Server Authoritative" (attendre que le serveur dicte l'état), mais oublié de répliquer cet état au client, votre client attendra indéfiniment une mise à jour qui n'arrivera jamais.

Tutoriel étape par étape : Architecturer une réplication d'état robuste

Pour corriger cet unreal engine rpc replication issue, nous devons passer d'une architecture pilotée par RPC à une State-Driven Architecture utilisant les RepNotifies.

Voici comment implémenter correctement un système d'équipement d'arme et de visée server-authoritative.

Étape 1 : Définir les propriétés répliquées avec RepNotifies

Au lieu de faire confiance à un RPC pour déclencher des animations, nous déclarons des variables persistantes. Lorsque le serveur modifie ces variables, le Net Driver d'Unreal les synchronise automatiquement avec les clients. En attachant une fonction ReplicatedUsing (un RepNotify), nous déclenchons les animations exactement au moment où le client apprend le changement d'état.

Dans votre fichier Header de personnage (.h) :

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

public:
    AMyCharacter();

    // L'état persistant. Répliqué à tous les clients.
    UPROPERTY(ReplicatedUsing = OnRep_EquippedWeapon)
    AWeapon* EquippedWeapon;

    UPROPERTY(ReplicatedUsing = OnRep_IsAiming)
    bool bIsAiming;

    // Fonctions RepNotify. S'exécutent sur le client quand le serveur met à jour la variable.
    UFUNCTION()
    void OnRep_EquippedWeapon();

    UFUNCTION()
    void OnRep_IsAiming();

    // RPCs Serveur pour demander des changements d'état
    UFUNCTION(Server, Reliable, WithValidation)
    void Server_EquipWeapon(AWeapon* NewWeapon);

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

    // Configuration de la réplication
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};

Étape 2 : Implémenter les RPCs Serveur et les règles de réplication

Dans votre fichier .cpp, enregistrez ces variables dans GetLifetimeReplicatedProps. Définissez ensuite les RPCs Serveur pour ne mettre à jour que l'état authoritative.

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

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

    // Répliquer ces variables à tous les clients connectés
    DOREPLIFETIME(AMyCharacter, EquippedWeapon);
    DOREPLIFETIME(AMyCharacter, bIsAiming);
}

// --- LOGIQUE DE VISÉE ---

bool AMyCharacter::Server_SetAiming_Validate(bool bWantsToAim)
{
    // Anti-cheat : Vérifier si le joueur est autorisé à viser
    return !bIsDead;
}

void AMyCharacter::Server_SetAiming_Implementation(bool bWantsToAim)
{
    bIsAiming = bWantsToAim;
    
    // CRITIQUE : Les RepNotifies ne s'exécutent PAS automatiquement sur le serveur en C++.
    // Si le serveur est un Listen Server, nous devons l'appeler manuellement.
    if (GetNetMode() != NM_DedicatedServer)
    {
        OnRep_IsAiming();
    }
}

Étape 3 : Implémenter les RepNotifies pour les mises à jour visuelles

C'est ici que se place votre logique d'animation, d'UI et d'attachement de mesh. Comme cela repose sur un état répliqué, les joueurs rejoignant tardivement déclencheront automatiquement cette logique dès que votre personnage deviendra pertinent pour eux.

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);
    }
}

La touche professionnelle : Client-Side Prediction

Sans cela, vous aurez de l'Input Latency. Avec un ping de 100ms, le joueur ressentira un délai de 200ms avant que son personnage ne vise. C'est inacceptable dans un shooter moderne.

Solution : Client-Side Prediction. Le client simule visuellement le changement d'état immédiatement, tout en demandant la permission au serveur.

void AMyCharacter::StartAiming()
{
    // 1. Prédire localement immédiatement (Zéro latence pour le joueur)
    bIsAiming = true;
    OnRep_IsAiming(); 

    // 2. Informer le serveur
    if (!HasAuthority())
    {
        Server_SetAiming(true);
    }
}

Si le serveur refuse, l'état répliqué corrigera le client. C'est la base d'une architecture multijoueur robuste, comme discuté dans The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It.

Au-delà du match : Persistance de l'état du joueur

La réplication gère l'état pendant le match. Mais qu'en est-il de l'inventaire après la partie ? Pour que les joueurs gardent leur équipement, l'état doit quitter Unreal Engine pour une base de données sécurisée.

Développer cela (load balancers, APIs, SSL) prend des semaines. Avec horizOn, ces services backend sont pré-configurés. Vous pouvez sauvegarder l'ID de l'EquippedWeapon et l'inventaire directement dans le cloud via des SDKs natifs.

5 Bonnes Pratiques pour la Réplication Unreal Engine

  1. Jamais de Multicast pour l'état persistant : Utilisez des Replicated Properties. Réservez le Multicast aux effets visuels.
  2. Appelez les RepNotifies manuellement sur le serveur : En C++, les fonctions OnRep_ ne se déclenchent pas seules sur le serveur.
  3. Validez vos Server RPCs : Ne faites jamais confiance au client. Utilisez _Validate pour les contrôles anti-triche.
  4. Surveillez NetUpdateFrequency : Si l'état visuel semble lagger, vérifiez la fréquence de mise à jour de l'Actor.
  5. Vérifiez l'Ownership des composants : Les RPCs de composants nécessitent un propriétaire valide (PlayerController).

Arrêtez de lutter contre le Net Driver

Le système de réplication d'Unreal est puissant mais impitoyable. Quand vos états clients ne se mettent plus à jour, résistez au Multicast. Suivez le flux d'autorité : le client demande, le serveur décide, la propriété se réplique.

Prêt à passer au niveau supérieur ? Ne vous souciez plus de l'infrastructure. Essayez horizOn gratuitement pour une progression persistante et un matchmaking fluide.