Multiplayer Desyncs: Corrigindo o Unreal Engine RPC Replication Issue que quebra seus estados
Todo desenvolvedor indie de jogos multiplayer conhece o momento exato em que seu netcode o trai. Você dispara um RPC Run on Server para equipar uma arma. Os logs do servidor confirmam que a arma está equipada. O cilindro de colisão do servidor mostra você em posição de mira. Mas na tela do seu cliente? Seu personagem está apenas parado em uma pose idle padrão, completamente indiferente à mudança de estado.
Quando sua lógica de equipar armas, estados de mira, interações de inventário e sistemas de crafting param de atualizar repentinamente no cliente, o pânico se instala. Você pode descobrir que mudar o RPC para Multicast corrige magicamente os bugs visuais.
Não deixe no Multicast.
Usar Multicast para corrigir bugs de estado persistente é um paliativo que acabará destruindo a performance de rede do seu jogo e arruinando a experiência para jogadores que entrarem depois (late-joining). Neste mergulho profundo, vamos desvendar a causa raiz do temido unreal engine rpc replication issue, explicar por que seus estados de servidor ignoram seus clientes e arquitetar uma sincronização de estado server-authoritative à prova de balas usando C++.
A Armadilha do Multicast: Por que "funciona" (e por que vai arruinar seu jogo)
Quando os desenvolvedores encontram esse bug, o processo de pensamento geralmente é este:
- O cliente chama
Server_EquipWeapon(). - O servidor equipa a arma.
- O visual do cliente não atualiza.
- Altera
Server_EquipWeapon()para chamarMulticast_EquipWeapon(). - O visual do cliente atualiza! Bug corrigido, certo?
Errado. Para entender o porquê, você deve compreender a diferença fundamental entre RPCs (Remote Procedure Calls) e Property Replication.
Um RPC é um evento de rede transitório. É um grito no vazio. Se um jogador estiver dentro da network cull distance quando o Multicast disparar, ele ouvirá o grito e reproduzirá a animação de equipar.
Mas o que acontece se um jogador entrar no servidor 10 segundos depois? O que acontece se um jogador estiver a 5.000 Unreal Units de distância, entrar no alcance de relevância (relevancy range) e vir seu personagem? Como o Multicast já disparou no passado, o novo cliente nunca recebe o evento. Ele verá seu personagem segurando uma arma invisível, deslizando em pose idle enquanto atira balas pelo peito.
Multicast é para eventos transitórios e não críticos para o gameplay: um visual de explosão, um efeito sonoro ou um efeito de partículas cosmético.
Para qualquer coisa que persista ao longo do tempo — como qual arma você está segurando, se está mirando ou o que está no seu inventário — você deve usar Property Replication.
Causa Raiz: Por que quebrou de repente?
Se seus RPCs Run on Server funcionavam anteriormente e de repente quebraram em vários sistemas (armas, mira, crafting), você provavelmente é vítima de uma destas três mudanças arquitetônicas no seu projeto:
1. A ilusão do Listen Server vs. Dedicated Server
Se você estava testando anteriormente no Play-In-Editor (PIE) usando um Listen Server, o jogador host é tanto o cliente quanto o servidor. Um RPC "Run on Server" executado pelo host atualiza imediatamente o estado visual local porque o host é o servidor. Quando você finalmente muda para testes em Dedicated Server (ou testa como Cliente 2), a ilusão se quebra. O servidor atualiza sua memória isolada e o cliente fica para trás.
2. Ownership de ActorComponent quebrado
Se você refatorou recentemente sua lógica de inventário ou armas em classes UActorComponent, pode ter quebrado a cadeia de replicação. RPCs só podem ser invocados de clientes se o cliente possuir (owns) o Actor. Se o seu componente for spawnado dinamicamente e não for explicitamente atribuído a um dono via SetOwner(PlayerController), o servidor simplesmente descartará o RPC. Cobrimos esse pesadelo arquitetônico em nosso guia sobre Multiplayer Inventory Nightmares Fixing Swapped Actorcomponent Owners In Unreal Engine.
3. Ignorando o estado local
Anteriormente, seu evento de entrada no lado do cliente poderia estar definindo o booleano bIsAiming local antes de chamar o Server RPC. Se você refatorou seu código para ser puramente "Server Authoritative" (esperando o servidor ditar o estado), mas esqueceu de replicar esse estado de volta para o cliente, seu cliente esperará permanentemente por uma atualização que nunca chega.
Tutorial Passo a Passo: Arquitetando uma Replicação de Estado à Prova de Balas
Para corrigir este unreal engine rpc replication issue, devemos transitar de uma arquitetura baseada em RPC para uma State-Driven Architecture usando RepNotifies.
Aqui está como implementar corretamente um sistema de equipar armas e mira server-authoritative que atualiza o cliente perfeitamente.
Passo 1: Definir Propriedades Replicadas com RepNotifies
Em vez de confiar em um RPC para disparar animações, declaramos variáveis persistentes. Quando o servidor altera essas variáveis, o Net Driver da Unreal as sincroniza automaticamente com os clientes. Ao anexar uma função ReplicatedUsing (um RepNotify), podemos disparar as animações exatamente quando o cliente toma conhecimento da mudança de estado.
No seu arquivo de Header do Personagem (.h):
UCLASS()
class MYGAME_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyCharacter();
// O estado persistente. Replicado para todos os clientes.
UPROPERTY(ReplicatedUsing = OnRep_EquippedWeapon)
AWeapon* EquippedWeapon;
UPROPERTY(ReplicatedUsing = OnRep_IsAiming)
bool bIsAiming;
// Funções RepNotify. Executadas no cliente quando o servidor atualiza a variável.
UFUNCTION()
void OnRep_EquippedWeapon();
UFUNCTION()
void OnRep_IsAiming();
// RPCs de Servidor para solicitar mudanças de estado
UFUNCTION(Server, Reliable, WithValidation)
void Server_EquipWeapon(AWeapon* NewWeapon);
UFUNCTION(Server, Reliable, WithValidation)
void Server_SetAiming(bool bWantsToAim);
// Configuração principal de replicação
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};
Passo 2: Implementar os RPCs de Servidor e Regras de Replicação
No seu arquivo .cpp, você deve registrar essas variáveis em GetLifetimeReplicatedProps. Em seguida, defina os RPCs de Servidor para atualizar apenas o estado authoritative.
#include "MyCharacter.h"
#include "Net/UnrealNetwork.h"
void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Replicar estas variáveis para todos os clientes conectados
DOREPLIFETIME(AMyCharacter, EquippedWeapon);
DOREPLIFETIME(AMyCharacter, bIsAiming);
}
// --- LÓGICA DE MIRA ---
bool AMyCharacter::Server_SetAiming_Validate(bool bWantsToAim)
{
// Anti-cheat: Garantir que o jogador pode mirar (ex: não está morto)
return !bIsDead;
}
void AMyCharacter::Server_SetAiming_Implementation(bool bWantsToAim)
{
bIsAiming = bWantsToAim;
// CRÍTICO: RepNotifies NÃO rodam automaticamente no servidor em C++.
// Se o servidor for um Listen Server, devemos chamar manualmente.
if (GetNetMode() != NM_DedicatedServer)
{
OnRep_IsAiming();
}
}
Passo 3: Implementar os RepNotifies para Atualizações Visuais
Aqui é onde entra sua lógica de animação, atualizações de UI e anexos de mesh. Como isso depende do estado replicado, jogadores que entrarem depois dispararão automaticamente essa lógica no momento em que seu personagem se tornar relevante para eles.
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);
}
}
O Toque Profissional: Client-Side Prediction
Sem isso, você terá Input Latency. Com um ping de 100ms, o jogador sentirá um atraso de 200ms antes do personagem mirar. Em um shooter moderno, isso é terrível.
Solução: Client-Side Prediction. O cliente simula visualmente a mudança de estado imediatamente, enquanto pede permissão ao servidor.
void AMyCharacter::StartAiming()
{
// 1. Predizer localmente imediatamente (Latência zero para o jogador)
bIsAiming = true;
OnRep_IsAiming();
// 2. Avisar o servidor para oficializar
if (!HasAuthority())
{
Server_SetAiming(true);
}
}
Se o servidor discordar, o estado replicado corrigirá o cliente. Esta é a base de uma arquitetura multiplayer robusta, como discutido em The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It.
Além da Partida: Persistência do Estado do Jogador
A replicação cuida do estado durante a partida. Mas e o inventário após o jogo? Para manter o progresso, o estado deve sair do Unreal Engine para um banco de dados seguro.
Construir isso (load balancers, APIs, SSL) leva semanas. Com horizOn, esses serviços de backend vêm pré-configurados. Você pode salvar o ID da EquippedWeapon e o inventário diretamente na nuvem via SDKs nativos.
5 Melhores Práticas para Replicação na Unreal Engine
- Nunca use Multicast para estados persistentes: Use Replicated Properties. Reserve Multicast para efeitos visuais "fire and forget".
- Chame RepNotifies manualmente no servidor: Em C++, as funções
OnRep_não disparam sozinhas no servidor. - Valide seus Server RPCs: Nunca confie no cliente. Use
_Validatepara checagens anti-cheat. - Atenção à NetUpdateFrequency: Se o estado visual parecer lagar, verifique a frequência de atualização do Actor.
- Verifique o Ownership do componente: RPCs de componentes exigem um dono válido (PlayerController).
Pare de lutar contra o Net Driver
O sistema de replicação da Unreal é poderoso, mas implacável. Quando os estados dos seus clientes não atualizarem, resista ao Multicast. Siga o fluxo da autoridade: o cliente solicita, o servidor dita, a propriedade replica.
Pronto para o próximo nível? Esqueça a gestão de infraestrutura. Teste horizOn gratuitamente para progressão persistente e matchmaking fluido.