Is there a way to retrieve a Rotator only on Pitch

“The spline points are added in Local Space.”

This does not matter.

Local Space and World Space are just points of reference. World Space location is relative to the Level origin; Local Space location is relative to the Actor origin. Your spline exists in Local Space and World Space at the same time.

When you are determining the transform you are comparing the actor’s world location:

FVector OldLocation = Owner->GetActorLocation();

To the splines local location:

FTransform NewTransform = Spline->GetTransformAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);

This will never work.

You need to do two things:

  1. Compare their locations from the same point of reference
  2. Make sure the spline isn’t moving when you move your pawn

To address point 1 you can convert the Actor’s world location to the spline’s local space:

FVector OldLocation = Owner->GetActorLocation();
OldLocation = OldLocation - Spline->ComponentToWorld.GetLocation();

But this is just a roundabout way of getting to the exact same place as retrieving the Spline transform in World space from the beginning:

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

You have to do one or the other.


In either case, you still have to address point 2) if the Spline is moving when the Pawn moves, you’re not going to get good results.

The Spline either needs to be created as a separate entity, or make sure you attach it with FAttachmentRules::KeepWorldTransform so it remains stationary within the world as the actor moves. You set this value when you spawn the spline.

To fix point one you actually have a third option - as long as the spline and the pawn share the same local space.

You can not retrieve the actor location. The actor location in local space is always 0,0,0; so you can just use the spline’s local location as the actor’s target translation.

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

But you still have to address point 2 above.

Hey, I found out what may cause the issue with this always facing bottom rotation.

When I used NewRotation.ToAxisAndAngle(NewVector, NewRadians);, I assumed that the NewVector will be equal to FVector::RightVector but it is not.

When the pawn moves right the NewVector is equal to FVector(0.0f,0.0f,0.0f) and the angle is 0 degrees.

When the pawn moves left, the NewVector is equal to FVector::RightVector and the angle is 180 degrees.

When the pawn moves top, the NewVector is equal to FVector(0.0f,1.0f,0.0f) and the angle is 90 degrees

When the pawn moves bottom the NewVector is equal to FVector(0.0f,-1.0f,0.0f) and the angle is also 90 degrees.

I will check if there is a way to get the angle of the FQuat with a given FVector. If there is I will always use the FVector::RightVector and I will receive the proper angle.

If there’s not I will fix it in a dirty way. If the angle is greater than 0, I will multiply it by the Y value of the FVector.

Or, you could just do what I suggested a week ago and store the CurrentPitch rotation as a float:

 /** 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;

And never retrieve the rotation from the actor:

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

And then you don’t have to worry about limiting what you get from ToAxisAndAngle because you already know what the rotation is.

It’s easier, it’s faster, it’s more reliable. And as a bonus, you only store a float value in RAM instead of an FQuat.

Hey thank you very much it works really good.

I added a method to compute the delta pitch beause when it is greater than 180 in absolute value, the pawn made the longest rotation. So here’s the tweak :

float UGridMovementComponent::ComputeDeltaPitch(const float &OldPitch, const float &NewPitch)
{
	float DeltaPitch = NewPitch - OldPitch;
	if (FMath::Abs(DeltaPitch) <= 180.0f)
	{
		return DeltaPitch;
	}
	else
	{
		if (DeltaPitch > 0)
		{			
			DeltaPitch -= 360;			
		}
		else
		{			
			DeltaPitch += 360;
		}
		return DeltaPitch;
	}
}

Now everything is fine. Thank you for your infinite patience and your help

Awesome!

Best of luck going forward!