Hi,
I want to simply replicate the rotation of the third person character in a planet scene, changing the gravity when it is needed.
[PlanetGravity.cpp] → What I did was creating a script for the planet gravity, where it calculates the surface normal, and compares it to the current normal of the character and changes the gravity direction.
[PlanetUniversMediepolysCharacter.cpp] This script basically calculates the gravity of the character every tick, and sets it to the CharacterMovementComponent->SetGravityDirection(NewGravity), after that I set the calculated gravity direction to the currentGravityDirection (named as medielerGravityDirection) of the PUMC script
The problem is that the client does not read the replicated rotations from other clients, but the server can see the rotations for all the clients…
Some of the Logs I registered, but I do not understand what it is happening:
LogTemp: Warning: BP_ThirdPersonCharacter_C_2: Role = Server (Authority)
LogTemp: Warning: BP_ThirdPersonCharacter_C_2: Role = Client (Simulated)
LogTemp: Warning: BP_ThirdPersonCharacter_C_0: Role = Client (Owning Player)
LogTemp: Warning: BP_ThirdPersonCharacter_C_1: Role = Client (Simulated)
LogTemp: Warning: BP_ThirdPersonCharacter_C_2: Role = Client (Simulated)
LogTemp: Warning: Client (BP_ThirdPersonCharacter_C_1):attempted to update gravity for a character it does NOT own!
…
LogTemp: Warning: Surface Direction: X=-4085.765 Y=-3917.150 Z=-7664.812
LogTemp: Warning: Client (Client (Owning Player)): Sending gravity update to the server.
LogBlueprintUserMessages: [MyPlanet_C_UAID_D843AE2CBE806C0E02_1561671289] Server: Entra gravetat!
LogTemp: Warning: Client (BP_ThirdPersonCharacter_C_2):attempted to update gravity for a character it does NOT own!
#include "PlanetUniversMediepolysCharacter.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"
#include "Net/UnrealNetwork.h"
APlanetUniversMediepolysCharacter::APlanetUniversMediepolysCharacter()
{
Super::InitializeParamaters();
PrimaryActorTick.bCanEverTick = true;
planetGravity = CreateDefaultSubobject<UPlanetGravity>(TEXT("PlanetGravity"));
Camera configurations...
[...]
bReplicates = true;
SetReplicates(true);
SetReplicateMovement(true);
}
void APlanetUniversMediepolysCharacter::BeginPlay()
{
Super::BeginPlay();
switch (GetLocalRole())
{
case ROLE_Authority:
RoleString = "Server (Authority)";
break;
case ROLE_AutonomousProxy:
RoleString = "Client (Owning Player)";
break;
case ROLE_SimulatedProxy:
RoleString = "Client (Simulated)";
break;
default:
RoleString = "Unknown Role";
}
UE_LOG(LogTemp, Warning, TEXT("%s: Role = %s"), *GetName(), *RoleString);
SphericMovement = Cast<USphericalMovementComponent>(GetCharacterMovement());
SphericMovement->SetIsReplicated(true);
}
void APlanetUniversMediepolysCharacter::UpdateCameraLook()
{
FRotator PlayerRotation = CameraBoom->GetRelativeRotation();
//clamp PlayerRotation.Yaw value to 40 degrees
PlayerRotation.Pitch += LookAxisVector.Y;
PlayerRotation.Pitch = FMath::Clamp(PlayerRotation.Pitch, -40.0f, 40.0f);
PlayerRotation.Yaw += LookAxisVector.X;
// Apply the rotation to the CameraBoom
CameraBoom->SetRelativeRotation(PlayerRotation);
CameraBoom->SetWorldLocation(GetActorLocation());
AlignedToActor();
}
void APlanetUniversMediepolysCharacter::AlignedToActor()
{
if (planetGravity->GetGravityEnabled())
{
FVector CharacterLocation = GetActorLocation();
FVector UpDirection = (planetGravity->GetPlanetCenter() - CharacterLocation).GetSafeNormal();
// Step 2: Adjust the camera boom's rotation
// Get the forward direction of the character (local X-axis)
FVector CamForward = CameraRoot->GetForwardVector();
// Create a rotation with the forward vector and the up direction
FRotator DesiredRotation = FRotationMatrix::MakeFromZX(-UpDirection, CamForward).Rotator();
// Smoothly interpolate to the desired rotation for a natural effect
FRotator SmoothedRotation = FMath::RInterpTo(
CameraRoot->GetComponentRotation(), // Current rotation
DesiredRotation, // Target rotation
GetWorld()->GetDeltaSeconds(), // Delta time
2.0f // Interpolation speed
);
CameraRoot->SetWorldRotation(SmoothedRotation);
}
}
void APlanetUniversMediepolysCharacter::Move(const FInputActionValue& Value)
{
FVector2D MovementVector = Value.Get<FVector2D>();
FVector CalculateUpVector;
if (!planetGravity->GetGravityEnabled()) {
CalculateUpVector = GetActorUpVector();
}
else {
CalculateUpVector = (GetActorLocation() - planetGravity->GetPlanetCenter()).GetSafeNormal();
}
// Get the camera's forward vector
FVector CameraForward = FollowCamera->GetForwardVector();
FVector ForwardDirection = FVector::VectorPlaneProject(CameraForward, CalculateUpVector).GetSafeNormal();
// Move the character forward direction relative to the camera
AddMovementInput(ForwardDirection, MovementVector.Y);
// Get the camera's right vector
FVector CameraRight = FollowCamera->GetRightVector();
FVector RightDirection = FVector::VectorPlaneProject(CameraRight, CalculateUpVector).GetSafeNormal();
// Move the character relative to the camera
AddMovementInput(RightDirection, MovementVector.X);
}
void APlanetUniversMediepolysCharacter::AddAdditionalInputs()
{
}
void APlanetUniversMediepolysCharacter::CalculateGravity()
{
if (IsValid(planetGravity)) {
// Ensure the gravity is only applied if the component is active
if (planetGravity->GetGravityEnabled())
{
if (planetGravity->ApplyGravity())
{
SphericMovement->UpdateGravityDirection(planetGravity->GetNewGravityDirection());
if (GetLocalRole() == ROLE_AutonomousProxy && !HasAuthority())
{
UE_LOG(LogTemp, Warning, TEXT("Client (%s): Sending gravity update to the server."), *RoleString);
//Server_SendGravityUpdate(planetGravity->GetNewGravityDirection());
Server_SendGravityUpdateToServer(planetGravity->GetNewGravityDirection());
}
}
}
}
}
/********************/
/* REPLICATED PART */
/********************/
void APlanetUniversMediepolysCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
//if (IsLocallyControlled()) //Only calculate locally
if (HasAuthority() || (IsLocallyControlled() && GetLocalRole() == ROLE_AutonomousProxy))
{
CalculateGravity();
}
}
void APlanetUniversMediepolysCharacter::AssignPlanetGravityToPlayer()
{
medielerGravityDirection = planetGravity->GetNewGravityDirection();
}
// Enable replication for GravityDirection
void APlanetUniversMediepolysCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(APlanetUniversMediepolysCharacter, medielerGravityDirection);
}
void APlanetUniversMediepolysCharacter::Server_SendGravityUpdateToServer_Implementation(const FVector& NewGravityDirection)
{
UE_LOG(LogTemp, Warning, TEXT("Server: Received gravity update from client."));
AssignPlanetGravityToPlayer();
OnRep_GravityDirection();
}
bool APlanetUniversMediepolysCharacter::Server_SendGravityUpdateToServer_Validate(const FVector& NewGravityDirection)
{
if (!IsLocallyControlled())
{
UE_LOG(LogTemp, Warning, TEXT("Client (%s):attempted to update gravity for a character it does NOT own!"), *GetName());
}
return true;
}
// Called when replicated gravity direction changes
void APlanetUniversMediepolysCharacter::OnRep_GravityDirection()
{
UE_LOG(LogTemp, Warning, TEXT("Client(%S): Received replicated gravity direction update: %s"), *RoleString, *medielerGravityDirection.ToString());
CalculateGravity();
}
And the header file:
#pragma once
#include "Net/UnrealNetwork.h"
#include "Engine/Engine.h"
#include "CoreMinimal.h"
#include "TimerManager.h"
#include "SphericalMovementComponent.h"
#include "UniversMediepolysCharacter.h"
#include "PlanetUniversMediepolysCharacter.generated.h"
UCLASS()
class UNIVERSMEDIEPOLYS_API APlanetUniversMediepolysCharacter : public AUniversMediepolysCharacter
{
GENERATED_BODY()
APlanetUniversMediepolysCharacter();
public:
protected:
// To add mapping context
virtual void BeginPlay();
/** Camera boom positioning the camera behind the character */
UPROPERTY(VisibleAnywhere, Category = Camera, meta = (AllowPrivateAccess = "true"))
USpringArmComponent* CameraBoom;
/** Follow camera */
UPROPERTY(VisibleAnywhere, Category = Camera, meta = (AllowPrivateAccess = "true"))
UCameraComponent* FollowCamera;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
USceneComponent* CameraRoot;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "PlanetController")
APlayerController* PController;
USphericalMovementComponent* SphericMovement;
UFUNCTION(Blueprintcallable)
void UpdateCameraLook();
UFUNCTION(Blueprintcallable)
void AlignedToActor();
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UPlanetGravity* planetGravity;
void AddAdditionalInputs() override;
public:
UPROPERTY()
FString RoleString;
void Move(const FInputActionValue& Value) override;
/** Returns CameraBoom subobject **/
FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
/** Returns FollowCamera subobject **/
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
void CalculateGravity();
virtual void Tick(float DeltaTime) override;
UFUNCTION(Blueprintcallable)
void AssignPlanetGravityToPlayer();
#pragma region ReplicationFunctions
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const;
UFUNCTION(Server, Reliable, WithValidation)
void Server_SendGravityUpdateToServer(const FVector& NewGravityDirection);
UFUNCTION()
void OnRep_GravityDirection(); // Called when GravityDirection is updated
UPROPERTY(Editanywhere, ReplicatedUsing = OnRep_GravityDirection, Category = "PlanetController")
FVector medielerGravityDirection;
#pragma endregion
};
And this some images of what happens: