Character Rotation Replication in a Planet surface

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:

Hey @RastaCat33 !

I check you code.
I would recommend not calculate the gravity in each client.
Just calculate it in the server or in the server and wait for the OnRep is calling.

Avoid using RPC if is possible.

In the function calculate gravity do you know if is being called on client?

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());
                }

            }
        }
    }
}

Are those if valid in client?

  • IsValid(planetGravity)
  • planetGravity->GetGravityEnabled()
  • planetGravity->ApplyGravity()

Did you try to remove all related to replication and calculate in each client?

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

    //Remove this if
    if (HasAuthority() || (IsLocallyControlled() && GetLocalRole() == ROLE_AutonomousProxy))
    {
        
        CalculateGravity();
    }
}

This will help you to find the issue :smiley:
Let me know if that help you to find the issue. If not I can help you go a lither further.

Will try as soon as I get home to test it.

Yes, those ‘IsValid’ , ‘planetGravity->GetGravityEnabled()’ … are executing in the client side, and what I wanted to try is to release the task of calculating the gravity from the server and giving this task to the clients.

I thought one solution was to change the Role of the client, because it was set as ‘Simulation’ automatically when starting the game. Don’t know if there is a solution by setting the local role as autonomus_proxy or something like that…

Will try simplify this solution and post the updates I get

Thx so much for the quick answer

Ok, I made some progress, but still not getting the desired behaviour.

The clients can see the other players aligned to the surface planet while idle , but it gets the Zaxis up vector and not calculating the surface planet vector aligned…

I think i am about to get it, but still need to fully understand how it works. Do you know why I am no replicating the rotation of the characters? I read that replicating the rotation of the controller would replicate automatically, I tried but it didn’t worked…

I post the code again:

#include "PlanetUniversMediepolysCharacter.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"		
#include "Net/UnrealNetwork.h"
#include "Engine/Engine.h"

APlanetUniversMediepolysCharacter::APlanetUniversMediepolysCharacter()
{
    Super::InitializeParamaters();

    PrimaryActorTick.bCanEverTick = true;

    CameraRoot = CreateDefaultSubobject<USceneComponent>(TEXT("CameraRoot"));
    CameraRoot->SetupAttachment(RootComponent);

    // Create a camera boom (pulls in towards the player if there is a collision)
    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    CameraBoom->SetupAttachment(CameraRoot);
    CameraBoom->TargetArmLength = 400.0f; // The camera follows at this distance behind the character	
    CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller

    // Create a follow camera
    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
    FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
    
    SetReplicates(true);
    SetReplicateMovement(true);

    moveComp = GetCharacterMovement();
    moveComp->SetIsReplicated(true);
}

void APlanetUniversMediepolysCharacter::BeginPlay()
{
    Super::BeginPlay();

    if (!planetGravity)
    {
        planetGravity = GetComponentByClass<UPlanetGravity>();
    }

    if (planetGravity)
    {
        planetGravity->SetIsReplicated(true);
    }
    else
    {
        UE_LOG(LogTemp, Error, TEXT("planetGravity is NULL when trying to replicate or register it."));
    }

    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);

}


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 == nullptr) return;

    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::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if (!planetGravity) return;

    // All clients and server calculate gravity continuously
    CalculateGravity();

    if (HasAuthority())
    {
        // Server maintains authoritative rotation
        FRotator TargetRotation = CalculateRotationBySurface();
        SetCharacterGravityRotation(TargetRotation);
    }
    else
    {
        // Clients use local prediction when server isn't available
        SmoothAlignToGravity(DeltaTime);

        // Apply smoothing to replicated rotations when available
        if (!ReplicatedCharacterRotation.IsZero())
        {
            SmoothRotationApplied();
        }
    }
}

// Enable replication for GravityDirection
void APlanetUniversMediepolysCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    //DOREPLIFETIME(APlanetUniversMediepolysCharacter, medielerGravityDirection);
    DOREPLIFETIME(APlanetUniversMediepolysCharacter, planetGravity);
    DOREPLIFETIME(APlanetUniversMediepolysCharacter, ReplicatedCharacterRotation);
}

void APlanetUniversMediepolysCharacter::CalculateGravity()
{
    if (!planetGravity) return;

    if (planetGravity->GetGravityEnabled())
    {
        if (planetGravity->ApplyGravity(GetActorLocation()))
        {
            medielerGravityDirection = planetGravity->GetNewGravityDirection();
            moveComp->SetGravityDirection(planetGravity->GetSurfaceNormal());
        }
    }
}


/** Called when entering a planet's gravity field */
void APlanetUniversMediepolysCharacter::OnEnterGravityField(UPlanetGravity* PlanetComponent)
{
    if (planetGravity != PlanetComponent)
    {
        planetGravity = PlanetComponent;
        OnRep_GravityChange();
    }
}

/** Called when exiting a planet's gravity field */
void APlanetUniversMediepolysCharacter::OnExitGravityField(UPlanetGravity* PlanetComponent)
{
    if (HasAuthority() && planetGravity == PlanetComponent)
    {
        planetGravity = nullptr;
        OnRep_GravityChange();
    }
}

void APlanetUniversMediepolysCharacter::OnRep_GravityChange()
{
    moveComp = GetCharacterMovement();

    CalculateGravity();

    //log the Surface Direction
    UE_LOG(LogTemp, Warning, TEXT("Client=>{%s} Role=>(%s), nova gravetat canviada: [%s] "), *GetName(), *RoleString, *medielerGravityDirection.ToString());

}

/** Smoothly aligns the character to the gravity direction */
FRotator APlanetUniversMediepolysCharacter::CalculateRotationBySurface()
{
    if (!planetGravity->CurrentPlanetAttached)
    {
        return FRotator::ZeroRotator;
    }

    FVector PlanetCenter = planetGravity->CurrentPlanetAttached->GetPlanetCenter();
    FVector GravityDirection = (GetActorLocation() - PlanetCenter).GetSafeNormal();

    return FRotationMatrix::MakeFromZX(GravityDirection, GetActorForwardVector()).Rotator();
}

/** Smoothly aligns the character to the gravity direction */
void APlanetUniversMediepolysCharacter::SmoothAlignToGravity(float DeltaTime)
{
    if (!planetGravity->CurrentPlanetAttached) return;

	FRotator TargetRotation = CalculateRotationBySurface();

    if (TargetRotation != FRotator::ZeroRotator)
    {
        SetCharacterGravityRotation(TargetRotation);
    }
}

void APlanetUniversMediepolysCharacter::SetCharacterGravityRotation(FRotator NewRotation)
{
    if (!HasAuthority()) return;

    ReplicatedCharacterRotation = NewRotation;
    SmoothRotationApplied();
    OnRep_CharacterRotation();     // Call manually on server
}

void APlanetUniversMediepolysCharacter::OnRep_CharacterRotation()
{
    // Apply to all remote clients (including the owning client when not authoritative)
    if (HasAuthority()) return;
    
        UE_LOG(LogTemp, Warning, TEXT("[%s] OnRep Rotation: %s"), *GetName(), *ReplicatedCharacterRotation.ToString());
        SmoothRotationApplied();
}

void APlanetUniversMediepolysCharacter::SmoothRotationApplied()
{

    FRotator CurrentRot = GetActorRotation();
    FRotator Smoothed = FMath::RInterpTo(CurrentRot, ReplicatedCharacterRotation, GetWorld()->DeltaTimeSeconds, 1.0f);
    SetActorRotation(Smoothed);
}

and the header file:


#pragma once

#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()

public:

	APlanetUniversMediepolysCharacter();

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;

	UCharacterMovementComponent* moveComp;


	UFUNCTION(BlueprintCallable)
	void UpdateCameraLook(); 

	UFUNCTION(BlueprintCallable)
	void AlignedToActor();

	UPROPERTY(ReplicatedUsing = OnRep_GravityChange)
	UPlanetGravity* planetGravity;

	UPROPERTY(ReplicatedUsing = OnRep_CharacterRotation)
	FRotator ReplicatedCharacterRotation;

	void AddAdditionalInputs() override;

public:

	UFUNCTION(BlueprintCallable)
	void SetPlanetGravity(UPlanetGravity* _planetGravity) { planetGravity = _planetGravity; }

	UFUNCTION(BlueprintCallable)
	UPlanetGravity* GetPlanetGravity() { return planetGravity; }

	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; }

	virtual void Tick(float DeltaTime) override;

#pragma region ReplicationFunctions

	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const;

	UPROPERTY(EditDefaultsOnly)
	FVector medielerGravityDirection;

	/** RepNotify function - Called when GravityDirection changes */
	UFUNCTION()
	void OnRep_GravityChange();

	UFUNCTION()
	FRotator CalculateRotationBySurface();

	UFUNCTION()
	void OnRep_CharacterRotation();

	void SmoothRotationApplied();

	UFUNCTION()
	void SmoothAlignToGravity(float DeltaTime);

	UFUNCTION()
	void SetCharacterGravityRotation(FRotator NewRotation);

	/** Function to calculate gravity locally */
	UFUNCTION()
	void CalculateGravity();

	UFUNCTION(BlueprintCallable)
	void OnEnterGravityField(UPlanetGravity* PlanetComponent);
	UFUNCTION(BlueprintCallable)
	void OnExitGravityField(UPlanetGravity* PlanetComponent);

#pragma endregion

};