Help with Spring Arm Pitch Rotation for -180 to 180 Degrees

Hello,
I am working on a racing game, and want the camera to follow the player like in Mario Kart. I am having an issue updating the camera pitch to follow the player around loops and hills. What is the best way to update the Pitch for a SpringArm Rotation to follow the player with 6 DOF?

are you working in C++ or Blueprints?

you could maybe look into something like a “lock on system” or “target lock system” but focus on the part where the camera is being adjusted to focus on a point. Then give the actor an invisible SceneComponent that is attached to the RootComponent. That way when the character turns the camera will always be “behind” the Actor. this can also be used for a quick focus forward system as well.

you also want to be normalizing the rotation every so often (mainly because the rotation is not actually Euler angles but rather a Quaternion and the matrix operations for Quaternions slow down drastically with big numbers)

Keep in mind that this implementation could be very jarring for rapid turns, or quick spins, so you might want to experiment with delays on the rotation, or just have it rotate slower then instant.

I am using C++ for this project for the most part. I have not looked into Lock on or Target Lock yet, but I will give it a look. I am rotating the Camera with a Spring Arm component, but the Pitch is causing me issues with rotation. Hopefully the lock on helps

Unfortunately no luck with this approach. Most tutorials are using UkismetMathLibrary::FindLookAtRotation, and that doesn’t work for complete pitch rotation.
Also the tutorials are focusing on Yaw and not Pitch for what I could find

FMath::RInterpTo(FRotator Current, FRotator Target, float DeltaTime, float RotationSpeed)
as long as you can get an FRotator for the new camera rotation you can throw this in Tick() (you could otherwise put it after a known delay loop, but you could end up breaking lockstep with the render pass).

//AMyCharacter.h
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Character)
    TObjectPtr<USceneComponent> TargetComponent;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Character)
    TObjectPtr<USpringArmComponent> SpringArm;
//...
void AMyCharacter::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if (SpringArm && TargetComponent)
    {
        // Get the current rotation of the SpringArm
        FRotator CurrentRotation = SpringArm->GetComponentRotation();

        // Get the desired rotation to face the TargetComponent
        FVector ToTarget = TargetComponent->GetComponentLocation() - SpringArm->GetComponentLocation();
        FRotator DesiredRotation = ToTarget.Rotation();

        // the lower the value the slower the rotation.
        // could even be dynamically changed to be inversely proportional to the characters velocity.
        float RotSpeed = 10.0f; // Adjust this for desired smoothness
        FRotator NewRotation = FMath::RInterpTo(CurrentRotation, DesiredRotation, DeltaTime, RotSpeed);

        // Apply the new rotation to the SpringArm
        SpringArm->SetWorldRotation(NewRotation);
    }
}

as long as SpringArm and TargetComponent are parented/fused to the same object in the hierarchy you should have consistent behavior.

This is along the lines of a few of the methods I have been using to get this to work. The issue comes when the pitch rotation is around the same as the Up Vector causing weird flipping on the camera rotation causing the issue I am having

Sorry for not realizing the issue sooner, it sounds like you are getting “Gimble Locked” (though this is usually experienced at 90-degree angular differences); this is the big drawback to Euler Angles, and why the engine on the backend uses Quaternions, but Matrices (the way we show, and work with Quaternions) can be messy and “hard”.

the solution should be “easy” for C++ where instead of using RotatorInterpilateTo we can use QuaternionInterpilateTo “QInterpTo()” (Epic has made it a point not to expose Quaternions to blueprints almost like they could scare off the artists)

The good news is that the matrix math is already taken care of, and the Transform.Rotation has been a Quaternion the entire time. The bad news is you need to derive a Quaternion, but still not that scarry.

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

    if (SpringArm && TargetComponent)
    {
        // Get the current rotation of the SpringArm as a quaternion
        FQuat CurrentRotation = SpringArm->GetComponentQuat();

        // Calculate the desired rotation to face the TargetComponent
        FVector ToTarget = TargetComponent->GetComponentLocation() - SpringArm->GetComponentLocation();
        FRotator DesiredRotator = ToTarget.Rotation();
        FQuat DesiredRotation = FQuat(DesiredRotator);

        // the lower the value the slower the rotation.
        // could even be dynamically changed to be inversely proportional to the characters velocity.
        float RotSpeed = 10.0f; // Adjust this for desired smoothness
        FQuat NewRotation = FMath::QInterpTo(CurrentRotation, DesiredRotation, DeltaTime, RotSpeed);

        // Apply the new rotation to the SpringArm
        SpringArm->SetWorldRotation(NewRotation.Rotator());
    }
}

Looks like I am still getting the flipping issue at 90 Degree Pitch with this route as well.
I think it might be the FVector ToTarget that might be the problem throwing off the angle. I am using the spring arm location subtracted from a Hit Result under my camera to calculate the direction Vector (FVector Direction = (MeshLocation - HitLocation).GetSafeNormal(); )
Also I am curious of the suggestion of FMath::QInterpTo over FQuat::Slerp for the smooth rotation calculation?

So to make sure I get the full picture of what is going on:
lets say your character/pawn with the SpringArmComponent has Euler rotation of 0,0,0 and when the pitch is modified but less then 179.9 the Camera is looking at the character/pawn right side up; but once the Pitch goes over 180.1 the character/pawn is upside down with respect to the camera.
Is this what is happening?

The FMath::QInterpTo vs FQuat::Slerp has to do with behavior and steps taken before and during the function call. for both you given them a ValueA and ValueB (often times current and goal)
InterpTo: you give a deltaTime and speed, so you can account for acceleration and deceleration by just handling the velocity (in this case the angular velocity because they are quaternions )
Slerp: you give it a percent as decimal between the current and goal, meaning you need to find that percentage at or before every call based on acceleration and deceleration, and feeding in constant values like .5 or .6 you end up arriving at the “goal” more often by Floating Point Approximation then the actual accumulation.

personally I only like Slerp for fixed start and end points where I am managing the velocity manually. Effectively if you have constant points A and B you can fine tune the progress between those points, and you can even reverse the progress almost trivially, but you have to maintain the current progress. you can imagine using Slerp in this way as utilizing a Blueprint curve but only with a linear (blueprint curves can be thought of as this same process but any function).

I prefer InterpTo for dynamic start and end points because I only need to manage the velocity (often a constant or an exposed member variable) and deltaTime which should be a known or attainable.

There is some things dealing with consistency of framerate; where sometimes for very high framerates, or very inconsistent frame rates I get more irregular behavior when calculating and handing off for Slerp() then, when just passing very small or very large deltaTime to an InterpTo().