Replicating Actor Rotation

I’m trying to replicate my actor rotation to all other clients which are connected in a multiplayer game. So far I’ve already achieved the implementation of a boolean called bIsAiming, following this awesome tutorial: A new, community-hosted Unreal Engine Wiki - Announcements - Unreal Engine Forums.

However I tried to get the replication of the rotation working in the same manner but keep stumbling across the problem that the rotation is only set on the calling client + the server. This means the server fails to tell all other clients to update the rotation of said actor accordingly. I’m a bit puzzled at the moment. I already tried to replicate an FRotator and SetActorRotation in the EveryTick method but this wasn’t successful either.

Here is my code example, perhaps someone can point me in the right direction :slight_smile:

BaseCharacter.h

	// gets the rotation needed to aim at the crosshair
	FRotator getRotationToCrosshair();

	// rotates the actor
	void SetRotation(FRotator rot);

	UFUNCTION(Server, Reliable, WithValidation)
	void ServerSetRotation(FRotator rot);

	void ServerSetRotation_Implementation(FRotator rot);

	bool ServerSetRotation_Validate(FRotator rot);

BaseCharacter.cpp

FRotator ABaseCharacter::getRotationToCrosshair()
{
	//set Rotation towards crosshair
	FRotator camYawn = FRotator(0, FollowCamera->GetComponentRotation().Yaw, 0);
	FRotator charYawn = FRotator(0, GetControlRotation().Yaw, 0);
	FRotator charRotation = FMath::RInterpTo(camYawn, charYawn, 400, 0.1);
	return charRotation;
}

void ABaseCharacter::SetRotation(FRotator rot)
{
	
	SetActorRotation(rot);

	if (Role < ROLE_Authority)
	{
		ServerSetRotation(rot);
	}
}


//////////////////////////////////////////////////////
// REPLICATION
/////////////////////////////////////////////////////

void ABaseCharacter::ServerSetRotation_Implementation(FRotator rot)
{
	SetRotation(rot);
}

bool ABaseCharacter::ServerSetRotation_Validate(FRotator rot)
{
	return true;
}

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

	// Value is already updated locally, skip in replication step
	DOREPLIFETIME_CONDITION(ABaseCharacter, bIsAiming, COND_SkipOwner);
	//DOREPLIFETIME_CONDITION(ABaseCharacter, rotation, COND_SkipOwner);

the setRotation method is called here in the .cpp

void ABaseCharacter::Tick( float DeltaTime )
{
	Super::Tick( DeltaTime );

	if (IsAiming())
	{
		ZoomIn();
	}
	else
	{
		ZoomOut();
	}
}

void ABaseCharacter::ZoomIn()
{
	//to-do: toggle crosshair

	if (!bIsSprinting && GetCharacterMovement()->IsMovingOnGround())
	{
		if (!bIsZooming)
		{
			//switch to over the shoulder view
			FollowCamera->SetWorldLocation(ShoulderCameraPosition->GetComponentLocation(), true);
		}

		bIsZooming = true;

		SetRotation(getRotationToCrosshair());
	}
}

Thanks in advance!

~Theo

P.S: Is it possible that the characterMovementComponent already handels the replication of the bIsCrouched and if a character is in the air? I didn’t need to implement methods for jumping and crouching as they were perfectly working in a multiplayer setup.

This should get you started. I typed this all off of the top of my head so it’s likely a made a mistake somewhere. Let me know if you have any more problems/questions.

.h:

UPROPERTY(ReplicatedUsing = OnRep_OwnerRotation)
FRotator OwnerRotation;

UFUNCTION()
void OnRep_OwnerRotation();

UFUNCTION(Server, Unreliable, WithValidation)
void Server_ReportOwnerRotation(const FRotator& NewRotation);

.cpp:

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

	// Value is already updated locally, skip in replication step
	DOREPLIFETIME_CONDITION(ABaseCharacter, bIsAiming, COND_SkipOwner);
	DOREPLIFETIME_CONDITION(ABaseCharacter, OwnerRotation, COND_SkipOwner);	
}

void ABaseCharacter::SetRotation(FRotator rot)
{
	OwnerRotation = rot;
	OnRep_OwnerRotation();

	if (!HasAuthority() && IsLocallyControlled())
	{
		Server_ReportClientRotation(rot);
	}
}

bool ABaseCharacter::Server_ReportClientRotationovement_Validate(const FRotator& NewRotation)
{
	// reject implausible rotations by returning false
	return true;
}
void ABaseCharacter::Server_ReportClientRotation_Implementation(const FRotator& NewRotation)
{
	// can also perform other validation here as well (e.g. prevent remote client from doing something obviously impossible), since this runs on server
	OwnerRotation = NewRotation;
	OnRep_OwnerRotation();
}

void ABaseCharacter::OnRep_OwnerRotation()
{
	// if you want to interpolate/smoothly animate on remote clients, comment this out
	SetActorRotation(OwnerRotation);
}

bonus points: non-owner instances of the game could interpolate to smooth out the animation (note that RInterpTo is not frame-rate independent. you would want to use a fixed time-step function plus interpolation or extrapolation to get perfect frame-rate independence, but this example should be good enough to get you started)

void ABaseCharacter::Tick(float DeltaTime)
{
	...

	if (!IsLocallyControlled())
	{
		SetActorRotation(FMath::RInterpTo(GetActorRotation(), OwnerRotation, DeltaTime, 0.5f));
	}

	...
}

Hey,

Thank you very much for answering! I still do have some questions and problems. First of all what exactly does

UPROPERTY(ReplicatedUsing = OnRep_OwnerRotation)

I was just aware of using

UPROPERTY(Transient, Replicated)

Do I understand the chain of events right, when I say that:

  1. Client calls “setRotation”.
  2. Client is not the server therefor
    tells the server to handle this
    problem
  3. The server executes the function:
    sets the rotation, and then tells
    every client to call
    “onRep_OwnerRotation()”?

Where does the server tell all the other clients that ClientXYZ is now looking in the direction A?
Isn’t there the reference to this specific actor missing? Something like this:
http://puu.sh/jJ7yg/5d87695742.jpg ?

My Problem is that this code obviously compiles but it’s not achieving the desired effect, unfortunately. It’s still the case that, when Client_1 rotates, he rotates locally (by calling “onRep_OwnerRotation()”), then tells the server about its change in rotation. The server does display the rotation movement of Client_1 correctly but Client_2 does not see any change in motion.

pic: http://puu.sh/jJ8gw/4ea097d432.jpg

Sorry for any inconvenience

~Theo

It’s still the case that, when Client_1 rotates, he rotates locally (by calling “onRep_OwnerRotation()”), then tells the server about its change in rotation. The server does display the rotation movement of Client_1 correctly but Client_2 does not see any change in motion.

I’ve just checked: The Client_2 has set the OwnerRotation to (0,0,0) which obviously means it doesn’t get updated on this client.

Any further help? :slight_smile:

Update:

Okay I got it working and Cancel was absolutely right (marking your answer as the solution now).

There is just one slight adjustment which has to be made in order for it to work properly:

void ABaseCharacter::SetRotation(FRotator rot)
{
	OwnerRotation = rot;

	if (!HasAuthority() && IsLocallyControlled())
	{
		Server_ReportOwnerRotation(rot);
		OnRep_OwnerRotation();
	}
}

Do not call OnRep_OwnerRotation() or SetActorLocation before this if statement. Otherwise it will f*** everything up :smiley:

Thank you very much canel!