Cómo solucionar el Player Location Desync en UEFN y Unreal Engine Multiplayer
La pesadilla del Multiplayer Transform Desync
Todo desarrollador de juegos multijugador se enfrenta tarde o temprano al momento en que su netcode empieza a mentirle. Programas una secuencia donde un jugador se sienta en una silla, la silla se mueve por el mapa y todo parece perfecto en el servidor. Pero al cargar un segundo cliente, la ilusión se rompe. El Cliente A se ve a sí mismo sentado perfectamente. El Cliente B ve la silla moverse mientras el Cliente A flota congelado en el aire en el punto de partida.
Este fenómeno específico —el uefn player location desync— es un punto de dolor bien documentado al usar inputs de jugador para activar comandos MoveTo en props que tienen jugadores vinculados (attached). El servidor registra la ubicación absoluta correcta, pero los simulated proxies (las representaciones del jugador en otros clientes) no heredan el transform actualizado de su prop padre.
Ya sea que estés creando una experiencia personalizada en Unreal Editor for Fortnite (UEFN) o diseñando un dedicated server independiente en Unreal Engine 5, entender por qué falla la replication de los attachments es crítico. En este tutorial, desglosaremos la mecánica exacta de Network Dormancy, por qué los attachments rompen la client-side prediction y cómo forzar al replication graph a respetar el estado de tu mundo.
Por qué los Attachments rompen las Network Updates
Para solucionar el desync, primero debes entender cómo Unreal Engine gestiona la actor replication de forma jerárquica.
Cuando un character actor se vincula a un prop (como sentarse en un dispositivo de silla), su transform se vuelve relativo al actor padre. En un entorno de red, el servidor debe decidir cómo transmitir estos datos a 100 clientes diferentes de manera eficiente. Para ahorrar ancho de banda, Unreal Engine utiliza agresivamente un concepto llamado Network Dormancy.
Cuando un jugador se sienta y deja de proporcionar input de movimiento directo, el replication graph del servidor puede marcar al player actor como dormant (latente). El servidor asume: "El jugador no se mueve de forma independiente, así que no necesito enviar actualizaciones para él. Solo enviaré actualizaciones para la silla en movimiento".
La matemática detrás del Desync
Veamos los números concretos que causan este fallo:
- Server Tick Rate: Normalmente 30Hz en UEFN.
- Prop NetUpdateFrequency: A menudo por defecto es 100 actualizaciones por segundo.
- Character MinNetUpdateFrequency: Puede bajar hasta 2.0 cuando no se detecta input o se activa la dormancy.
Al llamar a MoveTo en la silla, su transform se actualiza a 30Hz. Sin embargo, como el jugador vinculado está dormant o actualizándose a una frecuencia reducida, los otros clientes nunca reciben el RPC (Remote Procedure Call) que les indica que actualicen la posición relativa del jugador. ¿El resultado? Un desync visual masivo.
Si tienes problemas con otras variables que no se sincronizan, consulta The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It.
Paso 1: El Workaround en UEFN Verse
En los informes de errores oficiales de UEFN, los desarrolladores notaron una pista vital: salir de la silla soluciona el problema.
¿Por qué? Porque cambiar el movement mode del jugador de Custom (sentado) a Walking obliga al servidor a limpiar (flush) la Network Dormancy y transmitir una actualización de transform absoluta a todos los clientes.
Podemos recrear este "flush" programáticamente en Verse sin obligar al jugador a salir. Manipulando el estado del jugador o forzando un micro-teleport, despertamos al replication graph.
Aquí tienes una implementación en Verse que asegura que el transform del jugador se mantenga sincronizado:
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
prop_mover_device := class(creative_device):
@editable TargetProp : creative_prop = creative_prop{}
@editable ChairSeat : chair_device = chair_device{}
# Mueve el prop y fuerza un network update para los jugadores vinculados
SyncMoveProp(NewLocation : vector3, NewRotation : rotation, Duration: float)<suspends>:void=
# 1. Ejecutar el movimiento en el servidor
MoveResult := TargetProp.MoveTo(NewLocation, NewRotation, Duration)
# 2. Workaround para el UEFN Player Location Desync
ForceNetworkUpdate()
ForceNetworkUpdate():void=
if (SeatedAgent := ChairSeat.GetSeatedPlayer[]):
if (FortCharacter := SeatedAgent.GetFortCharacter[]):
CurrentTransform := FortCharacter.GetTransform()
# Teletransportar al personaje a su ubicación exacta actual
# Esto dispara un broadcast inmediato del transform absoluto
# a todos los proxy clients, resolviendo el desync visual.
FortCharacter.TeleportTo[CurrentTransform.Translation, CurrentTransform.Rotation]
Print("Forced replication update for seated player.")
Al usar TeleportTo a las mismas coordenadas, engañamos al motor C++ para que active el flag TeleportPhysics, reiniciando la client-side prediction de ese actor.
Paso 2: El Fix nativo en Unreal Engine C++
Si tienes acceso al código fuente o usas Unreal Engine C++ estándar, puedes interactuar directamente con la API de networking.
// Ejemplo de forzar net update en UE5 C++
void AMyVehicle::MoveVehicleAndSync(FVector NewLocation, FRotator NewRotation)
{
SetActorLocationAndRotation(NewLocation, NewRotation);
TArray<AActor*> AttachedActors;
GetAttachedActors(AttachedActors);
for (AActor* AttachedActor : AttachedActors)
{
if (AMyGameCharacter* SeatedPlayer = Cast<AMyGameCharacter>(AttachedActor))
{
if (SeatedPlayer->HasAuthority())
{
SeatedPlayer->FlushNetDormancy();
SeatedPlayer->ForceNetUpdate();
// Para escenarios de latencia extrema, lanzar un RPC al cliente
SeatedPlayer->Client_ForceSetLocation(SeatedPlayer->GetActorLocation(), SeatedPlayer->GetActorRotation());
}
}
}
}
Paso 3: Persistencia en el Backend
Si tu juego tiene un mundo persistente, debes asegurar que se guarden las coordenadas absolutas correctas. Consulta siempre el transform server-authoritative después de un forced net update.
Crear un backend para guardados de alta frecuencia es costoso. horizOn ofrece un Backend-as-a-Service diseñado para desarrolladores de juegos, con persistencia en tiempo real preconfigurada.
5 Buenas Prácticas
- Nunca confíes en el estado de attachment del cliente: Usa RPCs server-authoritative.
- Gestiona la NetDormancy manualmente: Llama a
FlushNetDormancydurante movimientos activos. - Jerarquías simples: Evita attachments de muchos niveles.
- RPCs fiables para cambios críticos: Usa NetMulticast para subir/bajar de props.
- Valida transforms al bajar: Comprueba la ubicación en el servidor al salir de un asiento.
Conclusión
Los desyncs multijugador suelen ser el resultado de optimizaciones agresivas. Al entender la Network Dormancy, puedes forzar actualizaciones proactivamente. Toma el control de tu replication graph y asegura la autoridad del servidor.
¿Listo para escalar tu backend? Prueba horizOn gratis y conecta tus servidores dedicados de Unreal Engine en minutos.