Files
PHY/Plugins/GMS/Source/GenericMovementSystem/Private/GMS_CharacterMovementSystemComponent.cpp
2026-03-03 01:23:02 +08:00

1084 lines
36 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GMS_CharacterMovementSystemComponent.h"
#include "TimerManager.h"
#include "Components/CapsuleComponent.h"
#include "Curves/CurveFloat.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/PlayerController.h"
#include "Kismet/KismetMathLibrary.h"
#include "Net/UnrealNetwork.h"
#include "Components/SkeletalMeshComponent.h"
#include "Animation/AnimInstance.h"
#include "Locomotions/GMS_MainAnimInstance.h"
#include "Net/Core/PushModel/PushModel.h"
#include "Settings/GMS_SettingObjectLibrary.h"
#include "Utility/GMS_Constants.h"
#include "Utility/GMS_Log.h"
#include "Utility/GMS_Math.h"
#include "Utility/GMS_Rotation.h"
#include "Utility/GMS_Utility.h"
#include "Utility/GMS_Vector.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_CharacterMovementSystemComponent)
namespace GMS_MovementComponentConstants
{
inline static constexpr auto TeleportDistanceThresholdSquared{FMath::Square(50.0f)};
}
UGMS_CharacterMovementSystemComponent::UGMS_CharacterMovementSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
SetIsReplicatedByDefault(true);
PrimaryComponentTick.bCanEverTick = true;
bWantsInitializeComponent = true;
bReplicateUsingRegisteredSubObjectList = true;
MovementIntent = FVector::ZeroVector;
MovementModeToTagMapping = {
{MOVE_None, GMS_MovementModeTags::None},
{MOVE_Walking, GMS_MovementModeTags::Grounded},
{MOVE_NavWalking, GMS_MovementModeTags::Grounded},
{MOVE_Falling, GMS_MovementModeTags::InAir},
{MOVE_Swimming, GMS_MovementModeTags::Swimming},
{MOVE_Flying, GMS_MovementModeTags::Flying},
};
}
void UGMS_CharacterMovementSystemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
FDoRepLifetimeParams Parameters;
Parameters.bIsPushBased = true;
Parameters.Condition = COND_SkipOwner;
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, DesiredMovementState, Parameters)
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, DesiredRotationMode, Parameters)
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, MovementIntent, Parameters)
}
void UGMS_CharacterMovementSystemComponent::InitializeComponent()
{
Super::InitializeComponent();
OwnerCharacter = Cast<ACharacter>(GetOwner());
if (OwnerCharacter)
{
MovementState = DesiredMovementState;
RotationMode = DesiredRotationMode;
CharacterMovement = Cast<UCharacterMovementComponent>(OwnerCharacter->GetMovementComponent());
OwnerCharacter->bUseControllerRotationPitch = false;
OwnerCharacter->bUseControllerRotationRoll = false;
OwnerCharacter->bUseControllerRotationYaw = false;
CharacterMovement->bUseControllerDesiredRotation = false;
CharacterMovement->bOrientRotationToMovement = false;
AnimationInstance = GetMesh()->GetAnimInstance();
// Make sure the mesh and animation blueprint are ticking after the character so they can access the most up-to-date character state.
GetMesh()->AddTickPrerequisiteComponent(this);
RefreshTargetYawAngleUsingActorRotation();
}
}
void UGMS_CharacterMovementSystemComponent::BeginPlay()
{
Super::BeginPlay();
//This callback fires on both sides, including simulated proxy.
OwnerCharacter->MovementModeChangedDelegate.AddDynamic(this, &ThisClass::OnCharacterMovementModeChanged);
OnCharacterMovementModeChanged(OwnerCharacter, OwnerCharacter->GetCharacterMovement()->GetGroundMovementMode(), 0);
RefreshRotationMode();
RefreshMovementSetSetting();
}
void UGMS_CharacterMovementSystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (IsValid(OwnerCharacter))
{
OwnerCharacter->MovementModeChangedDelegate.RemoveDynamic(this, &ThisClass::OnCharacterMovementModeChanged);
}
Super::EndPlay(EndPlayReason);
}
void UGMS_CharacterMovementSystemComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGMS_CharacterMovementSystemComponent::TickComponent"), STAT_GMS_MovementSystem_Tick, STATGROUP_GMS)
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
if (!GetMovementDefinition().IsValid() || !IsValid(AnimationInstance) || !IsValid(ControlSetting))
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
return;
}
RefreshMovementBase();
RefreshInput(DeltaTime);
RefreshLocomotionEarly();
RefreshView(DeltaTime);
RefreshRotationMode();
RefreshLocomotion(DeltaTime);
RefreshDynamicMovementState();
RefreshMovementState();
RefreshRotation(DeltaTime);
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
RefreshLocomotionLate(DeltaTime);
}
void UGMS_CharacterMovementSystemComponent::OnCharacterMovementModeChanged(ACharacter* InCharacter, EMovementMode PrevMovementMode, uint8 PreviousCustomMode)
{
// Use the character movement mode to set the locomotion mode to the right value. This allows you to have a
// custom set of movement modes but still use the functionality of the default character movement component.
EMovementMode CharMovementMode = CharacterMovement->MovementMode;
uint8 CharCustomMovementMode = CharacterMovement->CustomMovementMode;
if (CharMovementMode != MOVE_Custom)
{
if (MovementModeToTagMapping.Contains(CharMovementMode) && MovementModeToTagMapping[CharMovementMode].IsValid())
{
SetLocomotionMode(MovementModeToTagMapping[CharMovementMode]);
}
else
{
GMS_CLOG(Error, "No locomotion mode mapping for MovementMode:%s", *UEnum::GetDisplayValueAsText(CharMovementMode).ToString());
}
}
else
{
if (CustomMovementModeToTagMapping.Contains(CharCustomMovementMode) && CustomMovementModeToTagMapping[CharCustomMovementMode].IsValid())
{
SetLocomotionMode(CustomMovementModeToTagMapping[CharCustomMovementMode]);
}
else
{
GMS_CLOG(Error, "No locomotion mode mapping for CustomMovementMode:%d", CharCustomMovementMode);
}
}
}
void UGMS_CharacterMovementSystemComponent::OnReplicated_LocomotionMode(const FGameplayTag& PreviousLocomotionMode)
{
// OnMovementModeChanged will fire on all clients(including simulated pawn), mover is not. so don't call parent.
}
void UGMS_CharacterMovementSystemComponent::RefreshMovementState()
{
if (!DesiredMovementState.IsValid())
{
return;
}
if (MovementState == DesiredMovementState)
{
return;
}
ApplyMovementSetting();
SetMovementState(CalculateActualMovementState());
}
FGameplayTag UGMS_CharacterMovementSystemComponent::CalculateActualMovementState()
{
check(GetNumOfMovementStateSettings() != 0)
if (ControlSetting->MovementStates.Num() == 1)
{
return ControlSetting->MovementStates[0].Tag;
}
for (int32 i = 0; i < ControlSetting->MovementStates.Num(); i++)
{
float Speed = ControlSetting->MovementStates[i].Speed;
if (Speed > 0.0f && LocomotionState.Speed < Speed + 10.0f)
{
return ControlSetting->MovementStates[i].Tag;
}
}
return FGameplayTag::EmptyTag;
}
void UGMS_CharacterMovementSystemComponent::ApplyMovementSetting()
{
if (bAllowRefreshCharacterMovementSettings && IsValid(ControlSetting))
{
if (const FGMS_MovementStateSetting* TempMS = ControlSetting->GetMovementStateSetting(DesiredMovementState, true))
{
CharacterMovement->MaxWalkSpeed = TempMS->Speed;
CharacterMovement->MaxAcceleration = TempMS->Acceleration;
CharacterMovement->BrakingDecelerationWalking = TempMS->BrakingDeceleration;
CharacterMovement->MaxWalkSpeedCrouched = TempMS->Speed;
}
}
}
void UGMS_CharacterMovementSystemComponent::SetDesiredMovement(const FGameplayTag& NewDesiredMovement)
{
SetDesiredMovement(NewDesiredMovement, true);
}
void UGMS_CharacterMovementSystemComponent::SetMovementState(const FGameplayTag& NewMovementState)
{
if (NewMovementState.IsValid() && MovementState != NewMovementState)
{
const FGameplayTag PreviousMovementState{MovementState};
MovementState = NewMovementState;
OnMovementStateChanged(PreviousMovementState);
}
}
const FGameplayTag& UGMS_CharacterMovementSystemComponent::GetMovementState() const
{
return MovementState;
}
void UGMS_CharacterMovementSystemComponent::SetDesiredMovement(const FGameplayTag& NewDesiredMovement, bool bSendRpc)
{
if (DesiredMovementState == NewDesiredMovement || GetOwner()->GetLocalRole() < ROLE_AutonomousProxy)
{
return;
}
const auto PreviousMovement{DesiredMovementState};
DesiredMovementState = NewDesiredMovement;
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, DesiredMovementState, this)
if (bSendRpc)
{
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
{
ClientSetDesiredMovement(NewDesiredMovement);
}
else
{
ServerSetDesiredMovement(NewDesiredMovement);
}
}
}
void UGMS_CharacterMovementSystemComponent::ClientSetDesiredMovement_Implementation(const FGameplayTag& NewDesiredMovement)
{
SetDesiredMovement(NewDesiredMovement, false);
}
void UGMS_CharacterMovementSystemComponent::ServerSetDesiredMovement_Implementation(const FGameplayTag& NewDesiredMovement)
{
SetDesiredMovement(NewDesiredMovement, false);
}
const FGameplayTag& UGMS_CharacterMovementSystemComponent::GetDesiredRotationMode() const
{
return DesiredRotationMode;
}
void UGMS_CharacterMovementSystemComponent::SetDesiredRotationMode(const FGameplayTag& NewDesiredRotationMode)
{
SetDesiredRotationMode(NewDesiredRotationMode, true);
}
const FGameplayTag& UGMS_CharacterMovementSystemComponent::GetRotationMode() const
{
return RotationMode;
}
void UGMS_CharacterMovementSystemComponent::SetRotationMode(const FGameplayTag& NewRotationMode)
{
if (RotationMode != NewRotationMode)
{
if (bRespectAllowedRotationModesSettings && !GetMovementStateSetting().AllowedRotationModes.Contains(NewRotationMode))
{
GMS_CLOG(Warning, "current movement state(%s) doesn't allow new rotation mode(%s).", *MovementState.ToString(), *NewRotationMode.ToString());
return;
}
const auto PreviousRotationMode{RotationMode};
RotationMode = NewRotationMode;
OnRotationModeChanged(PreviousRotationMode);
}
}
void UGMS_CharacterMovementSystemComponent::RefreshRotationMode()
{
SetRotationMode(DesiredRotationMode);
}
void UGMS_CharacterMovementSystemComponent::SetDesiredRotationMode(const FGameplayTag& NewDesiredRotationMode, bool bSendRpc)
{
if (DesiredRotationMode == NewDesiredRotationMode || GetOwner()->GetLocalRole() < ROLE_AutonomousProxy)
{
return;
}
DesiredRotationMode = NewDesiredRotationMode;
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, DesiredRotationMode, this)
if (bSendRpc)
{
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
{
ClientSetDesiredRotationMode(DesiredRotationMode);
}
else
{
ServerSetDesiredRotationMode(DesiredRotationMode);
}
}
}
void UGMS_CharacterMovementSystemComponent::ClientSetDesiredRotationMode_Implementation(const FGameplayTag& NewDesiredRotationMode)
{
SetDesiredRotationMode(NewDesiredRotationMode, false);
}
void UGMS_CharacterMovementSystemComponent::ServerSetDesiredRotationMode_Implementation(const FGameplayTag& NewDesiredRotationMode)
{
SetDesiredRotationMode(NewDesiredRotationMode, false);
}
void UGMS_CharacterMovementSystemComponent::SetMovementIntent(FVector NewMovementIntent)
{
NewMovementIntent = NewMovementIntent.GetSafeNormal();
COMPARE_ASSIGN_AND_MARK_PROPERTY_DIRTY(ThisClass, MovementIntent, NewMovementIntent, this);
}
void UGMS_CharacterMovementSystemComponent::RefreshInput(float DeltaTime)
{
// Using current acceleration as movement input.
if (OwnerCharacter->GetLocalRole() >= ROLE_AutonomousProxy)
{
SetMovementIntent(CharacterMovement->GetCurrentAcceleration() / CharacterMovement->GetMaxAcceleration());
}
Super::RefreshInput(DeltaTime);
}
void UGMS_CharacterMovementSystemComponent::OnRotationModeChanged_Implementation(const FGameplayTag& PreviousRotationMode)
{
Super::OnRotationModeChanged_Implementation(PreviousRotationMode);
// if (PreviousRotationMode == GMS_RotationModeTags::VelocityDirection && !LocomotionState.bMoving)
// {
// // This prevents the actor from rotating in the last input direction after the
// // rotation mode has been changed and the actor is not moving at that moment.
// // LocomotionState.InputYawAngle = ViewState.Rotation.Yaw;
// LocomotionState.TargetYawAngle = ViewState.Rotation.Yaw;
// }
}
void UGMS_CharacterMovementSystemComponent::RefreshMovementBase()
{
const FBasedMovementInfo& BasedMovement = OwnerCharacter->GetBasedMovement();
if (BasedMovement.MovementBase != MovementBase.Primitive || BasedMovement.BoneName != MovementBase.BoneName)
{
MovementBase.Primitive = BasedMovement.MovementBase;
MovementBase.BoneName = BasedMovement.BoneName;
MovementBase.bBaseChanged = true;
}
else
{
MovementBase.bBaseChanged = false;
}
MovementBase.bHasRelativeLocation = BasedMovement.HasRelativeLocation();
MovementBase.bHasRelativeRotation = MovementBase.bHasRelativeLocation && BasedMovement.bRelativeRotation;
const auto PreviousRotation{MovementBase.Rotation};
MovementBaseUtility::GetMovementBaseTransform(BasedMovement.MovementBase, BasedMovement.BoneName,
MovementBase.Location, MovementBase.Rotation);
MovementBase.DeltaRotation = MovementBase.bHasRelativeLocation && !MovementBase.bBaseChanged
? (MovementBase.Rotation * PreviousRotation.Inverse()).Rotator()
: FRotator::ZeroRotator;
}
const FGameplayTag& UGMS_CharacterMovementSystemComponent::GetDesiredMovementState() const
{
return DesiredMovementState;
}
FVector UGMS_CharacterMovementSystemComponent::GetMovementIntent() const
{
return MovementIntent;
}
void UGMS_CharacterMovementSystemComponent::RefreshView(const float DeltaTime)
{
if (MovementBase.bHasRelativeRotation)
{
// Offset the rotations to keep them relative to the movement base.
ViewState.Rotation.Pitch += MovementBase.DeltaRotation.Pitch;
ViewState.Rotation.Yaw += MovementBase.DeltaRotation.Yaw;
ViewState.Rotation.Normalize();
}
ViewState.PreviousYawAngle = UE_REAL_TO_FLOAT(ViewState.Rotation.Yaw);
// update view/control rotation.
if (MovementBase.bHasRelativeRotation)
{
if (OwnerPawn->IsLocallyControlled())
{
// We can't depend on the view rotation sent by the character movement component
// since it's in world space, so in this case we always send it ourselves.
//将相对于平台的视角朝向设置为新的视角朝向。保持视角不变。
SetReplicatedViewRotation((MovementBase.Rotation.Inverse() * OwnerPawn->GetViewRotation().Quaternion()).Rotator(), true);
}
}
else
{
if (OwnerPawn->IsLocallyControlled() || (OwnerPawn->IsReplicatingMovement() && OwnerPawn->GetLocalRole() >= ROLE_Authority && IsValid(OwnerPawn->GetController())))
{
// The character movement component already sends the view rotation to the
// server if movement is replicated, so we don't have to do this ourselves.
SetReplicatedViewRotation(OwnerPawn->GetViewRotation().GetNormalized(), !OwnerPawn->IsReplicatingMovement());
}
}
// update view rotation based on if movement is based.
ViewState.Rotation = MovementBase.bHasRelativeRotation
? (MovementBase.Rotation * ReplicatedViewRotation.Quaternion()).Rotator()
: ReplicatedViewRotation;
// Set the yaw speed by comparing the current and previous view yaw angle, divided by
// delta seconds. This represents the speed the camera is rotating from left to right.
if (DeltaTime > UE_SMALL_NUMBER)
{
ViewState.YawSpeed = FMath::Abs(UE_REAL_TO_FLOAT(ViewState.Rotation.Yaw - ViewState.PreviousYawAngle)) / DeltaTime;
}
}
#pragma region Abstraction
bool UGMS_CharacterMovementSystemComponent::IsCrouching() const
{
return CharacterMovement->IsCrouching();
}
float UGMS_CharacterMovementSystemComponent::GetMaxSpeed() const
{
return CharacterMovement->GetMaxSpeed();
}
float UGMS_CharacterMovementSystemComponent::GetScaledCapsuleRadius() const
{
return OwnerCharacter->GetCapsuleComponent()->GetScaledCapsuleRadius();
}
float UGMS_CharacterMovementSystemComponent::GetScaledCapsuleHalfHeight() const
{
return OwnerCharacter->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
}
float UGMS_CharacterMovementSystemComponent::GetMaxAcceleration() const
{
return CharacterMovement->GetMaxAcceleration();
}
float UGMS_CharacterMovementSystemComponent::GetMaxBrakingDeceleration() const
{
return CharacterMovement->GetMaxBrakingDeceleration();
}
float UGMS_CharacterMovementSystemComponent::GetWalkableFloorZ() const
{
return CharacterMovement->GetWalkableFloorZ();
}
float UGMS_CharacterMovementSystemComponent::GetGravityZ() const
{
return CharacterMovement->GetGravityZ();
}
USkeletalMeshComponent* UGMS_CharacterMovementSystemComponent::GetMesh() const
{
return OwnerCharacter ? OwnerCharacter->GetMesh() : nullptr;
}
bool UGMS_CharacterMovementSystemComponent::IsMovingOnGround() const
{
return CharacterMovement->IsMovingOnGround();
}
#pragma endregion
#pragma region Locomotion
void UGMS_CharacterMovementSystemComponent::RefreshLocomotionEarly()
{
if (!LocomotionState.bMoving &&
RotationMode == GMS_RotationModeTags::VelocityDirection &&
ControlSetting->VelocityDirectionSetting.Get().bInheritBaseRotation)
{
DesiredVelocityYawAngle = FMath::UnwindDegrees(UE_REAL_TO_FLOAT(
DesiredVelocityYawAngle + MovementBase.DeltaRotation.Yaw));
LocomotionState.VelocityYawAngle = FMath::UnwindDegrees(UE_REAL_TO_FLOAT(
LocomotionState.VelocityYawAngle + MovementBase.DeltaRotation.Yaw));
}
if (MovementBase.bHasRelativeLocation)
{
// Offset the rotations (the actor's rotation too) to keep them relative to the movement base.
LocomotionState.TargetYawAngle = FMath::UnwindDegrees(UE_REAL_TO_FLOAT(
LocomotionState.TargetYawAngle + MovementBase.DeltaRotation.Yaw));
LocomotionState.ViewRelativeTargetYawAngle = FMath::UnwindDegrees(UE_REAL_TO_FLOAT(
LocomotionState.ViewRelativeTargetYawAngle + MovementBase.DeltaRotation.Yaw));
LocomotionState.SmoothTargetYawAngle = FMath::UnwindDegrees(UE_REAL_TO_FLOAT(
LocomotionState.SmoothTargetYawAngle + MovementBase.DeltaRotation.Yaw));
auto NewRotation{OwnerPawn->GetActorRotation()};
NewRotation.Pitch += MovementBase.DeltaRotation.Pitch;
NewRotation.Yaw += MovementBase.DeltaRotation.Yaw;
NewRotation.Normalize();
SetActorRotation(NewRotation);
}
LocomotionState.bAimingLimitAppliedThisFrame = false;
}
void UGMS_CharacterMovementSystemComponent::RefreshLocomotion(const float DeltaTime)
{
const auto bHadVelocity{LocomotionState.bHasVelocity};
LocomotionState.Velocity = OwnerPawn->GetVelocity();
// Determine if the character is moving by getting its speed. The speed equals the length
// of the horizontal velocity, so it does not take vertical movement into account. If the
// character is moving, update the last velocity rotation. This value is saved because it might
// be useful to know the last orientation of a movement even after the character has stopped.
LocomotionState.Speed = UE_REAL_TO_FLOAT(LocomotionState.Velocity.Size2D());
static constexpr auto HasSpeedThreshold{1.0f};
LocomotionState.bHasVelocity = LocomotionState.Speed >= HasSpeedThreshold;
if (LocomotionState.bHasVelocity)
{
LocomotionState.VelocityYawAngle = UE_REAL_TO_FLOAT(UGMS_Vector::DirectionToAngleXY(LocomotionState.Velocity));
}
// Character is moving if has speed and current acceleration, or if the speed is greater than the moving speed threshold.
LocomotionState.bMoving = (LocomotionState.bHasInput && LocomotionState.bHasVelocity) ||
LocomotionState.Speed > ControlSetting->MovingSpeedThreshold;
}
void UGMS_CharacterMovementSystemComponent::RefreshDynamicMovementState()
{
if (bAllowRefreshCharacterMovementSettings)
{
return;
}
if (IsValid(SpeedToMovementStateCurve) && OwnerPawn->HasAuthority())
{
int32 Index = UKismetMathLibrary::Round(SpeedToMovementStateCurve->GetFloatValue(LocomotionState.Speed));
FGMS_MovementStateSetting TempSetting;
if (ControlSetting->GetStateByIndex(Index, TempSetting))
{
SetDesiredMovement(TempSetting.Tag);
}
else
{
GMS_CLOG(Warning, "Found invalid index output from SpeedToMovementStateCurve, Index(%d) of movement definitions can't be found. dynamic adjust movement state failed! Actor:%s",
Index, *GetOwner()->GetName());
}
}
}
void UGMS_CharacterMovementSystemComponent::RefreshLocomotionLate(const float DeltaTime)
{
if (!LocomotionMode.IsValid())
{
RefreshTargetYawAngleUsingActorRotation();
}
LocomotionState.bResetAimingLimit = !LocomotionState.bAimingLimitAppliedThisFrame;
}
#pragma endregion
void UGMS_CharacterMovementSystemComponent::RefreshRotation_Implementation(float DeltaTime)
{
RefreshGroundedRotation(DeltaTime);
RefreshInAirRotation(DeltaTime);
}
void UGMS_CharacterMovementSystemComponent::RefreshGroundedRotation(const float DeltaTime)
{
if (LocomotionMode != GMS_MovementModeTags::Grounded || GetGameplayTags().HasAny(GroundedRotationBlockingTags))
{
return;
}
if (OwnerCharacter->HasAnyRootMotion())
{
RefreshTargetYawAngleUsingActorRotation();
return;
}
if (!LocomotionState.bMoving)
{
RefreshGroundedNotMovingRotation(DeltaTime);
}
else
{
RefreshGroundedMovingRotation(DeltaTime);
}
}
void UGMS_CharacterMovementSystemComponent::RefreshGroundedNotMovingRotation(float DeltaTime)
{
ApplyRotationYawSpeedAnimationCurve(DeltaTime);
if (RefreshCustomGroundedNotMovingRotation(DeltaTime))
{
return;
}
if (RotationMode == GMS_RotationModeTags::ViewDirection)
{
if (const auto* Setting = ControlSetting->ViewDirectionSetting.GetPtr<FGMS_ViewDirectionSetting_Default>())
{
if (Setting->bEnableRotationWhenNotMoving)
{
// const auto& TargetYawAngle = LocomotionState.bHasInput ? ViewState.Rotation.Yaw : LocomotionState.TargetYawAngle;
SetRotationExtraSmooth(ViewState.Rotation.Yaw, DeltaTime, Setting->RotationInterpolationSpeed, Setting->TargetYawAngleRotationSpeed);
return;
}
return;
}
if (const auto* Setting = ControlSetting->ViewDirectionSetting.GetPtr<FGMS_ViewDirectionSetting_Aiming>())
{
// refresh not moving aiming rotation.
{
if (Setting->bEnableRotationWhenNotMoving) // 吸附到视角
{
SetRotationExtraSmooth(ViewState.Rotation.Yaw, DeltaTime, Setting->RotationInterpolationSpeed, Setting->TargetYawAngleRotationSpeed);
return;
}
SetTargetYawAngle(ViewState.Rotation.Yaw);
FRotator NewActorRotation{GetOwner()->GetActorRotation()};
//Limit rotation so turn in place can catch up.
if (ConstrainAimingRotation(NewActorRotation, DeltaTime, true))
{
SetActorRotation(NewActorRotation);
}
}
return;
}
}
if (RotationMode == GMS_RotationModeTags::VelocityDirection)
{
if (const auto* Setting = ControlSetting->VelocityDirectionSetting.GetPtr<FGMS_VelocityDirectionSetting_Default>())
{
if (Setting->bEnableRotationWhenNotMoving)
{
SetRotationExtraSmooth(LocomotionState.TargetYawAngle, DeltaTime, Setting->RotationInterpolationSpeed, Setting->TargetYawAngleRotationSpeed);
return;
}
}
if (const auto* Setting = ControlSetting->VelocityDirectionSetting.GetPtr<FGMS_VelocityDirectionSetting_RateBased>())
{
if (Setting->bEnableRotationWhenNotMoving)
{
SetRotationInstant(DesiredVelocityYawAngle, ETeleportType::None);
return;
}
}
}
RefreshTargetYawAngleUsingActorRotation();
}
float UGMS_CharacterMovementSystemComponent::CalculateGroundedMovingRotationInterpolationSpeed(TObjectPtr<UCurveFloat> InterpolationSpeedCurve, float Default) const
{
// Calculate the rotation speed by using the rotation speed curve in the rotation settings. Using
// the curve in conjunction with the speed amount gives you a high level of control over the rotation
// rates for each speed. Increase the speed if the camera is rotating quickly for more responsive rotation.
const auto InterpolationHalfLife{
IsValid(InterpolationSpeedCurve)
? InterpolationSpeedCurve->GetFloatValue(FMath::Max(1.0f, GetMappedMovementSpeedLevel(UE_REAL_TO_FLOAT(LocomotionState.Velocity.Size2D()))))
: Default
};
static constexpr auto MinInterpolationHalfLifeMultiplier{0.333333f};
static constexpr auto ReferenceViewYawSpeed{300.0f};
return InterpolationHalfLife * UGMS_Math::LerpClamped(1.0f, MinInterpolationHalfLifeMultiplier,
ViewState.YawSpeed / ReferenceViewYawSpeed);
}
void UGMS_CharacterMovementSystemComponent::RefreshGroundedMovingRotation(float DeltaTime)
{
// Moving.
if (RefreshCustomGroundedMovingRotation(DeltaTime))
{
return;
}
if (RotationMode == GMS_RotationModeTags::ViewDirection)
{
if (const auto* Setting = ControlSetting->ViewDirectionSetting.GetPtr<FGMS_ViewDirectionSetting_Default>())
{
//TODO maybe use root yaw offset here?
float RotationYawOffset = AnimationInstance->GetCurveValue(UGMS_Constants::RotationYawOffsetCurveName());
const auto& TargetYawAngle = UE_REAL_TO_FLOAT(ViewState.Rotation.Yaw + RotationYawOffset);
GMS_CLOG(VeryVerbose, "rotating to target yaw angle(%f) with Yaw offset(%f)", TargetYawAngle, RotationYawOffset);
const float CalculatedRotationInterpolationSpeed = CalculateGroundedMovingRotationInterpolationSpeed(Setting->RotationInterpolationSpeedCurve, Setting->RotationInterpolationSpeed);
SetRotationExtraSmooth(TargetYawAngle, DeltaTime, CalculatedRotationInterpolationSpeed, Setting->TargetYawAngleRotationSpeed);
return;
}
if (const auto* Setting = ControlSetting->ViewDirectionSetting.GetPtr<FGMS_ViewDirectionSetting_Aiming>())
{
//TODO Should use this if using root yaw offset?
FRotator NewActorRotation{GetOwner()->GetActorRotation()};
SetTargetYawAngleSmooth(UE_REAL_TO_FLOAT(ViewState.Rotation.Yaw), DeltaTime, Setting->TargetYawAngleRotationSpeed);
GMS_CLOG(VeryVerbose, "wants to aiming to target yaw angle(%f) with view yaw(%f)", NewActorRotation.Yaw, ViewState.Rotation.Yaw);
NewActorRotation.Yaw = UGMS_Rotation::DamperExactAngle(
UE_REAL_TO_FLOAT(FMath::UnwindDegrees(NewActorRotation.Yaw)), LocomotionState.SmoothTargetYawAngle, DeltaTime, Setting->RotationInterpolationSpeed);
if (ConstrainAimingRotation(NewActorRotation, DeltaTime))
{
// Cancel the extra smooth rotation, otherwise the actor will rotate too weirdly.
LocomotionState.SmoothTargetYawAngle = LocomotionState.TargetYawAngle;
}
GMS_CLOG(VeryVerbose, "aiming to target yaw angle(%f)", NewActorRotation.Yaw);
SetActorRotation(NewActorRotation);
return;
}
}
if (RotationMode == GMS_RotationModeTags::VelocityDirection)
{
if (const auto* Setting = ControlSetting->VelocityDirectionSetting.GetPtr<FGMS_VelocityDirectionSetting_RateBased>())
{
SetRotationInstant(DesiredVelocityYawAngle, ETeleportType::None);
return;
}
if (const auto* Setting = ControlSetting->VelocityDirectionSetting.GetPtr<FGMS_VelocityDirectionSetting_Default>())
{
if (LocomotionState.bHasInput)
{
const auto& TargetRotation = Setting->bOrientateToMoveInputIntent ? LocomotionState.InputYawAngle : LocomotionState.VelocityYawAngle;
const float CalculatedRotationInterpolationSpeed = CalculateGroundedMovingRotationInterpolationSpeed(Setting->RotationInterpolationSpeedCurve, Setting->RotationInterpolationSpeed);
GMS_CLOG(VeryVerbose, "rotating to target yaw angle(%f) at rotation speed(%f)", TargetRotation, CalculatedRotationInterpolationSpeed);
SetRotationExtraSmooth(TargetRotation, DeltaTime, CalculatedRotationInterpolationSpeed, Setting->TargetYawAngleRotationSpeed);
}
return;
}
}
RefreshTargetYawAngleUsingActorRotation();
}
bool UGMS_CharacterMovementSystemComponent::ConstrainAimingRotation(FRotator& ActorRotation, float DeltaTime, bool bApplySecondaryConstraint)
{
const FGMS_ViewDirectionSetting_Aiming* Setting = ControlSetting->ViewDirectionSetting.GetPtr<FGMS_ViewDirectionSetting_Aiming>();
if (Setting == nullptr)
{
return false;
}
LocomotionState.bAimingLimitAppliedThisFrame = true;
if (LocomotionState.bResetAimingLimit)
{
LocomotionState.AimingYawAngleLimit = 180.0f;
}
// Limit the actor's rotation when aiming to prevent situations where the lower body noticeably
// fails to keep up with the rotation of the upper body when the camera is rotating very fast.
float ViewRelativeAngle{FRotator3f::NormalizeAxis(UE_REAL_TO_FLOAT(ViewState.Rotation.Yaw - ActorRotation.Yaw))};
if (FMath::Abs(ViewRelativeAngle) <= Setting->MinAimingYawAngleLimit + UE_KINDA_SMALL_NUMBER)
{
LocomotionState.AimingYawAngleLimit = Setting->MinAimingYawAngleLimit;
return false;
}
ViewRelativeAngle = UGMS_Rotation::RemapAngleForCounterClockwiseRotation(ViewRelativeAngle);
// Secondary constraint. Simply increases the actor's rotation speed. Typically only used when the actor is standing still.
if (bApplySecondaryConstraint)
{
static constexpr auto RotationInterpolationHalfLife{0.1f};
// Interpolate the angle only to the point where the constraints no longer apply to ensure a smoother completion of the rotation.
const auto TargetViewRelativeAngle{
FMath::Clamp(ViewRelativeAngle, -Setting->MinAimingYawAngleLimit,
Setting->MinAimingYawAngleLimit)
};
const auto DeltaAngle{FMath::UnwindDegrees(TargetViewRelativeAngle - ViewRelativeAngle)};
if (FMath::IsNearlyZero(DeltaAngle, UE_KINDA_SMALL_NUMBER))
{
ViewRelativeAngle = TargetViewRelativeAngle;
}
else
{
const auto InterpolationAmount{UGMS_Math::DamperExactAlpha(DeltaTime, RotationInterpolationHalfLife)};
ViewRelativeAngle = FMath::UnwindDegrees(ViewRelativeAngle + DeltaAngle * InterpolationAmount);
}
}
// Primary constraint. Prevents the actor from rotating beyond a certain angle relative to the camera.
if (FMath::Abs(ViewRelativeAngle) > LocomotionState.AimingYawAngleLimit + UE_KINDA_SMALL_NUMBER)
{
ViewRelativeAngle = FMath::Clamp(ViewRelativeAngle, -LocomotionState.AimingYawAngleLimit, LocomotionState.AimingYawAngleLimit);
}
else
{
LocomotionState.AimingYawAngleLimit = FMath::Max(FMath::Abs(ViewRelativeAngle), Setting->MinAimingYawAngleLimit);
}
const auto PreviousActorYawAngle{ActorRotation.Yaw};
ActorRotation.Yaw = FMath::UnwindDegrees(UE_REAL_TO_FLOAT(ViewState.Rotation.Yaw - ViewRelativeAngle));
// We use UE_KINDA_SMALL_NUMBER here because even if ViewRelativeAngle hasn't
// changed, converting it back to ActorRotation.Yaw may introduce a rounding
// error, and FMath::IsNearlyZero() with default arguments will return false.
return !FMath::IsNearlyZero(FMath::UnwindDegrees(ActorRotation.Yaw - PreviousActorYawAngle), UE_KINDA_SMALL_NUMBER);
}
bool UGMS_CharacterMovementSystemComponent::ApplyRotationYawSpeedAnimationCurve(float DeltaTime)
{
// Use curve to drive actor rotation only if no root bone rotation. 仅在没有使用根骨旋转时采用曲线驱动Actor旋转。
if (UGMS_MainAnimInstance* AnimInst = Cast<UGMS_MainAnimInstance>(MainAnimInstance))
{
if (AnimInst->GetOffsetRootBoneRotationMode() != EOffsetRootBoneMode::Release)
{
return false;
}
}
const float CurveValue = AnimationInstance->GetCurveValue(UGMS_Constants::RotationYawSpeedCurveName());
const float DeltaYawAngle{CurveValue * DeltaTime};
if (FMath::Abs(DeltaYawAngle) > UE_SMALL_NUMBER)
{
auto NewActorRotation{GetOwner()->GetActorRotation()};
NewActorRotation.Yaw += DeltaYawAngle;
SetActorRotation(NewActorRotation);
RefreshTargetYawAngleUsingActorRotation();
return true;
}
return false;
}
bool UGMS_CharacterMovementSystemComponent::RefreshCustomGroundedMovingRotation_Implementation(float DeltaTime)
{
return false;
}
bool UGMS_CharacterMovementSystemComponent::RefreshCustomGroundedNotMovingRotation_Implementation(float DeltaTime)
{
return false;
}
void UGMS_CharacterMovementSystemComponent::RefreshInAirRotation(const float DeltaTime)
{
if (LocomotionMode != GMS_MovementModeTags::InAir || GetGameplayTags().HasAny(InAirRotationBlockingTags))
{
return;
}
if (RotationMode == GMS_RotationModeTags::VelocityDirection || RotationMode == GMS_RotationModeTags::ViewDirection)
{
switch (ControlSetting->InAirRotationMode)
{
case EGMS_InAirRotationMode::RotateToVelocityOnJump:
if (LocomotionState.bMoving)
{
SetRotationSmooth(LocomotionState.VelocityYawAngle, DeltaTime, ControlSetting->InAirRotationInterpolationSpeed);
}
else
{
RefreshTargetYawAngleUsingActorRotation();
}
break;
case EGMS_InAirRotationMode::KeepRelativeRotation:
SetRotationSmooth(
FRotator3f::NormalizeAxis(UE_REAL_TO_FLOAT(ViewState.Rotation.Yaw) - LocomotionState.ViewRelativeTargetYawAngle),
DeltaTime, ControlSetting->InAirRotationInterpolationSpeed);
break;
default:
RefreshTargetYawAngleUsingActorRotation();
break;
}
}
else
{
RefreshTargetYawAngleUsingActorRotation();
}
}
void UGMS_CharacterMovementSystemComponent::SetRotationInstant_Implementation(const float TargetYawAngle, const ETeleportType Teleport)
{
SetTargetYawAngle(TargetYawAngle);
auto NewRotation{GetOwner()->GetActorRotation()};
NewRotation.Yaw = TargetYawAngle;
SetActorRotation(NewRotation);
}
void UGMS_CharacterMovementSystemComponent::SetRotationSmooth_Implementation(const float TargetYawAngle, const float DeltaTime, const float InterpolationHalfLife)
{
SetTargetYawAngle(TargetYawAngle);
auto DesiredRotation{OwnerPawn->GetActorRotation()};
DesiredRotation.Yaw = UGMS_Rotation::DamperExactAngle(UE_REAL_TO_FLOAT(FMath::UnwindDegrees(DesiredRotation.Yaw)),
LocomotionState.SmoothTargetYawAngle, DeltaTime, InterpolationHalfLife);
SetActorRotation(DesiredRotation);
}
void UGMS_CharacterMovementSystemComponent::SetRotationExtraSmooth_Implementation(const float TargetYawAngle, const float DeltaTime,
const float InterpolationHalfLife, const float TargetYawAngleRotationSpeed)
{
SetTargetYawAngleSmooth(TargetYawAngle, DeltaTime, TargetYawAngleRotationSpeed);
auto DesiredRotation{OwnerPawn->GetActorRotation()};
DesiredRotation.Yaw = UGMS_Rotation::DamperExactAngle(UE_REAL_TO_FLOAT(FMath::UnwindDegrees(DesiredRotation.Yaw)),
LocomotionState.SmoothTargetYawAngle, DeltaTime, InterpolationHalfLife);
SetActorRotation(DesiredRotation);
}
void UGMS_CharacterMovementSystemComponent::RefreshTargetYawAngleUsingActorRotation()
{
const auto YawAngle{UE_REAL_TO_FLOAT(OwnerPawn->GetActorRotation().Yaw)};
SetTargetYawAngle(YawAngle);
}
void UGMS_CharacterMovementSystemComponent::SetTargetYawAngle(const float TargetYawAngle)
{
LocomotionState.TargetYawAngle = FMath::UnwindDegrees(TargetYawAngle);
RefreshViewRelativeTargetYawAngle();
LocomotionState.SmoothTargetYawAngle = LocomotionState.TargetYawAngle;
}
void UGMS_CharacterMovementSystemComponent::SetTargetYawAngleSmooth(float TargetYawAngle, float DeltaTime, float RotationSpeed)
{
LocomotionState.TargetYawAngle = FMath::UnwindDegrees(TargetYawAngle);
LocomotionState.SmoothTargetYawAngle = UGMS_Rotation::InterpolateAngleConstant(
LocomotionState.SmoothTargetYawAngle, LocomotionState.TargetYawAngle, DeltaTime, RotationSpeed);
RefreshViewRelativeTargetYawAngle();
}
void UGMS_CharacterMovementSystemComponent::RefreshViewRelativeTargetYawAngle()
{
LocomotionState.ViewRelativeTargetYawAngle = FMath::UnwindDegrees(UE_REAL_TO_FLOAT(
ViewState.Rotation.Yaw - LocomotionState.TargetYawAngle));
}
void UGMS_CharacterMovementSystemComponent::SetActorRotation(FRotator DesiredRotation)
{
const bool bWantsToBeVertical = CharacterMovement->ShouldRemainVertical();
if (bWantsToBeVertical)
{
DesiredRotation.Pitch = 0.f;
DesiredRotation.Yaw = FMath::UnwindDegrees(DesiredRotation.Yaw);
DesiredRotation.Roll = 0.f;
}
else
{
DesiredRotation.Normalize();
}
OwnerPawn->SetActorRotation(DesiredRotation);
}
FGMS_PredictGroundMovementPivotLocationParams UGMS_CharacterMovementSystemComponent::GetPredictGroundMovementPivotLocationParams() const
{
FGMS_PredictGroundMovementPivotLocationParams Params;
if (CharacterMovement)
{
Params.Acceleration = CharacterMovement->GetCurrentAcceleration();
Params.Velocity = CharacterMovement->GetLastUpdateVelocity();
Params.GroundFriction = CharacterMovement->GroundFriction;
}
return Params;
}
FGMS_PredictGroundMovementStopLocationParams UGMS_CharacterMovementSystemComponent::GetPredictGroundMovementStopLocationParams() const
{
FGMS_PredictGroundMovementStopLocationParams Params;
if (CharacterMovement)
{
Params.Velocity = CharacterMovement->GetLastUpdateVelocity();
Params.bUseSeparateBrakingFriction = CharacterMovement->bUseSeparateBrakingFriction;
Params.BrakingFriction = CharacterMovement->BrakingFriction;
Params.GroundFriction = CharacterMovement->GroundFriction;
Params.BrakingFrictionFactor = CharacterMovement->BrakingFrictionFactor;
Params.BrakingDecelerationWalking = CharacterMovement->BrakingDecelerationWalking;
}
return Params;
}