Multiplayer mesh rotation issues

I am currently working on a multiplayer game that can have up to 8 players.
It is top down view and the player has to look at the mouse position at all times.
I am working from a listen server point of view.
When the server rotates it rotates all the clients as well but on Client 1 the rotation of Client 1 is not changed and on Client 2 the rotation of Client 2 is not changed.
Client 1 rotation is normal and replicated fine to other clients and server.
Client 2 rotation is normal and replicated fine to other clients and server.

This is my code:


ASaltPlayerController* MyPC = Cast<ASaltPlayerController>(GetController());
	if (MyPC)
	{
		FPlane Plane = FPlane(0.f, 0.f, 1.f, 0.f);
		FVector WorldLocation;
		FVector WorldDirection;
		float t;
		FVector IntersectionPoint;

		MyPC->DeprojectMousePositionToWorld(WorldLocation, WorldDirection);
		WorldDirection = WorldDirection * 100000;

		if (UKismetMathLibrary::LinePlaneIntersection(WorldLocation, WorldLocation + WorldDirection, Plane, t, IntersectionPoint))
		{
			FVector dir = (IntersectionPoint - GetActorLocation()).GetSafeNormal();
			DirectionFaced = FVector(dir.X, dir.Y, 0.f);
			FRotator LookAtRot = UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), IntersectionPoint);
			FRotator NewRot = FRotator(0.f, LookAtRot.Yaw, 0.f);
			SetActorRotation(NewRot);
		}
	}

	if (Role < ROLE_Authority)
	{
		ServerRotateTowardsMouse(GetActorRotation());
	}
	else
	{
		RotateTowardsMouse(GetActorRotation());
	}


void ASaltCharacter::RotateTowardsMouse_Implementation(FRotator Rotation)
{
	SetActorRotation(Rotation);
}

void ASaltCharacter::ServerRotateTowardsMouse_Implementation(FRotator Rotation)
{
	RotateTowardsMouse(Rotation);
}

bool ASaltCharacter::ServerRotateTowardsMouse_Validate(FRotator Rotation)
{
	return true;
}

Your code seems to be working well.

Rather than use RPCs for all those messages, you should just replicate an FRotator (call it ServerRotation or some such) and just watch changes to that.

If your RPC’s aren’t marked as reliable, they could be getting dropped which could cause the behavior you’re seeing.

What do you mean with that?
I have to update my rotation through the server client unless I am the server client right?
So how is 1 variable going to do that? I have to recalculate that variable for each different client from the server side and only update the clients rotation when the server changes my variable?

Send the server the Mouse positions, have the server update the locator as a replicated variable.




UClass()
class AMouseActor : public AActor
{
   //...

   UPROPERTY(Replicated, ReplicatedUsing="OnServerRotationUpdated")
   FRotator ServerRotation;

   void OnServerRotationUpdated()  { SetActorRotation(ServerRotation); }
} 

For the locally controlled client, you probably want to make your own FRotator that you act on immediately and just occasionally watch the server version to make sure they’re not too far out of sync.

Okay so I switched to your solution and nothing has changed.

If the mouse is active in the server client the following things happen:
Server client -> All clients including the server client rotate towards the mouse position.
Every other client -> See’s all the characters rotate except their own.

If the mouse is active in any other client that is not the server:
Server client -> Nothing happens all characters are not rotating.
Active client -> Character rotates correctly towards the mouse position.
Other clients -> Nothing happens all characters are not rotating.

.h file


// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "SaltCharacter.generated.h"

UCLASS()
class SALT_API ASaltCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	ASaltCharacter();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Camera)
		class UCameraComponent* CameraComponent;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Camera)
		class USpringArmComponent* CameraBoom;

	// Saved player direction
	FVector DirectionFaced;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	void MoveForward(float Val);
	void MoveRight(float Val);
	
private:
	UPROPERTY(Replicated, ReplicatedUsing=OnRep_SetServerRotation)
	FRotator R_ServerRotation;

	UFUNCTION()
	void OnRep_SetServerRotation();

};


.cpp file


// Fill out your copyright notice in the Description page of Project Settings.

#include "SaltCharacter.h"
#include "Player/SaltPlayerController.h"

#include "Net/UnrealNetwork.h"
#include "Kismet/KismetMathLibrary.h"
#include "Runtime/Engine/Classes/GameFramework/SpringArmComponent.h"
#include "Runtime/Engine/Classes/Camera/CameraComponent.h"

// Sets default values
ASaltCharacter::ASaltCharacter()
{
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(GetRootComponent());
	CameraBoom->bAbsoluteRotation = true;
	CameraBoom->TargetArmLength = 1200.f;
	CameraBoom->RelativeRotation = FRotator(-60.f, 0.f, 0.f);
	CameraBoom->bDoCollisionTest = false;

	CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
	CameraComponent->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
	CameraComponent->bUsePawnControlRotation = false;

	GetMesh()->bOnlyOwnerSee = false;
	GetMesh()->bOwnerNoSee = false;
	GetMesh()->bReceivesDecals = false;
	GetMesh()->SetCollisionObjectType(ECC_Pawn);
	GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	GetMesh()->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);

 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void ASaltCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	ASaltPlayerController* MyPC = Cast<ASaltPlayerController>(GetController());
	if (MyPC)
	{
		MyPC->bShowMouseCursor = true;
		MyPC->bEnableClickEvents = true;
		MyPC->bEnableMouseOverEvents = true;
	}
}

// Called every frame
void ASaltCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	ASaltPlayerController* MyPC = Cast<ASaltPlayerController>(GetController());
	if (MyPC)
	{
		FPlane Plane = FPlane(0.f, 0.f, 1.f, 0.f);
		FVector WorldLocation;
		FVector WorldDirection;
		float t;
		FVector IntersectionPoint;

		MyPC->DeprojectMousePositionToWorld(WorldLocation, WorldDirection);
		WorldDirection = WorldDirection * 100000;

		if (UKismetMathLibrary::LinePlaneIntersection(WorldLocation, WorldLocation + WorldDirection, Plane, t, IntersectionPoint))
		{
			FVector dir = (IntersectionPoint - GetActorLocation()).GetSafeNormal();
			DirectionFaced = FVector(dir.X, dir.Y, 0.f);
			FRotator LookAtRot = UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), IntersectionPoint);
			FRotator NewRot = FRotator(0.f, LookAtRot.Yaw, 0.f);
			R_ServerRotation = NewRot;
			SetActorRotation(NewRot);
		}
	}
}

// Called to bind functionality to input
void ASaltCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	PlayerInputComponent->BindAxis("MoveForward", this, &ASaltCharacter::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &ASaltCharacter::MoveRight);
}

void ASaltCharacter::MoveForward(float Val)
{
	FVector Direction = FRotationMatrix(GetController()->GetControlRotation()).GetScaledAxis(EAxis::X);
	AddMovementInput(Direction, Val);
}

void ASaltCharacter::MoveRight(float Val)
{
	FVector Direction = FRotationMatrix(GetController()->GetControlRotation()).GetScaledAxis(EAxis::Y);
	AddMovementInput(Direction, Val);
}

void ASaltCharacter::OnRep_SetServerRotation()
{
	SetActorRotation(R_ServerRotation);
}

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

	DOREPLIFETIME(ASaltCharacter, R_ServerRotation);
}

You’re not sending the info to the server via RPC. Your flow needs to look this like:

Client Sends Mouse Position to Server via RPC -> Server updates R_ServerRotator -> Client applies R_ServerRotator when its notified it changed.

Yeah you are right I forgot to do that.
The only thing left now is that the server doesn’t rotate at all and I understand why because I am doing things if the client does not have Authority but I can’t seem to figure out how to do it on the server.
I tried multiple things but every time I end up with the server rotating all the characters again.

If the mouse is active in the server client the following things happen:
Server client -> All clients including the server client rotate towards the mouse position.
Every other client -> See’s all the characters rotate except their own.

If the mouse is active in any other client that is not the server:
Server client -> The active client character gets rotated correctly
Active client -> Character rotates correctly towards the mouse position.
Other clients -> The active client character gets rotated correctly


// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "SaltCharacter.generated.h"

UCLASS()
class SALT_API ASaltCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	ASaltCharacter();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Camera)
		class UCameraComponent* CameraComponent;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Camera)
		class USpringArmComponent* CameraBoom;

	// Saved player direction
	FVector DirectionFaced;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	void MoveForward(float Val);
	void MoveRight(float Val);
	
private:
	UPROPERTY(Replicated, ReplicatedUsing=OnRep_SetServerRotation)
	FRotator R_ServerRotation;

	UFUNCTION()
	void OnRep_SetServerRotation();

	UFUNCTION(Server, Reliable, WithValidation)
	void Server_CalculateRotation(FVector WorldLocation, FVector WorldDirection, FVector ActorLocation);

};



// Fill out your copyright notice in the Description page of Project Settings.

#include "SaltCharacter.h"
#include "Player/SaltPlayerController.h"

#include "Engine/Engine.h"
#include "Net/UnrealNetwork.h"
#include "Kismet/KismetMathLibrary.h"
#include "Runtime/Engine/Classes/GameFramework/SpringArmComponent.h"
#include "Runtime/Engine/Classes/Camera/CameraComponent.h"

// Sets default values
ASaltCharacter::ASaltCharacter()
{
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(GetRootComponent());
	CameraBoom->bAbsoluteRotation = true;
	CameraBoom->TargetArmLength = 1200.f;
	CameraBoom->RelativeRotation = FRotator(-60.f, 0.f, 0.f);
	CameraBoom->bDoCollisionTest = false;

	CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
	CameraComponent->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
	CameraComponent->bUsePawnControlRotation = false;

	GetMesh()->bOnlyOwnerSee = false;
	GetMesh()->bOwnerNoSee = false;
	GetMesh()->bReceivesDecals = false;
	GetMesh()->SetCollisionObjectType(ECC_Pawn);
	GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
	GetMesh()->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);

 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void ASaltCharacter::BeginPlay()
{
	Super::BeginPlay();

	ASaltPlayerController* MyPC = Cast<ASaltPlayerController>(GetController());
	if (MyPC)
	{
		MyPC->bShowMouseCursor = true;
		MyPC->bEnableClickEvents = true;
		MyPC->bEnableMouseOverEvents = true;
	}
}

// Called every frame
void ASaltCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	ASaltPlayerController* MyPC = Cast<ASaltPlayerController>(GetController());
	if (MyPC)
	{
		FVector WorldLocation;
		FVector WorldDirection;
		FPlane Plane = FPlane(0.f, 0.f, 1.f, 0.f);
		float t;
		FVector IntersectionPoint;
		FVector ActorLocation = GetActorLocation();

		MyPC->DeprojectMousePositionToWorld(WorldLocation, WorldDirection);
		WorldDirection = WorldDirection * 100000;

		if (Role < ROLE_Authority)
		{
			if (UKismetMathLibrary::LinePlaneIntersection(WorldLocation, WorldLocation + WorldDirection, Plane, t, IntersectionPoint))
			{
				FVector dir = (IntersectionPoint - ActorLocation).GetSafeNormal();
				DirectionFaced = FVector(dir.X, dir.Y, 0.f);
				FRotator LookAtRot = UKismetMathLibrary::FindLookAtRotation(ActorLocation, IntersectionPoint);
				FRotator NewRot = FRotator(0.f, LookAtRot.Yaw, 0.f);
				SetActorRotation(NewRot);
			}

			Server_CalculateRotation(WorldLocation, WorldDirection, ActorLocation);
		}
	}
}

// Called to bind functionality to input
void ASaltCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	PlayerInputComponent->BindAxis("MoveForward", this, &ASaltCharacter::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &ASaltCharacter::MoveRight);
}

void ASaltCharacter::MoveForward(float Val)
{
	FVector Direction = FRotationMatrix(GetController()->GetControlRotation()).GetScaledAxis(EAxis::X);
	AddMovementInput(Direction, Val);
}

void ASaltCharacter::MoveRight(float Val)
{
	FVector Direction = FRotationMatrix(GetController()->GetControlRotation()).GetScaledAxis(EAxis::Y);
	AddMovementInput(Direction, Val);
}

void ASaltCharacter::OnRep_SetServerRotation()
{
	SetActorRotation(R_ServerRotation);
}

void ASaltCharacter::Server_CalculateRotation_Implementation(FVector WorldLocation, FVector WorldDirection, FVector ActorLocation)
{
	FPlane Plane = FPlane(0.f, 0.f, 1.f, 0.f);
	float t;
	FVector IntersectionPoint;

	if (UKismetMathLibrary::LinePlaneIntersection(WorldLocation, WorldLocation + WorldDirection, Plane, t, IntersectionPoint))
	{
		FVector dir = (IntersectionPoint - ActorLocation).GetSafeNormal();
		DirectionFaced = FVector(dir.X, dir.Y, 0.f);
		FRotator LookAtRot = UKismetMathLibrary::FindLookAtRotation(ActorLocation, IntersectionPoint);
		FRotator NewRot = FRotator(0.f, LookAtRot.Yaw, 0.f);
		R_ServerRotation = NewRot;
		OnRep_SetServerRotation();
	}
}

bool ASaltCharacter::Server_CalculateRotation_Validate(FVector WorldLocation, FVector WorldDirection, FVector ActorLocation)
{
	return true;
}

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

	DOREPLIFETIME(ASaltCharacter, R_ServerRotation);
}

Listen servers are special cases, you just have to treat them as such:



// Called every frame
void ASaltCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	ASaltPlayerController* MyPC = Cast<ASaltPlayerController>(GetController());
	if (MyPC)
	{
		FVector WorldLocation;
		FVector WorldDirection;
		FPlane Plane = FPlane(0.f, 0.f, 1.f, 0.f);
		float t;
		FVector IntersectionPoint;
		FVector ActorLocation = GetActorLocation();

		MyPC->DeprojectMousePositionToWorld(WorldLocation, WorldDirection);
		WorldDirection = WorldDirection * 100000;

		if (Role < ROLE_Authority)
		{
			if (UKismetMathLibrary::LinePlaneIntersection(WorldLocation, WorldLocation + WorldDirection, Plane, t, IntersectionPoint))
			{
				FVector dir = (IntersectionPoint - ActorLocation).GetSafeNormal();
				DirectionFaced = FVector(dir.X, dir.Y, 0.f);
				FRotator LookAtRot = UKismetMathLibrary::FindLookAtRotation(ActorLocation, IntersectionPoint);
				FRotator NewRot = FRotator(0.f, LookAtRot.Yaw, 0.f);
				SetActorRotation(NewRot);
			}

			Server_CalculateRotation(WorldLocation, WorldDirection, ActorLocation);
		}
                else if (Role == ROLE_Authority && IsLocallyControlled()) 
                {
                         // Listen Server, treat it as a client but without the RPC call. 
			if (UKismetMathLibrary::LinePlaneIntersection(WorldLocation, WorldLocation + WorldDirection, Plane, t, IntersectionPoint))
			{
				FVector dir = (IntersectionPoint - ActorLocation).GetSafeNormal();
				DirectionFaced = FVector(dir.X, dir.Y, 0.f);
				FRotator LookAtRot = UKismetMathLibrary::FindLookAtRotation(ActorLocation, IntersectionPoint);
				FRotator NewRot = FRotator(0.f, LookAtRot.Yaw, 0.f);
				SetActorRotation(NewRot);
			}
                 }
	}
}


Wow I tried to check for Role_Authority but completely forgot about the IsLocallyControlled()
Thank you so much.
Everything is working as I want it to.