I think the cursor functionality is so cool where it shoots where your cursor is at, but I really wanted to set my camera more over-the-shoulder and looking forward, and for that to really work I found I had to do a few things.
- Have the camera attach to the TurretMesh instead of the RootComponent,
- Control the Turret’s rotation with mouse X input, and
- Make sure turning with A/D doesn’t also pull your view away (so the turret is always looking where you’re aiming, regardless of how the tank is turning).
Here it is in action:
To do this, first I needed to make the TurretMesh component in the PawnBase class accessible to the TankPawn class. That was easy enough just by moving it from private to protected.
Next, I wanted to attach the Camera Spring Arm to the TurretMesh rather than the Capsule component. Maybe I didn’t need to do that in the end, but at the time it felt like the step I needed to take, so I made the following change:
APawnTank::APawnTank()
{
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("Camera Spring Arm"));
// Changed this from RootComponent to TurretMesh.
SpringArm->SetupAttachment(TurretMesh);
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
Camera->SetupAttachment(SpringArm);
}
Then, I needed a way to take the mouse input and use it to rotate our view. Since the camera arm is now attached to the TurretMesh, I would do:
void APawnTank::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("MoveForwardAndBack", this, &APawnTank::MoveTank);
PlayerInputComponent->BindAxis("TurnRightAndLeft", this, &APawnTank::RotateTank);
// Added this line for mouse input.
PlayerInputComponent->BindAxis("RotateTurret", this, &APawnTank::RotateView);
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &APawnTank::Fire);
}
/// Rotate turret with mouse (alternative to aiming at cursor).
void APawnTank::RotateView(float Input)
{
FRotator Rotation = TurretMesh->GetRelativeRotation();
// The += is important here, otherwise my view would never move very far
// before snapping back to 0 once you stopped moving the mouse.
Rotation.Yaw += Input * MouseSensitivity * GetWorld()->DeltaTimeSeconds;
TurretMesh->SetRelativeRotation(Rotation);
}
With all of that done, finally I needed to couteract the rotation of the root component so that the turret and thus our camera wasn’t pulled along for the ride every time we hit A or D, making aiming a lot more difficult.
/// Determine rotation speed and direction on y-axis (spinning/yaw). Update RotationDirection.
void APawnTank::RotateTank(float Input)
{
float Pitch = 0;
float Yaw = Input * TurnSpeed * GetWorld()->DeltaTimeSeconds;
float Roll = 0;
FRotator Rotation = FRotator(Pitch, Yaw, Roll);
// An exact opposite rotation with negative Yaw result.
FRotator CounterRotation = FRotator(Pitch, -Yaw, Roll);
RotationDirection = FQuat(Rotation);
// Here is where the Tank actor is rotated.
AddActorLocalRotation(RotationDirection, true);
// And here is where we counter rotate the turret the same amount
// so the view stays the same.
TurretMesh->AddLocalRotation(CounterRotation, false);
}
And that’s it! I just felt proud of myself for figuring out the fact that I needed to add that counter rotation to keep the view centered. It took me a bit lol.
I don’t know if any of this is actually explored later in the lecture but either way, hopefully this is interesting to someone.