Character Rotation Replication in a Planet surface

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

};