How to Rotate Turret Bone to Camera (Fast Snappy Rotation without Jitter) in Unreal?

Hey, thanks for joining in with the conversation! In our case, the turret isn’t a separate mesh, but the entire tank is a single skeletal mesh, and the turret is controlled through a bone. I read your response in the other thread where you suggested using a SpringArmComponent to decouple turret rotation, which seems like a smart approach for separate components, but our setup doesn’t really match that.

If you’ve worked with bone-driven turret rotation before, I’d be really interested to hear how you handled it!

Appreciate you sharing the approach though, if you’ve dealt with bone-based turret setups before, I’d be super interested in any best practices you’ve come across!

the tank cpp

#include "Player/MainPlayer.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Player/PlayerWeaponSystem.h"

AMainPlayer::AMainPlayer()
{
	PrimaryActorTick.bCanEverTick = true;

	bUseControllerRotationYaw = false;
	JumpMaxHoldTime = JumpHoldTime;

	// Capsule Component
	SetRootComponent(GetCapsuleComponent());
	GetCapsuleComponent()->SetCapsuleHalfHeight(80.0f);
	GetCapsuleComponent()->SetCapsuleRadius(60.0f);
	
	// Base Mesh
	GetMesh()->SetupAttachment(GetRootComponent());
	GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	GetMesh()->SetRelativeLocation(FVector(0.0f, 0.0f, -75.0f));

	// Spring Arm
	CameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));
	CameraSpringArm->SetupAttachment(GetCapsuleComponent());
	CameraSpringArm->TargetArmLength = 600.0f;
	CameraSpringArm->TargetOffset = FVector(0.0f, 0.0f, 150.0f);
	CameraSpringArm->bUsePawnControlRotation = true;

	// Camera
	Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
	Camera->SetupAttachment(CameraSpringArm);

	// Movement Component
	GetCharacterMovement()->MaxWalkSpeed = MovementSpeed;
	GetCharacterMovement()->MaxAcceleration = Acceleration;
	GetCharacterMovement()->BrakingDecelerationWalking = BrakingDecelerationWalking;
	GetCharacterMovement()->RotationRate = FRotator(0.0f, RotationRate, 0.0f);
	GetCharacterMovement()->BrakingFrictionFactor = BrakingFrictionFactor;
	
	GetCharacterMovement()->GravityScale = Gravity;
	GetCharacterMovement()->JumpZVelocity = JumpForce;
	GetCharacterMovement()->BrakingDecelerationFalling = BrakingDecelerationWhileFalling;
	GetCharacterMovement()->AirControl = AirControl;

	GetCharacterMovement()->GroundFriction = 8.0f;
	GetCharacterMovement()->MinAnalogWalkSpeed = 20.0f;
	GetCharacterMovement()->bUseSeparateBrakingFriction = true;
	GetCharacterMovement()->bOrientRotationToMovement = true;

	// Weapon system component
	WeaponComponent = CreateDefaultSubobject<UPlayerWeaponSystem>(TEXT("WeaponComponent"));
}

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

	ApplyPlayerVariables();

	if (WeaponComponent)
	{
		WeaponComponent->InitializeWeaponComponent(this);
	}
}

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

	if (bShouldDampenZVelocity && bIsGliding)
	{
		GlideDampElapsed += DeltaTime;

		// Lerp from the speed the player was falling to the glide down speed
		const float Alpha = FMath::Clamp(GlideDampElapsed / GlideDampTime, 0.0f, 1.0f);
		const float NewZ = FMath::Lerp(InitialZVelocity, GlideDownSpeed, Alpha);

		// Update velocity with new Z value
		FVector Velocity = GetCharacterMovement()->Velocity;
		Velocity.Z = NewZ;
		GetCharacterMovement()->Velocity = Velocity;

		if (Alpha >= 1.0f)
		{
			bShouldDampenZVelocity = false;
		}
	}

	if (bIsGliding)
	{
		// Gradually increase the forward glide speed up to the maximum glide speed
		CurrentGlideSpeed = FMath::Clamp(CurrentGlideSpeed + (GlideAcceleration * DeltaTime), 0.0f, GlideSpeed);

		// Set forward velocity and keep Z velocity updated
		FVector ForwardVelocity = GetActorForwardVector() * CurrentGlideSpeed;
		ForwardVelocity.Z = GetCharacterMovement()->Velocity.Z; // Maintain the updated Z velocity
		GetCharacterMovement()->Velocity = ForwardVelocity;
	}

	if (TimeSinceDash < TimeBetweenDashes) TimeSinceDash += DeltaTime;
}

void AMainPlayer::ApplyPlayerVariables() const
{
	GetCharacterMovement()->MaxWalkSpeed = MovementSpeed;
	GetCharacterMovement()->MaxAcceleration = Acceleration;
	GetCharacterMovement()->BrakingDecelerationWalking = BrakingDecelerationWalking;
	GetCharacterMovement()->RotationRate = FRotator(0.0f, RotationRate, 0.0f);
	GetCharacterMovement()->BrakingFrictionFactor = BrakingFrictionFactor;
	
	GetCharacterMovement()->GravityScale = Gravity;
	GetCharacterMovement()->JumpZVelocity = JumpForce;
	GetCharacterMovement()->BrakingDecelerationFalling = BrakingDecelerationWhileFalling;
	GetCharacterMovement()->AirControl = AirControl;
}

// Moving
void AMainPlayer::Move(const FInputActionValue& Value)
{
	const FVector2D MovementVector = Value.Get<FVector2D>();

	const FRotator ControlRotation = GetControlRotation();
	const FRotator YawRotation(0.f, ControlRotation.Yaw, 0.f);

	const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
	const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
	
	AddMovementInput(RightDirection, MovementVector.X);
	AddMovementInput(ForwardDirection, MovementVector.Y);

	TankTreadMovement();
}

// Looking
void AMainPlayer::Look(const FInputActionValue& Value)
{
	const FVector2D LookAxisVector = Value.Get<FVector2D>();
	
	AddControllerYawInput(LookAxisVector.X);
	AddControllerPitchInput(-LookAxisVector.Y);
}

void AMainPlayer::Landed(const FHitResult& Hit)
{
	Super::Landed(Hit);

	StopGlide();
}

// Jumping
void AMainPlayer::StartJump()
{
	PlayAnimMontage(JumpMontage);
	Jump();
}

void AMainPlayer::StopJump()
{
	StopJumping();	
}

void AMainPlayer::StartGlide()
{
	if (GetCharacterMovement()->IsFalling())
	{
		const FVector PlayerVelocity = GetCharacterMovement()->Velocity;

		StopJumping();

		// Here we check if the player is moving forward
		const float InitialSpeed = FVector::DotProduct(PlayerVelocity, GetActorForwardVector());
		CurrentGlideSpeed = FMath::Max(InitialSpeed, 0.0f);

		// Start Z velocity damp
		InitialZVelocity = PlayerVelocity.Z;
		GlideDampElapsed = 0.0f;

		// Disable gravity temporarily to prevent it from interfering with glide downward speed
		GetCharacterMovement()->GravityScale = 0.0f;

		// Enable glide-specific movement settings
		GetCharacterMovement()->AirControl = GlideAirControl;
		GetCharacterMovement()->RotationRate = FRotator(0.0f, GlideRotationRate, 0.0f);

		bIsGliding = true;
		bShouldDampenZVelocity = true;
	}
}

void AMainPlayer::StopGlide()
{
	GetCharacterMovement()->GravityScale = Gravity; 
	GetCharacterMovement()->AirControl = AirControl;
	GetCharacterMovement()->RotationRate = FRotator(0.0f, RotationRate, 0.0f);

	bIsGliding = false;
	CurrentGlideSpeed = 0.0f;
	bShouldDampenZVelocity = false;
	InitialZVelocity = 0.0f;
	GlideDampElapsed = 0.0f;
}

void AMainPlayer::Dash()
{
	if (TimeSinceDash >= TimeBetweenDashes)
	{
		const FVector MovementInput = GetLastMovementInputVector();

		bIsDashing = true;

		const FVector UpVector = this->GetActorUpVector();
		const FVector DashVector = FMath::Lerp(MovementInput, UpVector, DashAngle / 90.0f);

		this->LaunchCharacter(DashVector * DashStrength, true, true);
		TimeSinceDash = 0.0f;
	}
}