Multiplayer Desyncs: Jak naprawić Unreal Engine RPC Replication Issue psujący stany gry
Każdy niezależny twórca gier multiplayer zna ten moment, w którym netcode go zdradza. Wywołujesz RPC Run on Server, aby wyposażyć postać w broń. Logi serwera potwierdzają: broń została dodana. Cylinder kolizji na serwerze pokazuje, że postać celuje. Ale na ekranie klienta? Twoja postać po prostu stoi w domyślnej pozie idle, całkowicie ignorując zmianę stanu.
Kiedy logika wyposażenia broni, stany celowania, interakcje z ekwipunkiem i systemy craftingu nagle przestają się aktualizować u klienta, pojawia się panika. Możesz odkryć, że zmiana RPC na Multicast magicznie naprawia błędy wizualne.
Nie zostawiaj tego na Multicast.
Używanie Multicast do naprawiania błędów trwałych stanów (persistent state) to tylko plaster, który ostatecznie zniszczy wydajność sieciową Twojej gry i zrujnuje wrażenia graczom dołączającym w trakcie rozgrywki (late-joining). W tej analizie przyjrzymy się przyczynom niesławnego unreal engine rpc replication issue, wyjaśnimy, dlaczego stany serwera ignorują klientów, i zaprojektujemy niezawodną synchronizację stanów typu server-authoritative przy użyciu C++.
Pułapka Multicast: Dlaczego to „działa” (i dlaczego zniszczy Twoją grę)
Kiedy deweloperzy napotykają ten błąd, proces myślowy zazwyczaj wygląda tak:
- Klient wywołuje
Server_EquipWeapon(). - Serwer wyposaża postać w broń.
- Wizualizacja u klienta nie aktualizuje się.
- Zmieniasz
Server_EquipWeapon(), aby wywoływałoMulticast_EquipWeapon(). - Wizualizacja u klienta działa! Błąd naprawiony, prawda?
Błąd. Aby zrozumieć dlaczego, musisz pojąć fundamentalną różnicę między RPCs (Remote Procedure Calls) a Property Replication.
RPC to przejściowe zdarzenie sieciowe. To krzyk w próżnię. Jeśli gracz znajduje się w zasięgu network cull distance w momencie wywołania Multicast, usłyszy ten krzyk i odegra animację.
Ale co się stanie, jeśli gracz dołączy do serwera 10 sekund później? Co jeśli gracz znajduje się 5000 jednostek Unreal dalej, wejdzie w zasięg istotności (relevancy range) i zobaczy Twoją postać? Ponieważ Multicast został wywołany w przeszłości, nowy klient nigdy nie otrzyma tego zdarzenia. Zobaczy Twoją postać trzymającą niewidzialną broń, ślizgającą się w pozie idle, podczas gdy pociski wylatują z jej klatki piersiowej.
Multicast służy do zdarzeń przejściowych, niekrytycznych dla rozgrywki: efektów wizualnych eksplozji, dźwięków czy kosmetycznych cząsteczek.
Dla wszystkiego, co trwa w czasie — jak to, jaką broń trzymasz, czy celujesz, czy co masz w ekwipunku — musisz używać Property Replication.
Przyczyna: Dlaczego nagle przestało działać?
Jeśli Twoje RPC Run on Server wcześniej działały, a nagle przestały w wielu systemach (broń, celowanie, crafting), prawdopodobnie padłeś ofiarą jednej z trzech zmian architektonicznych w projekcie:
1. Iluzja Listen Server vs. Dedicated Server
Jeśli wcześniej testowałeś w Play-In-Editor (PIE) używając Listen Server, host jest jednocześnie klientem i serwerem. RPC "Run on Server" wykonane przez hosta natychmiast aktualizuje lokalny stan wizualny, ponieważ host jest serwerem. Kiedy w końcu przechodzisz na testy Dedicated Server (lub testujesz jako Klient 2), iluzja pryska. Serwer aktualizuje swoją odizolowaną pamięć, a klient zostaje w tyle.
2. Przerwany Ownership w ActorComponent
Jeśli niedawno przeniosłeś logikę ekwipunku lub broni do klas UActorComponent, mogłeś przerwać łańcuch replikacji. RPC mogą być wywoływane z klientów tylko wtedy, gdy klient posiada (owns) danego Actora. Jeśli Twój komponent jest tworzony dynamicznie i nie ma przypisanego właściciela przez SetOwner(PlayerController), serwer po prostu odrzuci RPC. Omawiamy ten koszmar w naszym poradniku Multiplayer Inventory Nightmares Fixing Swapped Actorcomponent Owners In Unreal Engine.
3. Pominięcie stanu lokalnego
Wcześniej zdarzenie wejściowe po stronie klienta mogło ustawiać lokalną zmienną bIsAiming przed wywołaniem Server RPC. Jeśli zmieniłeś kod na czysto „Server Authoritative” (czekanie, aż serwer podyktuje stan), ale zapomniałeś zreplikować ten stan z powrotem do klienta, Twój klient będzie wiecznie czekał na aktualizację, która nigdy nie nadejdzie.
Tutorial krok po kroku: Projektowanie niezawodnej replikacji stanu
Aby naprawić ten unreal engine rpc replication issue, musimy przejść z architektury opartej na RPC na State-Driven Architecture z użyciem RepNotifies.
Oto jak poprawnie zaimplementować system wyposażania broni i celowania typu server-authoritative.
Krok 1: Definiowanie replikowanych właściwości z RepNotifies
Zamiast ufać RPC w kwestii wyzwalania animacji, deklarujemy trwałe zmienne. Gdy serwer je zmienia, Net Driver Unreala automatycznie synchronizuje je z klientami. Dołączając funkcję ReplicatedUsing (RepNotify), możemy wyzwolić animacje dokładnie wtedy, gdy klient dowie się o zmianie stanu.
W pliku nagłówkowym postaci (.h):
UCLASS()
class MYGAME_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyCharacter();
// Trwały stan. Replikowany do wszystkich klientów.
UPROPERTY(ReplicatedUsing = OnRep_EquippedWeapon)
AWeapon* EquippedWeapon;
UPROPERTY(ReplicatedUsing = OnRep_IsAiming)
bool bIsAiming;
// Funkcje RepNotify. Uruchamiane na kliencie, gdy serwer aktualizuje zmienną.
UFUNCTION()
void OnRep_EquippedWeapon();
UFUNCTION()
void OnRep_IsAiming();
// Server RPCs do żądania zmian stanu
UFUNCTION(Server, Reliable, WithValidation)
void Server_EquipWeapon(AWeapon* NewWeapon);
UFUNCTION(Server, Reliable, WithValidation)
void Server_SetAiming(bool bWantsToAim);
// Podstawowa konfiguracja replikacji
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};
Krok 2: Implementacja Server RPCs i zasad replikacji
W pliku .cpp musisz zarejestrować te zmienne w GetLifetimeReplicatedProps. Następnie zdefiniuj Server RPCs tak, aby aktualizowały tylko stan authoritative.
#include "MyCharacter.h"
#include "Net/UnrealNetwork.h"
void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Replikuj te zmienne do wszystkich połączonych klientów
DOREPLIFETIME(AMyCharacter, EquippedWeapon);
DOREPLIFETIME(AMyCharacter, bIsAiming);
}
// --- LOGIKA CELOWANIA ---
bool AMyCharacter::Server_SetAiming_Validate(bool bWantsToAim)
{
// Anti-cheat: Upewnij się, że gracz może celować (np. nie nie żyje)
return !bIsDead;
}
void AMyCharacter::Server_SetAiming_Implementation(bool bWantsToAim)
{
bIsAiming = bWantsToAim;
// WAŻNE: RepNotifies NIE uruchamiają się automatycznie na serwerze w C++.
// Jeśli serwer jest typu Listen Server, musimy wywołać to ręcznie.
if (GetNetMode() != NM_DedicatedServer)
{
OnRep_IsAiming();
}
}
Krok 3: Implementacja RepNotifies dla aktualizacji wizualnych
Tutaj umieszczasz logikę animacji, aktualizacje UI i podpinanie meshy. Ponieważ opiera się to na zreplikowanym stanie, gracze dołączający później automatycznie wyzwolą tę logikę, gdy tylko Twoja postać stanie się dla nich istotna.
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);
}
}
Profesjonalny sznyt: Client-Side Prediction
Bez predykcji napotkasz Input Latency. Przy pingu 100ms gracz odczuje 200ms opóźnienia, zanim postać zacznie celować. W nowoczesnych strzelankach to niedopuszczalne.
Rozwiązanie: Client-Side Prediction. Klient natychmiast wizualnie symuluje zmianę stanu, jednocześnie prosząc serwer o zatwierdzenie.
void AMyCharacter::StartAiming()
{
// 1. Natychmiastowa predykcja lokalna (Zero opóźnienia dla gracza)
bIsAiming = true;
OnRep_IsAiming();
// 2. Poinformuj serwer, aby zatwierdził zmianę
if (!HasAuthority())
{
Server_SetAiming(true);
}
}
Jeśli serwer się nie zgodzi, zreplikowany stan skoryguje klienta. To fundament solidnej architektury multiplayer, o czym piszemy w The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It.
Skalowanie poza mecz: Persystencja stanu gracza
Replikacja dba o stan w trakcie meczu. Ale co z ekwipunkiem po zakończeniu gry? Aby gracze zachowali postępy, stan musi opuścić Unreal Engine i trafić do bezpiecznej bazy danych.
Samodzielne budowanie tego (load balancers, APIs, SSL) zajmuje tygodnie. Dzięki horizOn te usługi backendowe są gotowe. Możesz zapisywać ID EquippedWeapon i ekwipunek bezpośrednio w chmurze przez natywne SDK.
5 dobrych praktyk replikacji w Unreal Engine
- Nigdy nie używaj Multicast dla trwałych stanów: Używaj Replicated Properties. Multicast zostaw dla efektów wizualnych.
- Ręcznie wywołuj RepNotifies na serwerze: W C++ funkcje
OnRep_nie uruchamiają się same na serwerze. - Waliduj Server RPCs: Nigdy nie ufaj klientowi. Używaj
_Validatedo sprawdzania logiki. - Pamiętaj o NetUpdateFrequency: Jeśli wizualizacje lagują, sprawdź częstotliwość aktualizacji Actora.
- Sprawdzaj Ownership komponentów: RPC z komponentów wymagają poprawnego właściciela (PlayerController).
Przestań walczyć z Net Driverem
System replikacji Unreala jest potężny, ale bezlitosny. Gdy stany klienta się nie aktualizują, nie ulegaj pokusie nadużywania Multicast. Podążaj ścieżką autorytetu: klient prosi, serwer decyduje, właściwość się replikuje.
Gotowy na wyższy poziom? Przestań martwić się o infrastrukturę. Wypróbuj horizOn za darmo i zapewnij graczom trwały postęp i płynny matchmaking.