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