Hey,
Im using true first person for my game with free aim.
But im using a custom aim pitch / yaw cause for multiplayer only aim pitch is replicated and seems using GetAimOffsets like in the ShooterGame example giving issues like the flipping camera when looking down all the way and loosing control when switching to weapon attached camera for ADS view.
I’m using the AimPitch/AimYaw variables directly in the animation BP.
In the character BP i also disable UseControllerRotationPitch/Yaw/Roll (need to override FaceRotation when doing this)
For the head attached camera, enable the “Use Pawn Control Rotation”
Character Header file
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Transient, Replicated, Category = "Character")
float AimPitch;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Transient, Replicated, Category = "Character")
float AimYaw;
Character Source file
void ASwatCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (IsLocallyControlled())
{
// Save the current aim pitch and yaw
float SavedAimPitch = AimPitch;
float SavedAimYaw = AimYaw;
// Update the aim offset
FRotator ControlRotation = GetControlRotation();
FRotator ActorRotation = GetActorRotation();
FRotator DeltaRotation = ControlRotation - ActorRotation;
DeltaRotation.Normalize();
// Interpolate the current and new aim offsets for smoothing
FRotator CurrentAimOffset(AimPitch, AimYaw, 0.0f);
FRotator UpdatedAimOffset = FMath::RInterpTo(CurrentAimOffset, DeltaRotation, DeltaTime, 15.0f);
AimPitch = UpdatedAimOffset.Pitch;
AimYaw = UpdatedAimOffset.Yaw;
// Notify the server about the new aim offset
if (Role < ROLE_Authority && (AimPitch != SavedAimPitch || AimYaw != SavedAimYaw))
{
//TODO: This might give some network overhead, see if we can replicate this trough the movement component
// see RemoteViewPitch (how its set and replicated)
ServerSetAimOffset(AimPitch, AimYaw);
}
}
}
void ASwatCharacter::FaceRotation(FRotator NewControlRotation, float DeltaTime)
{
// Only continue if it's not game over
static const FName NAME_GameOver = FName(TEXT("GameOver"));
auto SwatPlayerController = Cast<ASwatPlayerController>(Controller);
if (SwatPlayerController && SwatPlayerController->GetStateName() == NAME_GameOver)
{
return;
}
// Update the actor rotation based on the aim offset
FRotator ControlRotation = GetControlRotation();
FRotator ActorRotation = GetActorRotation();
FRotator ControlYawRotation(0.0f, ControlRotation.Yaw, 0.0f);
FRotator ActorYawRotation(0.0f, ActorRotation.Yaw, 0.0f);
FRotator YawDeltaRotation = ControlYawRotation - ActorYawRotation;
YawDeltaRotation.Normalize();
FRotator AimRotation = FMath::RInterpTo(FRotator::ZeroRotator, YawDeltaRotation, DeltaTime, 5.0f);
if (FMath::Abs(YawDeltaRotation.Yaw) >= 70.0f || bIsInAimRotation || GetVelocity().Size() > 0.0f)
{
AddActorWorldRotation(AimRotation);
bIsInAimRotation = !FMath::IsNearlyEqual(YawDeltaRotation.Yaw, 0.0f, 2.0f);
}
// Update the actor rotation
const FRotator CurrentRotation = GetActorRotation();
if (!bUseControllerRotationPitch)
{
NewControlRotation.Pitch = CurrentRotation.Pitch;
}
if (!bUseControllerRotationYaw)
{
NewControlRotation.Yaw = CurrentRotation.Yaw;
}
if (!bUseControllerRotationRoll)
{
NewControlRotation.Roll = CurrentRotation.Roll;
}
SetActorRotation(NewControlRotation);
}
void ASwatCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION(ASwatCharacter, AimPitch, COND_SkipOwner);
DOREPLIFETIME_CONDITION(ASwatCharacter, AimYaw, COND_SkipOwner);
}