// 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& 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(GetOwner()); if (OwnerCharacter) { MovementState = DesiredMovementState; RotationMode = DesiredRotationMode; CharacterMovement = Cast(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()) { 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()) { // 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()) { if (Setting->bEnableRotationWhenNotMoving) { SetRotationExtraSmooth(LocomotionState.TargetYawAngle, DeltaTime, Setting->RotationInterpolationSpeed, Setting->TargetYawAngleRotationSpeed); return; } } if (const auto* Setting = ControlSetting->VelocityDirectionSetting.GetPtr()) { if (Setting->bEnableRotationWhenNotMoving) { SetRotationInstant(DesiredVelocityYawAngle, ETeleportType::None); return; } } } RefreshTargetYawAngleUsingActorRotation(); } float UGMS_CharacterMovementSystemComponent::CalculateGroundedMovingRotationInterpolationSpeed(TObjectPtr 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()) { //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()) { //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()) { SetRotationInstant(DesiredVelocityYawAngle, ETeleportType::None); return; } if (const auto* Setting = ControlSetting->VelocityDirectionSetting.GetPtr()) { 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(); 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(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; }