Назад к блогу

Multiplayer Desyncs: Исправление Unreal Engine RPC Replication Issue, ломающего состояния

Опубликовано 3 марта 2026 г.
Multiplayer Desyncs: Исправление Unreal Engine RPC Replication Issue, ломающего состояния

Каждый независимый разработчик мультиплеерных игр знает тот самый момент, когда неткод его предает. Вы вызываете RPC Run on Server, чтобы экипировать оружие. Логи сервера подтверждают: оружие экипировано. Коллизия на сервере показывает, что вы находитесь в стойке прицеливания. Но на экране клиента? Ваш персонаж просто стоит в дефолтной idle-позе, совершенно не реагируя на изменение состояния.

Когда логика экипировки, состояния прицеливания, взаимодействие с инвентарем и системы крафта внезапно перестают обновляться на клиенте, начинается паника. Вы можете обнаружить, что переключение RPC на Multicast магическим образом исправляет визуальные баги.

Не оставляйте это на Multicast.

Использование Multicast для исправления багов персистентного состояния — это «костыль», который в конечном итоге уничтожит сетевую производительность вашей игры и испортит опыт для игроков, подключившихся позже (late-joining). В этом глубоком разборе мы разберем первопричину пресловутой unreal engine rpc replication issue, объясним, почему состояния вашего сервера игнорируют клиентов, и спроектируем надежную server-authoritative синхронизацию состояний на C++.

Ловушка Multicast: Почему это «работает» (и почему это погубит вашу игру)

Когда разработчики сталкиваются с этим багом, ход мыслей обычно такой:

  1. Клиент вызывает Server_EquipWeapon().
  2. Сервер экипирует оружие.
  3. Визуал на клиенте не обновляется.
  4. Меняем Server_EquipWeapon() на вызов Multicast_EquipWeapon().
  5. Визуал на клиенте обновился! Баг исправлен, верно?

Неверно. Чтобы понять почему, нужно осознать фундаментальную разницу между RPCs (Remote Procedure Calls) и Property Replication.

RPC — это кратковременное сетевое событие. Это крик в пустоту. Если игрок находится в пределах network cull distance в момент срабатывания Multicast, он слышит крик и проигрывает анимацию экипировки.

Но что произойдет, если игрок присоединится к серверу через 10 секунд? Что если игрок находится в 5000 юнитов Unreal, входит в зону релевантности (relevancy range) и видит вашего персонажа? Поскольку Multicast уже сработал в прошлом, новый клиент никогда не получит это событие. Он увидит вашего персонажа с невидимым оружием, скользящего в idle-позе и стреляющего пулями из груди.

Multicast предназначен для кратковременных, не критичных для геймплея событий: визуал взрыва, звуковой эффект или косметические частицы.

Для всего, что сохраняется во времени — какое оружие у вас в руках, целитесь ли вы или что лежит в вашем инвентаре — вы должны использовать Property Replication.

Первопричина: Почему все вдруг сломалось?

Если ваши RPC Run on Server раньше работали, а потом внезапно сломались в нескольких системах (оружие, прицеливание, крафт), вы, скорее всего, стали жертвой одного из трех архитектурных сдвигов в проекте:

1. Иллюзия Listen Server vs. Dedicated Server

Если вы раньше тестировали в Play-In-Editor (PIE) через Listen Server, хост-игрок является одновременно и клиентом, и сервером. RPC «Run on Server», выполненный хостом, немедленно обновляет локальное визуальное состояние, потому что хост и есть сервер. Когда вы переходите к тестированию на Dedicated Server (или тестируете как Клиент 2), иллюзия рушится. Сервер обновляет свою изолированную память, а клиент остается ни с чем.

2. Нарушение Ownership в ActorComponent

Если вы недавно перенесли логику инвентаря или оружия в классы UActorComponent, вы могли разорвать цепочку репликации. RPC могут вызываться клиентами только в том случае, если клиент владеет (owns) актором. Если ваш компонент спавнится динамически и ему не назначен владелец через SetOwner(PlayerController), сервер просто проигнорирует RPC. Мы подробно разбираем этот кошмар в гайде Multiplayer Inventory Nightmares Fixing Swapped Actorcomponent Owners In Unreal Engine.

3. Обход локального состояния

Раньше ваше событие ввода на стороне клиента могло устанавливать локальную переменную bIsAiming до вызова Server RPC. Если вы переписали код на чисто «Server Authoritative» (ожидание, пока сервер продиктует состояние), но забыли реплицировать это состояние обратно клиенту, ваш клиент будет вечно ждать обновления, которое никогда не придет.

Пошаговый туториал: Проектирование надежной репликации состояний

Чтобы исправить эту unreal engine rpc replication issue, мы должны перейти от архитектуры на базе RPC к State-Driven Architecture с использованием RepNotifies.

Вот как правильно реализовать server-authoritative систему экипировки и прицеливания.

Шаг 1: Определение реплицируемых свойств с RepNotifies

Вместо того чтобы доверять RPC запуск анимаций, мы объявляем персистентные переменные. Когда сервер меняет эти переменные, Net Driver Unreal автоматически синхронизирует их с клиентами. Привязывая функцию ReplicatedUsing (RepNotify), мы запускаем анимации именно тогда, когда клиент узнает об изменении состояния.

В заголовочном файле персонажа (.h):

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

public:
    AMyCharacter();

    // Персистентное состояние. Реплицируется всем клиентам.
    UPROPERTY(ReplicatedUsing = OnRep_EquippedWeapon)
    AWeapon* EquippedWeapon;

    UPROPERTY(ReplicatedUsing = OnRep_IsAiming)
    bool bIsAiming;

    // Функции RepNotify. Запускаются на клиенте, когда сервер обновляет переменную.
    UFUNCTION()
    void OnRep_EquippedWeapon();

    UFUNCTION()
    void OnRep_IsAiming();

    // Server RPCs для запроса изменения состояния
    UFUNCTION(Server, Reliable, WithValidation)
    void Server_EquipWeapon(AWeapon* NewWeapon);

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

    // Базовая настройка репликации
    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};

Шаг 2: Реализация Server RPCs и правил репликации

В файле .cpp вы должны зарегистрировать эти переменные в GetLifetimeReplicatedProps. Затем определите Server RPCs так, чтобы они обновляли только авторитетное состояние.

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

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

    // Реплицировать эти переменные всем подключенным клиентам
    DOREPLIFETIME(AMyCharacter, EquippedWeapon);
    DOREPLIFETIME(AMyCharacter, bIsAiming);
}

// --- ЛОГИКА ПРИЦЕЛИВАНИЯ ---

bool AMyCharacter::Server_SetAiming_Validate(bool bWantsToAim)
{
    // Anti-cheat: Проверка, может ли игрок целиться (например, не мертв ли он)
    return !bIsDead;
}

void AMyCharacter::Server_SetAiming_Implementation(bool bWantsToAim)
{
    bIsAiming = bWantsToAim;
    
    // ВАЖНО: RepNotifies НЕ запускаются автоматически на сервере в C++.
    // Если сервер — это Listen Server, мы должны вызвать функцию вручную.
    if (GetNetMode() != NM_DedicatedServer)
    {
        OnRep_IsAiming();
    }
}

Шаг 3: Реализация RepNotifies для визуальных обновлений

Здесь располагается логика анимаций, обновления UI и аттач мешей. Поскольку это опирается на реплицированное состояние, игроки, подключившиеся позже, автоматически запустят эту логику, как только ваш персонаж станет для них релевантным.

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

Профессиональный подход: Client-Side Prediction

Без предсказания вы столкнетесь с Input Latency. При пинге 100 мс игрок почувствует задержку в 200 мс перед тем, как персонаж начнет целиться. В современном шутере это ощущается ужасно.

Решение: Client-Side Prediction. Клиент визуально имитирует изменение состояния немедленно, одновременно запрашивая разрешение у сервера.

void AMyCharacter::StartAiming()
{
    // 1. Предсказать локально немедленно (Нулевая задержка для игрока)
    bIsAiming = true;
    OnRep_IsAiming(); 

    // 2. Сообщить серверу, чтобы сделать это официальным
    if (!HasAuthority())
    {
        Server_SetAiming(true);
    }
}

Если сервер не согласен, реплицированное состояние скорректирует клиента. Это основа надежной мультиплеерной архитектуры, как описано в The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It.

Масштабирование: Сохранение состояния игрока

Репликация обеспечивает синхронизацию во время матча. Но что происходит с инвентарем после игры? Чтобы игроки сохраняли прогресс, состояние должно покинуть Unreal Engine и попасть в базу данных.

Создание такой системы (load balancers, APIs, SSL) занимает недели. С horizOn эти бэкенд-сервисы уже настроены. Вы можете сохранять ID EquippedWeapon и инвентарь прямо в облако через нативные SDK.

5 правил репликации в Unreal Engine

  1. Никогда не используйте Multicast для персистентных состояний: Используйте Replicated Properties. Multicast — только для визуальных эффектов.
  2. Вызывайте RepNotifies на сервере вручную: В C++ функции OnRep_ не срабатывают сами на сервере.
  3. Валидируйте Server RPCs: Никогда не верьте клиенту. Используйте _Validate для проверок.
  4. Следите за NetUpdateFrequency: Если визуал лагает, проверьте частоту обновления актора.
  5. Проверяйте Ownership компонентов: RPC из компонентов требуют валидного владельца (PlayerController).

Хватит бороться с Net Driver

Система репликации Unreal мощная, но беспощадная. Когда состояния клиентов не обновляются, не поддавайтесь искушению спамить Multicast. Следуйте пути авторитета: клиент запрашивает, сервер диктует, свойство реплицируется.

Готовы к новому уровню? Забудьте об управлении инфраструктурой. Попробуйте horizOn бесплатно для персистентной прогрессии и бесшовного матчмейкинга.