Is there a way to retrieve a Rotator only on Pitch

Hello there !

I am making a tile based strategy game in 2D and I need to rotate my pawn to make it move on the targeted tile but only on pitch. For the example if my pawn moves on the right, I need the rotator to be (Pitch:180,Yaw:0,Roll:0) but I got (Pitch:0,Yaw:180,Roll :0).

I know that pitch rotations are limited between [-90,90] but there’s some workarounds that I wanted to test.

Cheers guys

When you say you “got (Pitch:0, Yaw:180, Roll:0)” - where did you get it?

You can certainly create an FRotator that only has pitch rotation:

FRotator MyPitchRotator = FRotator();
MyPitchRotator.Pitch = 90.0f;


//or

FRotator MyPitchRotator(90.0f, 0.0f, 0.0f);

Or you can retrieve the current rotation and retain only the Pitch value:

FRotator MyPitchRotation = MyPawn->GetActorRotation();
MyPitchRotation.Yaw = 0.0f;
MyPitchRotation.Roll = 0.0f;

Or, if you like, you can just retrieve and work with the pitch value itself:

float MyPitchRotation = MyPawn->GetActorRotation().Pitch;

If you are retrieving the rotation from a scene component (UStaticMesh etc.) within your Pawn, the rotation you receive is the actual component’s rotation. You may find that your Pawn’s local (relative) orientation is different from its world orientation (i.e. pitch, roll, and yaw may be different apparent axes is local space than they are in world space).

You can specify which space you are working with by using GetComponentRotation, SetComponentRotation, and AddWorldRotation vs. AddLocalRotation, AddRelativeRotation, and SetRelativeRotation (there is no GetLocal or GetRelativeRotation because you can access the RelativeRotation member variable of the component directly, MyComponent->RelativeRotation).

It might be easier for you to use Quaternions, rather than Eulers, especially if you’re trying to work around the 90± limits and gymbal lock. You can use all the same methods to pass Quats in, and it’s easier to constrain rotation to specific axes. The FQuat constructor has a version that allows to to give it an axis around which to rotate, and the amount of rotation to apply (in radians, not degrees). You can use it like this:

FQuat MyQuatRotation = FQuat(FVector::RightVector, FMath::DegreesToRadians(Degrees));

//or

FQuat MyQuatRotation(FVector::RightVector, FMath::DegreesToRadians(Degrees));

The first argument is the axis around which to rotate, you can enter any vector you like. Here I’m using FVector’s static RightVector value, which corresponds to rotation around Y, which is Pitch.

FMath::DegreesToRadians converts your Degrees value into a radians value, allowing you to continue your rotation calculation based on the often more intuitive degrees.

Once you have your quat, you can easily manipulate it as needed, and pass it to SetRelativeRotation etc. as you would an FRotator - but without any of the hassle of gymbal lock, etc.

You can also retrieve a quat from the returned values of GetActorRotation/GetComponentRotation, or from the MyComponent->RelativeRotation value:

FQuat MyActorRotation = MyPawn->GetActorRotation().Quaternion();

FQuat MyCompWorldRotation = MyComponent->GetComponentRotation().Quaternion();

FQuat MyCompRotation = MyComponent->RelativeRotation.Quaternion();

Basically, I tried to move my pawn along a spline to the targeted tile. So I retrieve the transform of my spline with the distance.

 FTransform NewTransform = Spline->GetTransformAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);	
FRotator DesiredRotation = NewTransform.Rotator();

The targeted tile is on the right of the pawn, so the easiest way to reach it in a 2D world is to make a rotation of (P:180,Y:0,R:0). But it gives me a rotation of (P:0,Y:180,R:0) that makes the pawn to turn around in a “3D way”.

These 2 rotators achieve the same result, and I wanted to know if there is a way to tell FRotator that I prefer the first method instead of what it is giving to me.

Use Quaternions.

Euler angles (Pitch, Yaw, Roll) are unpredictable when interpolating between two values.

Have you verified that using a rotator of (P:180, Y:0, R:0) has the same end result but different motion as (P:0, Y:180, R:0) ?

If you know the best way to rotate is with Pitch, you can override the value in the transform:

  FTransform NewTransform = Spline->GetTransformAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);    
NewTransform.SetRotation(FQuat(FVector::UpVector, FMath::DegreesToRadians(180.0f));

Transforms store their rotation values as Quaternions natively.

If you are working with only a set number of rotations you can prebuild the rotation quats and apply them as needed:

//in header:

static FQuat RotateRight;
static FQuat RotateLeft;

//in source
FQuat RotateRight(FVector::UpVector, FMath::DegreesToRadians(90.0f));
FQuat RotateLeft(FVector::UpVector, FMath::DegreesToRadians(-90.0f));
FQuat ReverseRight(FVector::UpVector, FMath::DegreesToRadians(180.0f));
FQuat ReverseLeft(FVector::UpVector, FMath::DegreesToRadians(-180.0f));


//within movement method
FTransform NewTransform = Spline->GetTransformAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);    
NewTransform.SetRotation(ReverseRight);

Then apply the transform to your pawn.

Alternatively, you can just get the location from the spline and build your own transform:

FVector NewLocation = Spline->GetLocationAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::World);    
FTransform NewTransform(ReverseRight, NewLocation - Pawn->GetActorLocation(), FVector(1.0f, 1.0f, 1.0f));

Thanks for all of these explanations. I went back to some basics code to create the FRotator for my tests.

FRotator UGridMovementComponent::ComputeRotator(const FVector &PawnLocation, const FVector &Target)
{
	FVector Delta = Target - PawnLocation;
	FRotator Result = FRotator::ZeroRotator;

	if (Delta.X > 0.0f)
	{
		Result.Pitch = 0.0f;
	}
	else if (Delta.X < 0.0f)
	{
		Result.Pitch = 180.0f;
	}

	if (Delta.Z > 0.0f)
	{
		Result.Pitch = -90.0f;
	}
	else if (Delta.Z < 0.0f)
	{
		Result.Pitch = 90.0f;
	}

	return Result;
}

I used this method in my movement function :

AActor *Owner = GetOwner();		
FTransform OldTransform=Owner->GetActorTransform();				
FTransform NewTransform = Spline->GetTransformAtDistanceAlongSpline(Distance,ESplineCoordinateSpace::Local);	

FRotator DesiredRotation=ComputeRotator(OldTransform.GetLocation(),NewTransform.GetLocation());

print("Desired " + DesiredRotation.ToString());   
NewTransform.SetRotation(FQuat(FVector::RightVector, FMath::DegreesToRadians(DesiredRotation.Pitch)));

print("Final " + NewTransform.Rotator().ToString());
Owner->SetActorTransform(NewTransform);

The rotation works but I now I got the Pitch Rotation issue . If I make a rotation (P:180,Y:0,R:0) it is transformed into (P:0,Y:180, R:0) after the DesiredRotator is converted into Quat

I will check the workarounds. Thanks :slight_smile:

You say the rotation works now, but that’s occurring at the last line in the code you posted:
Owner->SetActorTransform(NewTransform);

The change from Pitch:180 to Yaw:180 isn’t happening when you build the Quat, it’s happening when the Quat is converted into Euler:
print("Final " + NewTransform.Rotator().ToString());

If you ever get the FRotator from your FTransform and use it as a Euler you’re going to run into the rotation problems you’ve been having. You need to keep your calculations outside of Euler. You can do your calculations for each axis independently in degrees, but you can’t do them in combination as a euler set; that’s what gives you the undesired results.

You don’t need to keep it as part of an FRotator though, because you’re only working with the pitch; you can store it as a float and do all your degree calculations as float.

 float UGridMovementComponent::ComputePitch(const FVector &PawnLocation, const FVector &Target)
{
    FVector Delta = Target - PawnLocation;
    if (Delta.Z > 0.0f)
    {
        return -90.0f;
    }
    if (Delta.Z < 0.0f)
    {
        return 90.0f;
    }     
    if (Delta.X < 0.0f)
    {
        return 180.0f;
    }
    return 0.0f;
}

Then, in your movement method, I would say do this:

AActor *Owner = GetOwner();             
FVector NewLocation = Spline->GetLocationAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::World);    

 //if you need to keep your pitch value for later calculations, assign it to a member of your class (rather than a local variable, as I do here)
float DesiredPitch=ComputeRotator(Owner->GetActorLocation(), NewLocation);

Owner->SetActorTransform(FTransform(FQuat(FVector::RightVector, FMath::DegreesToRadians(DesiredPitch)), NewLocation, FVector(1.0f, 1.0f, 1.0f)));

Actually you’re right. DesiredRotation gives me (P:180,Y:0,R:0) and Final (P:0,Y:180,R:0). These 2 Rotators achieve the same result.

What I was trying to do before is to limit the rotation over time with a RotationSpeed property to get a smooth rotation animation.

Here’s the code :

FRotator UGridMovementComponent::LimitRotation(const FRotator &OldRotator, const FRotator &NewRotator, float DeltaSeconds)
{	
	FRotator Result = OldRotator;
	FRotator DeltaRotator = NewRotator - OldRotator;
	
	DeltaRotator.Normalize();	

	Result.Pitch += DeltaRotator.Pitch > 0 ? FMath::Min<float>(DeltaRotator.Pitch, MaxRotationSpeed * DeltaSeconds) :
		FMath::Max<float>(DeltaRotator.Pitch, MaxRotationSpeed * -DeltaSeconds);	
	
	return Result;
}

If I used this method, the problem stays the same, but I don’t know if I can achieve to make the smooth rotation animation with this weird Rotator behaviour.

Just work directly with the pitch value and interpolate that, instead of interpolating the FRotator. Don’t rely on retrieving the pitch from your existing FRotator, you can’t trust it. Instead, store the pitch value separately and express that rotation with a quaternion.

I’ll post what I mean in an answer below.

Within your header, add a float value to store your current pitch, and one to store your target pitch.

/** Desired pitch rotation after current movement is complete */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement")
float TargetPitch;

/** The current pitch rotation */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement")
float CurrentPitch;

In your movement methods you’ll want to do something like below:

float UGridMovement::DoMovement(const float &DeltaSeconds)
{
    //calculate your target location, travel spline, etc. as you have
    AActor *Owner = GetOwner();
    
    //I'm assuming you've already limited your Distance value based on a MaxTranslationSpeed and DeltaSeconds.
    FVector NewLocation = Spline->GetLocationAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::World);
    
    TargetPitch = ComputeTargetPitch(Owner->GetActorLocation(), NewLocation);

    CurrentPitch = LimitRotation(CurrentPitch, TargetPitch, DeltaSeconds);

    //perform the actual movement, I'll leave you to decide how to address collisions
    Owner->SetActorLocationAndRotation(NewLocation, FQuat(FVector::RightVector, FMath::DegreesToRadians(CurrentPitch)));

}

float UGridMovementComponent::ComputeTargetPitch(const FVector &PawnLocation, const FVector &Target)
{
    FVector Delta = Target - PawnLocation;
    if (Delta.Z > 0.0f)
    {
        return -90.0f;
    }
    if (Delta.Z < 0.0f)
    {
        return 90.0f;
    }     
    if (Delta.X < 0.0f)
    {
        return 180.0f;
    }
    return 0.0f;
}

float UGridMovementComponent::LimitRotation(const float &CurrentDegrees, const FRotator &TargetDegrees, const float &DeltaSeconds)
{   
    if (FMath::IsNearlyEqual(CurrentDegrees, TargetDegrees)) {
        return TargetDegrees;

    }
    float DeltaDegrees = TargetDegrees - CurrentDegrees;

    DeltaDegrees = FMath::Clamp(DeltaDegrees, MaxRotationSpeed * -DeltaSeconds, MaxRotationSpeed * DeltaSeconds);     
    return CurrentDegrees + DeltaDegrees;
}

It’s probably a good idea to separate your target calculations from the actual movement update, because you don’t need to update the TargetPitch each frame, just when a new movement is desired.

Since you only have four facing directions, you can prebuild your rotations as static values, this might save you some time.

In your header:

static FQuat FaceUp;
static FQuat FaceDown;
static FQuat FaceLeft;
static FQuat FaceRight;

In your source file:

FQuat FaceRight(FVector::RightVector, FMath::DegreesToRadians(0.0f));
FQuat FaceLeft(FVector::RightVector, FMath::DegreesToRadians(180.0f));
FQuat FaceUp(FVector::RightVector, FMath::DegreesToRadians(-90.0f));
FQuat FaceDown(FVector::RightVector, FMath::DegreesToRadians(90.0f));

These static values will only be calculated once for the class, then every actor instance you have can access them from anywhere via “UGridMovementComponent::FaceUp” etc. You can use them in your CalculateTargetPitch method as below:

float UGridMovementComponent::ComputeTargetPitch(const FVector &PawnLocation, const FVector &Target)
{
    FVector Delta = Target - PawnLocation;
    if (Delta.Z > 0.0f)
    {
        return FaceUp;
    }
    if (Delta.Z < 0.0f)
    {
        return FaceDown;
    }     
    if (Delta.X < 0.0f)
    {
        return FaceLeft;
    }
    return FaceRight;
}

Hey thanks, this is much more clear.

Rotation is almost fixed. I tweaked a little the code you gave to me with the help all your explanations. I got 2 problems now. First when the pawn moves up or down, it always faces the down direction. And the 2nd problem is that the LimitRotation method is not effective. The rotation seems to be instantly done. I checked everything I could but I am sure I missed something.

here’s the statics attributes :

FQuat UGridMovementComponent::FaceRight(FVector::RightVector, FMath::DegreesToRadians(0.0f));
FQuat UGridMovementComponent::FaceLeft(FVector::RightVector, FMath::DegreesToRadians(180.0f));
FQuat UGridMovementComponent::FaceUp(FVector::RightVector, FMath::DegreesToRadians(-90.0f));
FQuat UGridMovementComponent::FaceDown(FVector::RightVector, FMath::DegreesToRadians(90.0f));

The tweaked LimitRotation method :

FQuat UGridMovementComponent::LimitRotation(const FQuat &OldRotation, const FQuat &NewRotation, float DeltaSeconds)
{	
	float OldDegrees, NewDegrees;
	FVector OldVector,NewVector ;
	OldRotation.ToAxisAndAngle(OldVector, OldDegrees);
	NewRotation.ToAxisAndAngle(NewVector, NewDegrees);	

	if (FMath::IsNearlyEqual(OldDegrees, NewDegrees))
	{
		return FQuat(FVector::RightVector, NewDegrees);	
	}

	float DeltaDegrees = NewDegrees - OldDegrees;
	
	DeltaDegrees = FMath::Clamp(DeltaDegrees, MaxRotationSpeed * -DeltaSeconds, MaxRotationSpeed * DeltaSeconds);

	return FQuat(FVector::RightVector, OldDegrees+DeltaDegrees);
	
}

FQuat UGridMovementComponent::ComputeTargetPitch(const FVector &PawnLocation, const FVector &Target)
{
	FVector Delta = Target - PawnLocation;
	FRotator Result = FRotator::ZeroRotator;

	if (Delta.Z > 0.0f)
	{
		return FaceUp;
	}
	if (Delta.Z < 0.0f)
	{
		return FaceDown;
	}
	if (Delta.X < 0.0f)
	{
		return FaceLeft;
	}
	return FaceRight;
}

And now the moving method :

if (Moving)
	{		
		float CurrentSpeed = MaxWalkingSpeed*DeltaTime;				
		Distance = FMath::Min(Spline->GetSplineLength(), Distance + CurrentSpeed);		

		AActor *Owner = GetOwner();
		FVector OldLocation = Owner->GetActorLocation();
		FTransform NewTransform = Spline->GetTransformAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
		FVector NewLocation = NewTransform.GetLocation();

		TargetQuat = ComputeTargetPitch(OldLocation, NewLocation);
		CurrentQuat = LimitRotation(CurrentQuat, TargetQuat, DeltaTime);

		Owner->SetActorLocationAndRotation(NewLocation, CurrentQuat);
	
		if (Distance >= Spline->GetSplineLength())
		{			
			Moving = false;
			Distance = 0;
			Velocity = FVector::ZeroVector;
		}
		else
		{			
			Velocity = (NewLocation - OldLocation)*(1 / DeltaTime);
		}
		UpdateComponentVelocity();
	}

Thanks again to waste your time with me :slight_smile:

When you build your return FQuat in LimitRotation you’re doing this:

 return FQuat(FVector::RightVector, NewDegrees);

and this:

   return FQuat(FVector::RightVector, OldDegrees+DeltaDegrees);

That form of the constructor accepts the angle of rotation in radians, not degrees. Fix those with this:

 return FQuat(FVector::RightVector, FMath::DegreesToRadians(NewDegrees));

and this:

   return FQuat(FVector::RightVector, FMath::DegreesToRadians(OldDegrees+DeltaDegrees));

That might fix both your issues. But the always facing down problem might have to do with this in your movement method:

FVector OldLocation = Owner->GetActorLocation();
FTransform NewTransform = Spline->GetTransformAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
FVector NewLocation = NewTransform.GetLocation();

GetActorLocation returns the location in World Space, but you’re retrieving the Spline coordinates in local space. It may be that the Z location along your spline is always 0.0, so any positive Z starting location results in a negative Z transform.

Try this:

FVector OldLocation = Owner->GetActorLocation();
FTransform NewTransform = Spline->GetTransformAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::World);
FVector NewLocation = NewTransform.GetLocation();

Also, in your ComputeTargetPitch method, you create an FRotator you never use:

FRotator Result = FRotator::ZeroRotator;

Actually the problem with the Limit RotationMethod was the angle clamping. The angles returned by ToAxisAndAngle are in radians. So I converted the DeltaRadians in degrees then I clamped the value. Finally I recalculated the delta angle in radians. Here’s the code :

    FQuat UGridMovementComponent::LimitRotation(const FQuat &OldRotation, const FQuat &NewRotation, float DeltaSeconds)
    {	
    	float OldRadians, NewRadians;
    	FVector OldVector,NewVector ;
    	OldRotation.ToAxisAndAngle(OldVector, OldRadians);
    	NewRotation.ToAxisAndAngle(NewVector, NewRadians);	
    
    	if (FMath::IsNearlyEqual(OldRadians, NewRadians))
    	{    		
    		return FQuat(FVector::RightVector, NewRadians);
    	}
    
    	float DeltaRadians = NewRadians - OldRadians;
    	float DeltaDegrees = FMath::RadiansToDegrees(DeltaRadians);	
    	
    	DeltaDegrees = FMath::Clamp(DeltaDegrees, MaxRotationSpeed * -DeltaSeconds, MaxRotationSpeed * DeltaSeconds);
    	DeltaRadians = FMath::DegreesToRadians(DeltaDegrees);
    
    	return FQuat(FVector::RightVector,OldRadians+DeltaRadians);
    	
    }


For the facing down issue, I checked the ComputeTargetPitch method. It returned me the right FaceDown Quat so I am a little bit lost ^^ . 

All my spline points are created in Local Space. I tested to create them in World Space and the result is kinda messy :p 

I can resume you the creation process of the Spline Points. Each tile of my navigation grid has a pawn location offset which is in local space `(X:0.0f,Y:1.0f,Z:0.0f)` . It will be the location of the pawn when it will move on the selected tile.

bool UGridMovementComponent::CreatePath(UGridTileComponent &Target)
{	
	CurrentPath.Empty();
	Spline->ClearSplinePoints();
	AGridPawn *Pawn = Cast<AGridPawn>(GetOwner());
	CurrentTile = Grid->GetTile(Pawn->GetActorLocation());	
	if (CurrentTile != NULL )
	{
		TArray<UGridTileComponent*> Range;
		Grid->TilesInRange(CurrentTile, Range, Pawn, true);		
		if (Range.Contains(&Target))
		{			
			UGridTileComponent *Current = &Target;
			while (Current)
			{				
				CurrentPath.Add(Current);
				Current = Current->Backpointer;
			}		
			Algo::Reverse(CurrentPath);		

			Spline->AddSplinePoint(Pawn->GetActorLocation(), ESplineCoordinateSpace::Local);
			Spline->SetSplinePointType(0, ESplinePointType::Linear);
			for (int i = 1; i < CurrentPath.Num(); ++i)
			{
			
				CurrentPath[i]->AddSplinePoint(*Spline);
				Spline->SetSplinePointType(i, ESplinePointType::Linear);
				
			}			
			return true;
		}
	}		
	return false;
}

Here’s the tile function that add spline points with it’s pawn location offset.

void UGridTileComponent::AddSplinePoint(USplineComponent &Out)
{
	Out.AddSplinePoint(PawnLocation->GetComponentLocation(), ESplineCoordinateSpace::Local);
}

You don’t have to change how you create the spline, it can be made in local space because everything is relative to the pawn’s location. But the Actor location is always (0, 0, 0) in local space because the scene root is the origin of local space. Since you are trying to compare to the result of GetActorLocation you need to retrieve the Spline’s location in World space. You can’t compare a World space location to a local space location and expect reasonable results.

Try this, it should work:

 FVector OldLocation = Owner->GetActorLocation();
 FTransform NewTransform = Spline->GetTransformAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::World);
 FVector NewLocation = NewTransform.GetLocation();

But, if you’re building the Spline in the Pawn’s local space, don’t even bother getting the current actor location, because you know it will always be 0,0,0 in that local space. The Location retrieved from the DistanceAlongSpline is your local postion delta. Just use those vector values as they come back to you:

FVector NewLocation =  Spline->GetLocationAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
TargetQuat = ComputeTargetPitchLocal(NewLocation );


float UGridMovementComponent::ComputeTargetPitchLocal(const FVector &Target)
 {
     if (Target.Z > 0.0f)
     {
         return FaceUp;
     }
     if (Target.Z < 0.0f)
     {
         return FaceDown;
     }     
     if (Target.X < 0.0f)
     {
         return FaceLeft;
     }
     return FaceRight;
 }

I figured out what is the problem. The spline component seems to move randomly in the local space. I set its relative location to ZeroVector in the constructor but I believe each time I use Spline->ClearSplinePoints(); , its relative location changes

I will try to resolve this issue before trying your solutions

Hey I loged the rotation process and I found out something weird.

FQuat UGridMovementComponent::LimitRotation(const FQuat &OldRotation, const FQuat &NewRotation, float DeltaSeconds)
{	
	float OldRadians, NewRadians, OldDegrees, NewDegrees;
	FVector OldVector,NewVector;
	OldRotation.ToAxisAndAngle(OldVector, OldRadians);
	NewRotation.ToAxisAndAngle(NewVector, NewRadians);	
	OldDegrees = FMath::RadiansToDegrees(OldRadians);
	NewDegrees = FMath::RadiansToDegrees(NewRadians);	
	UE_LOG(LogTemp, Warning, TEXT("Old Degrees : %f"),OldDegrees);
	UE_LOG(LogTemp, Warning, TEXT("New Degrees asked : %f"), NewDegrees);
	if (FMath::IsNearlyEqual(OldRadians, NewRadians))
	{
		print("nearlyequal");
		return FQuat(FVector::RightVector, NewRadians);
	}

	float DeltaRadians = NewRadians - OldRadians;
	float DeltaDegrees = FMath::RadiansToDegrees(DeltaRadians);		
	
	UE_LOG(LogTemp, Warning, TEXT("Old Delta : %f"), DeltaDegrees);
	DeltaDegrees = FMath::Clamp(DeltaDegrees, MaxRotationSpeed * -DeltaSeconds, MaxRotationSpeed * DeltaSeconds);
	DeltaRadians = FMath::DegreesToRadians(DeltaDegrees);
	UE_LOG(LogTemp, Warning, TEXT("New Delta : %f"), DeltaDegrees);
	UE_LOG(LogTemp, Warning, TEXT("Degrees : %f"), OldDegrees + DeltaDegrees);

	return FQuat(FVector::RightVector,OldRadians+DeltaRadians);
	
}

On the beginning of the game, the pawn is facing the right direction. So its old degrees should be 0.0f, but it is 180.0f.

Another issue is that even if the pawn moves up or down and set its TargetRotation degrees is always equal to 90.0f. I checked the FQuat returned by the ComputeTargetPitch method, and it returned everytime the right FQuat asked , so up or down FQuat.

“On the beginning of the game, the pawn is facing the right direction. So its old degrees should be 0.0f, but it is 180.0f.”

Where are you getting the rotation value? Have you initialized your CurrentQuat variable?

"Another issue is that even if the pawn moves up or down and set its TargetRotation degrees is always equal to 90.0f. I checked the FQuat returned by the ComputeTargetPitch method, and it returned everytime the right FQuat asked , so up or down FQuat. "

That’s probably because you’re comparing a world space location to a local space location, like I’ve said multiple times.

Have you checked the input to ComputeTargetPitch?

“Where are you getting the rotation value?” I am getting it in the moving method. I logged everything.

“Have you initialized your CurrentQuat variable?” I have initialized it to 0.0f in the constructor.

There might be a problem with using the world location of the spline as it is a component inside the MovementComponent. The pawn seems to move infinitely.
As we used the transform of the spline to move and the spline world transform depends on the pawn location.

ComputeTargetPitch returns the right Quat. TargetRotation degrees is always equal to 90.0f in the LimitRotation method. I checked it here :

FQuat UGridMovementComponent::LimitRotation(const FQuat &OldRotation, const FQuat &NewRotation, float DeltaSeconds)
{	
	float OldRadians, NewRadians, OldDegrees, NewDegrees;
	FVector OldVector,NewVector;
	OldRotation.ToAxisAndAngle(OldVector, OldRadians);
	NewRotation.ToAxisAndAngle(NewVector, NewRadians);	
	OldDegrees = FMath::RadiansToDegrees(OldRadians);
	NewDegrees = FMath::RadiansToDegrees(NewRadians);	
	UE_LOG(LogTemp, Warning, TEXT("Old Degrees : %f"),OldDegrees);
	UE_LOG(LogTemp, Warning, TEXT("New Degrees asked : %f"), NewDegrees);
	if (FMath::IsNearlyEqual(OldRadians, NewRadians))
	{
		print("nearlyequal");
		return FQuat(FVector::RightVector, NewRadians);
	}

	float DeltaRadians = NewRadians - OldRadians;
	float DeltaDegrees = FMath::RadiansToDegrees(DeltaRadians);		
	
	UE_LOG(LogTemp, Warning, TEXT("Old Delta : %f"), DeltaDegrees);
	DeltaDegrees = FMath::Clamp(DeltaDegrees, MaxRotationSpeed * -DeltaSeconds, MaxRotationSpeed * DeltaSeconds);
	DeltaRadians = FMath::DegreesToRadians(DeltaDegrees);
	UE_LOG(LogTemp, Warning, TEXT("New Delta : %f"), DeltaDegrees);
	UE_LOG(LogTemp, Warning, TEXT("Degrees : %f"), OldDegrees + DeltaDegrees);

	return FQuat(FVector::RightVector,OldRadians+DeltaRadians);
	
}

“There might be a problem with using the world location of the spline as it is a component inside the MovementComponent. The pawn seems to move infinitely. As we used the transform of the spline to move and the spline world transform depends on the pawn location.”

The spline relative transform also depends on the location of the pawn, though, doesn’t it? If the spline’s location is updated as the pawn moves in world space (because the spline is attached to the pawn’s location), then it’s local position relative to the pawn is constant. No matter how far the pawn moves, the spline moves with it and the pawn never reaches its target.

If I understand what you’re saying correctly, what I would suggest is create the spline to determine the translation and rotation targets once when you want to initialize a new movement and store those targets: the world location and the rotation. Then when you interpolate towards those goals over the following frames don’t modify them, just use the existing values.

When you create the spline, either create it as a separate entity, or if you’re creating it as a component attach it with KeepWorldTransform so it’s doesn’t follow the character’s movement; otherwise the spline will move with the pawn and constantly change the pawn’s path.

If the player or other controller instigates a new move target, recompute then with a new spline and set your new targets.

That way you just decide your goal location and rotation once, and each frame move from the actor’s current location/rotation to the static target.

“ComputeTargetPitch returns the right Quat. TargetRotation degrees is always equal to 90.0f in the LimitRotation method.”

I thought you were saying that ComputeTargetPitch always returned the same Quat. But I’m still a bit confused here; where is TargetRotation within LimitRotation method, I don’t see it.
Do you mean “NewRotation” ? The TargetQuat that is being passed into LimitRotation?

Hey thanks for your reply. Sorry my posts are confusing.

Indeed when I told about TargetPitch in the LimitRotation method, I meant the NewRotation Quat in parameter in this method. Actually, I used this method with CurrentQuat for the first parameter and TarquetQuat for the 2nd parameter.

The SplineComponent is actually created inside a custom MovementComponent, which is attached to the Pawn. The Spline is initialized in the constructor of this MovementComponent. Each time I select a pawn and after I click on a Tile, I delete all its Spline Points and add new Points in local space using the location of the path I have found to reach the selected tile.

I hope I’ve shed some light on what I meant. I am sorry, my english is not as good as I expected and it’s sometimes hard for me to explain what I meant.

Thank you for your reply. I am sorry for my confused posts.

You are right. When I talked about the LimitRotation problem, I meant the NewRotation Quat. Actually I use this method with the 2 Quats which are class properties. I use CurrentQuat as the 1st parameter and TargetQuat as the 2nd.

About the Spline, I use it in a custom MovementComponent, which is used for the Pawn. The Spline is initialized in the Component constructor. Then after selecting a pawn and a tile, I remove all the spline points and add a new spline point for each tile in my path to the selected tile. The spline points are added in Local Space.

I hope I’ve shed some light on what I am intending to do and what I meant. I am sorry my english is not as good as I expected and sometimes it’s hard for me to explain clearly my problems or my goals.