'ReplicatedUsing' & Setting variable on owning client beforehand - Need help understanding the behaviour

The PlayerController sets an FRotator with ReplicatedUsing to call a function that updates the FRotator when the player right clicks.

On Tick, the Character will rotate towards this FRotator. For testing purposes it is simply SetActorRotation() however it will use a lerp once I get it working, so it does need to run on Tick and not as a one-off.

Simply doing it this way would work fine, other than it doesn’t account for latency so if the player right clicks there will be a delay before the character reacts. To counteract this, I set the variable manually on the client so that they see it update on their end immediately.

However, this means that initially the client will rotate, then once the latency catches up it will repeat that rotation. For an example of what this actually looks like here’s a quick GIF. The things to keep in mind is that the FRotator variable is being set to an identical value on the server and all clients regardless of when it takes place.

90631-jittergif.gif


Here is the code:

.h

UFUNCTION(Reliable, Server, WithValidation)
void ServerLookRotation(FRotator InRotation);

UPROPERTY(ReplicatedUsing = OnRep_LookRotation)
FRotator R_LookRotation;

UFUNCTION()
virtual void OnRep_LookRotation();

FRotator LookRotation;

GetLifetimeReplicatedProps - no difference without a condition

DOREPLIFETIME_CONDITION(APlayer_Proxy, R_LookRotation, COND_SimulatedOnly);

Replication from .cpp

bool APlayer_Proxy::ServerLookRotation_Validate(FRotator InRotation)
{
	return true;
}

void APlayer_Proxy::ServerLookRotation_Implementation(FRotator InRotation)
{
	//DoRep_LookRotation = !DoRep_LookRotation;
	R_LookRotation = InRotation;

	OnRep_LookRotation();
}

void APlayer_Proxy::OnRep_LookRotation()
{
	//LookRotation = R_LookRotation;
	if (Role != ROLE_AutonomousProxy)
	{
		LookRotation = R_LookRotation;
		Print(FString("Doing!"), this);
	}
}

Tick from .cpp

if (CR != nullptr)
{
	CR->SetActorRotation(LookRotation);
	if (Role != ROLE_SimulatedProxy)
	{
		Print(LookRotation, this);
	}
}

What causes your behavior, despite your OnRep only executing for non autonomous proxy, is that the Character’s CharacterMovementComponent already handles replication of location and rotation. So two mechanisms here are fighting about what the character’s rotation should be. You’ll have to disable one of them.

The character movement component has its own copy of the rotation and applies smoothing. This causes the setback, despite the client and server having the same vakue in your custom rotation variable.

Having replicated rotation manually before like you’re doing, I recommend trying to use the CharacterMovementComponent because it already has sophisticated lag compensation and variables for tweaking it, for example in the editor look for ‘smoothing’. Unless of course you have extraordinary rotation mechanics.

Hi there, sorry for the late reply but it was a ton of work implementing a custom FSavedMoves particularly with my Player Pathfinding implementation causing lots of issues.

In the end, it works. It was excessive for this one thing, but with other functionality that I need it’s worth it overall. Besides, it’s smooth as butter this way.