// Copyright 2025 https://yuewu.dev/en All Rights Reserved. #include "Locomotions/GMS_MainAnimInstance.h" #include "AnimationWarpingLibrary.h" #include "PoseSearch/PoseSearchTrajectoryPredictor.h" #include "DrawDebugHelpers.h" #include "GMS_CharacterMovementSystemComponent.h" #include "GMS_MovementSystemComponent.h" #include "KismetAnimationLibrary.h" #include "Curves/CurveFloat.h" #include "GameFramework/CharacterMovementComponent.h" #include "Kismet/KismetMathLibrary.h" #include "Locomotions/GMS_AnimLayer.h" #include "Locomotions/GMS_AnimLayer_Additive.h" #include "Locomotions/GMS_AnimLayer_Overlay.h" #include "Locomotions/GMS_AnimLayer_States.h" #include "Locomotions/GMS_AnimLayer_View.h" #include "Locomotions/GMS_AnimLayer_SkeletalControls.h" #include "PoseSearch/PoseSearchTrajectoryLibrary.h" #include "Utility/GMS_Log.h" #include "Utility/GMS_Math.h" #include "Utility/GMS_Utility.h" #include "Utility/GMS_Vector.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_MainAnimInstance) UGMS_MainAnimInstance::UGMS_MainAnimInstance() { RootMotionMode = ERootMotionMode::RootMotionFromMontagesOnly; MovementIntent = FVector::ZeroVector; } UGMS_MovementSystemComponent* UGMS_MainAnimInstance::GetMovementSystemComponent() const { return MovementSystem; } void UGMS_MainAnimInstance::RegisterStateNameToTagMapping(UAnimInstance* SourceAnimInstance, TArray Mapping) { if (SourceAnimInstance && SourceAnimInstance->Blueprint_GetMainAnimInstance() == this && !Mapping.IsEmpty()) { FGMS_AnimStateNameToTagWrapper Wrapper; Wrapper.AnimStateNameToTagMapping = Mapping; RuntimeAnimStateNameToTagMappings.Emplace(SourceAnimInstance, Wrapper); } } void UGMS_MainAnimInstance::UnregisterStateNameToTagMapping(UAnimInstance* SourceAnimInstance) { if (SourceAnimInstance && SourceAnimInstance->Blueprint_GetMainAnimInstance() == this && RuntimeAnimStateNameToTagMappings.Contains(SourceAnimInstance)) { TArray Tags; for (const FGMS_AnimStateNameToTag& Mapping : RuntimeAnimStateNameToTagMappings[SourceAnimInstance].AnimStateNameToTagMapping) { Tags.Add(Mapping.Tag); } NodeRelevanceTags.RemoveTags(FGameplayTagContainer::CreateFromArray(Tags)); RuntimeAnimStateNameToTagMappings.Remove(SourceAnimInstance); } } #pragma region Definition void UGMS_MainAnimInstance::RefreshLayerSettings_Implementation() { const FGMS_MovementSetSetting& MSSetting = MovementSystem->GetMovementSetSetting(); const auto& States = MSSetting.bUseInstancedStatesSetting ? MSSetting.AnimLayerSetting_States : MSSetting.DA_AnimLayerSetting_States; SetAnimLayerBySetting(States, StateLayerInstance); const auto& Overlay = MSSetting.bUseInstancedOverlaySetting ? MSSetting.AnimLayerSetting_Overlay : MSSetting.DA_AnimLayerSetting_Overlay; SetAnimLayerBySetting(Overlay, OverlayLayerInstance); SetAnimLayerBySetting(MSSetting.AnimLayerSetting_View, ViewLayerInstance); SetAnimLayerBySetting(MSSetting.AnimLayerSetting_Additive, AdditiveLayerInstance); SetAnimLayerBySetting(MSSetting.AnimLayerSetting_SkeletalControls, SkeletonControlsLayerInstance); } void UGMS_MainAnimInstance::SetOffsetRootBoneRotationMode_Implementation(EOffsetRootBoneMode NewRotationMode) { if (GeneralSetting.bEnableOffsetRootBoneRotation && RootState.RotationMode != NewRotationMode) { RootState.RotationMode = NewRotationMode; } } EOffsetRootBoneMode UGMS_MainAnimInstance::GetOffsetRootBoneRotationMode_Implementation() const { if (bAnyMontagePlaying) { return EOffsetRootBoneMode::Release; } // Temporal solution:prevent root rotation offset when standing at moving platform. // if (GetMovementSystemComponent() && GetMovementSystemComponent()->GetMovementBase().bHasRelativeRotation) // { // return EOffsetRootBoneMode::Release; // } return GeneralSetting.bEnableOffsetRootBoneRotation ? RootState.RotationMode : EOffsetRootBoneMode::Release; } void UGMS_MainAnimInstance::SetOffsetRootBoneTranslationMode_Implementation(EOffsetRootBoneMode NewTranslationMode) { if (GeneralSetting.bEnableOffsetRootBoneTranslation && RootState.TranslationMode != NewTranslationMode) { RootState.TranslationMode = NewTranslationMode; } } EOffsetRootBoneMode UGMS_MainAnimInstance::GetOffsetRootBoneTranslationMode_Implementation() const { if (bAnyMontagePlaying) { return EOffsetRootBoneMode::Release; } return GeneralSetting.bEnableOffsetRootBoneTranslation ? RootState.TranslationMode : EOffsetRootBoneMode::Release; } void UGMS_MainAnimInstance::OnLocomotionModeChanged_Implementation(const FGameplayTag& Prev) { check(IsInGameThread()) check(IsValid(MovementSystem)) // GMS_ANIMATION_CLOG(Verbose, "Refresh layer settings due to LocomotionMode Changed.") LocomotionMode = MovementSystem->GetLocomotionMode(); LocomotionModeContainer = LocomotionMode.GetSingleTagContainer(); if (Prev == GMS_MovementModeTags::InAir) { InAirState.bJumping = false; InAirState.bFalling = false; } bLocomotionModeChanged = true; GetWorld()->GetTimerManager().SetTimerForNextTick([this]() { bLocomotionModeChanged = false; }); } void UGMS_MainAnimInstance::OnRotationModeChanged_Implementation(const FGameplayTag& Prev) { check(IsInGameThread()) check(IsValid(MovementSystem)) check(IsValid(MovementSystem->GetControlSetting())) // GMS_ANIMATION_CLOG(Verbose, "Refresh layer settings due to RotationMode Changed.") RotationMode = MovementSystem->GetRotationMode(); RotationModeContainer = MovementSystem->GetRotationMode().GetSingleTagContainer(); RefreshLayerSettings(); bRotationModeChanged = true; GetWorld()->GetTimerManager().SetTimerForNextTick([this]() { bRotationModeChanged = false; }); } void UGMS_MainAnimInstance::OnMovementSetChanged_Implementation(const FGameplayTag& Prev) { check(IsInGameThread()) check(IsValid(MovementSystem)) // GMS_ANIMATION_CLOG(Verbose, "Refresh layer settings due to MovementSet Changed.") MovementSet = MovementSystem->GetMovementSet(); MovementSetContainer = MovementSet.GetSingleTagContainer(); RefreshLayerSettings(); bMovementSetChanged = true; GetWorld()->GetTimerManager().SetTimerForNextTick([this]() { bMovementSetChanged = false; }); } void UGMS_MainAnimInstance::OnMovementStateChanged_Implementation(const FGameplayTag& Prev) { check(IsInGameThread()) check(IsValid(MovementSystem)) // GMS_ANIMATION_CLOG(Verbose, "Refresh layer settings due to MovementState Changed.") MovementState = MovementSystem->GetMovementState(); MovementStateContainer = MovementState.GetSingleTagContainer(); RefreshLayerSettings(); bMovementStateChanged = true; GetWorld()->GetTimerManager().SetTimerForNextTick([this]() { bMovementStateChanged = false; }); } void UGMS_MainAnimInstance::OnOverlayModeChanged_Implementation(const FGameplayTag& Prev) { check(IsInGameThread()) check(IsValid(MovementSystem)) // GMS_ANIMATION_CLOG(Verbose, "Refresh layer settings due to OverlayMode Changed.") OverlayMode = MovementSystem->GetOverlayMode(); OverlayModeContainer = MovementSystem->GetOverlayMode().GetSingleTagContainer(); RefreshLayerSettings(); bOverlayModeChanged = true; GetWorld()->GetTimerManager().SetTimerForNextTick([this]() { bOverlayModeChanged = false; }); } #pragma endregion Definition void UGMS_MainAnimInstance::NativeInitializeAnimation() { Super::NativeInitializeAnimation(); PawnOwner = Cast(GetOwningActor()); #if WITH_EDITOR if (GetWorld() && !GetWorld()->IsGameWorld() && !IsValid(PawnOwner)) { // Use default objects for editor preview. PawnOwner = GetMutableDefault(); MovementSystem = PawnOwner->FindComponentByClass(); } #endif } void UGMS_MainAnimInstance::NativeUninitializeAnimation() { if (IsValid(MovementSystem)) { MovementSystem->OnLocomotionModeChangedEvent.RemoveDynamic(this, &ThisClass::OnLocomotionModeChanged); MovementSystem->OnRotationModeChangedEvent.RemoveDynamic(this, &ThisClass::OnRotationModeChanged); MovementSystem->OnMovementSetChangedEvent.RemoveDynamic(this, &ThisClass::OnMovementSetChanged); MovementSystem->OnMovementStateChangedEvent.RemoveDynamic(this, &ThisClass::OnMovementStateChanged); MovementSystem->OnOverlayModeChangedEvent.RemoveDynamic(this, &ThisClass::OnOverlayModeChanged); } if (InitialTimerHandle.IsValid()) { GetWorld()->GetTimerManager().ClearTimer(InitialTimerHandle); } Super::NativeUninitializeAnimation(); } void UGMS_MainAnimInstance::NativeBeginPlay() { Super::NativeBeginPlay(); ensure(PawnOwner); MovementSystem = PawnOwner->FindComponentByClass(); ensure(MovementSystem); if (IsValid(MovementSystem)) { // TrajectoryPredictor = MovementSystem->GetTrajectoryPredictor(); MovementSystem->MainAnimInstance = this; MovementSystem->OnLocomotionModeChangedEvent.AddDynamic(this, &ThisClass::OnLocomotionModeChanged); MovementSystem->OnRotationModeChangedEvent.AddDynamic(this, &ThisClass::OnRotationModeChanged); MovementSystem->OnMovementSetChangedEvent.AddDynamic(this, &ThisClass::OnMovementSetChanged); MovementSystem->OnMovementStateChangedEvent.AddDynamic(this, &ThisClass::OnMovementStateChanged); MovementSystem->OnOverlayModeChangedEvent.AddDynamic(this, &ThisClass::OnOverlayModeChanged); //Grab latest info and intialize. FTimerDelegate Delegate = FTimerDelegate::CreateLambda([this]() { InitialTimerHandle.Invalidate(); const FGMS_MovementSetSetting& MSSetting = MovementSystem->GetMovementSetSetting(); LocomotionMode = MovementSystem->GetLocomotionMode(); LocomotionModeContainer = LocomotionMode.GetSingleTagContainer(); MovementSet = MovementSystem->GetMovementSet(); MovementSetContainer = MovementSet.GetSingleTagContainer(); MovementState = MovementSystem->GetMovementState(); MovementStateContainer = MovementState.GetSingleTagContainer(); RotationMode = MovementSystem->GetRotationMode(); RotationModeContainer = MovementSystem->GetRotationMode().GetSingleTagContainer(); OverlayMode = MovementSystem->GetOverlayMode(); OverlayModeContainer = MovementSystem->GetOverlayMode().GetSingleTagContainer(); RefreshLayerSettings(); }); GetWorld()->GetTimerManager().SetTimer(InitialTimerHandle, Delegate, 0.2f, false); } else { GMS_ANIMATION_CLOG(Error, "Missing Movement system component, This anim instance(%s) will not work properly!", *GetClass()->GetName()) } } void UGMS_MainAnimInstance::NativeUpdateAnimation(const float DeltaTime) { DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGMS_MainAnimInstance::NativeUpdateAnimation"), STAT_GMS_MainAnimInstance_NativeUpdateAnimation, STATGROUP_GMS) TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__) Super::NativeUpdateAnimation(DeltaTime); if (!IsValid(PawnOwner) || !IsValid(MovementSystem)) { return; } if (UGMS_CharacterMovementSystemComponent* CharacterMovementSystemComponent = Cast(MovementSystem)) { if (!IsValid(CharacterMovementSystemComponent->GetCharacterMovement())) { return; } } RefreshStateOnGameThread(); RefreshRelevanceOnGameThread(); bAnyMontagePlaying = IsAnyMontagePlaying(); } void UGMS_MainAnimInstance::NativeThreadSafeUpdateAnimation(const float DeltaTime) { DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGMS_MainAnimInstance::NativeThreadSafeUpdateAnimation"), STAT_GMS_MainAnimInstance_NativeThreadSafeUpdateAnimation, STATGROUP_GMS) TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__) Super::NativeThreadSafeUpdateAnimation(DeltaTime); if (!IsValid(PawnOwner) || !IsValid(MovementSystem)) { return; } RefreshTrajectoryState(DeltaTime); RefreshLocomotion(DeltaTime); RefreshGrounded(); RefreshInAir(); RefreshView(DeltaTime); } void UGMS_MainAnimInstance::SetAnimLayerBySetting(const UGMS_AnimLayerSetting* LayerSetting, TObjectPtr& LayerInstance) { check(IsInGameThread() && IsValid(MovementSystem) && IsValid(MovementSystem->AnimGraphSetting)) //invalid setting if (!IsValid(LayerSetting)) { if (IsValid(LayerInstance)) { UnlinkAnimClassLayers(LayerInstance->GetClass()); LayerInstance->OnUnlinked(); LayerInstance = nullptr; } return; } TSubclassOf LayerClass = nullptr; if (!LayerSetting->GetOverrideAnimLayerClass(LayerClass)) { bool bValidMapping = MovementSystem->AnimGraphSetting->AnimLayerSettingToInstanceMapping.Contains(LayerSetting->GetClass()) && MovementSystem->AnimGraphSetting-> AnimLayerSettingToInstanceMapping[LayerSetting-> GetClass()] != nullptr; if (!bValidMapping) { GMS_ANIMATION_CLOG(Error, "Can't find exising anim instance mapping for anim layer setting(%s) or mapped a invalid anim instance. Please check anim graph setting:%s", *LayerSetting->GetClass()->GetName(), *MovementSystem->AnimGraphSetting->GetName()) if (IsValid(LayerInstance)) { UnlinkAnimClassLayers(LayerInstance->GetClass()); LayerInstance->OnUnlinked(); LayerInstance = nullptr; } return; } LayerClass = MovementSystem->AnimGraphSetting->AnimLayerSettingToInstanceMapping[LayerSetting->GetClass()]; } if (IsValid(LayerInstance) && LayerClass != LayerInstance->GetClass()) { UnlinkAnimClassLayers(LayerInstance->GetClass()); LayerInstance->OnUnlinked(); LayerInstance = nullptr; } if (!IsValid(LayerInstance)) { LinkAnimClassLayers(LayerClass); LayerInstance = Cast(GetLinkedAnimLayerInstanceByClass(LayerClass)); if (LayerInstance) { LayerInstance->OnLinked(); } else { GMS_ANIMATION_CLOG(Error, "Failed to link anim layer by class(%s), It will happen if this class doesn't implement any anim layer interface required on main anim instance. ", *LayerClass->GetName()); } } if (LayerInstance) { LayerInstance->ApplySetting(LayerSetting); } } void UGMS_MainAnimInstance::RefreshTrajectoryState(float DeltaTime) { // if (TScriptInterface Predictor = MovementSystem->GetTrajectoryPredictor()) // { // UPoseSearchTrajectoryLibrary::PoseSearchGenerateTransformTrajectoryWithPredictor(Predictor, GetDeltaSeconds(), TrajectoryState.Trajectory, TrajectoryState.DesiredControllerYaw, // TrajectoryState.Trajectory, -1.0f, 30, 0.1, 15); // // UPoseSearchTrajectoryLibrary::GetTransformTrajectoryVelocity(TrajectoryState.Trajectory, -0.3f, -0.4f, TrajectoryState.PastVelocity, false); // UPoseSearchTrajectoryLibrary::GetTransformTrajectoryVelocity(TrajectoryState.Trajectory, 0.0f, 0.2f, TrajectoryState.CurrentVelocity, false); // UPoseSearchTrajectoryLibrary::GetTransformTrajectoryVelocity(TrajectoryState.Trajectory, 0.4f, 0.5f, TrajectoryState.FutureVelocity, false); // } } void UGMS_MainAnimInstance::RefreshView(const float DeltaTime) { // ViewState.YawAngle = FRotator3f::NormalizeAxis(UE_REAL_TO_FLOAT(ViewState.Rotation.Yaw - LocomotionState.Rotation.Yaw - RootState.YawOffset)); ViewState.YawAngle = FRotator3f::NormalizeAxis(UE_REAL_TO_FLOAT(ViewState.Rotation.Yaw - LocomotionState.Rotation.Yaw)); ViewState.PitchAngle = FRotator3f::NormalizeAxis(UE_REAL_TO_FLOAT(ViewState.Rotation.Pitch - LocomotionState.Rotation.Pitch)); ViewState.PitchAmount = 0.5f - ViewState.PitchAngle / 180.0f; } void UGMS_MainAnimInstance::RefreshLocomotion(const float DeltaTime) { const auto& ActorTransform = GetOwningActor()->GetActorTransform(); const auto ActorDeltaTime{GetDeltaSeconds() * GetOwningActor()->CustomTimeDilation}; const auto bCanCalculateRateOfChange{ActorDeltaTime > UE_SMALL_NUMBER}; // update location data LocomotionState.PreviousDisplacement = (GetOwningActor()->GetActorLocation() - LocomotionState.Location).Size2D(); LocomotionState.Location = ActorTransform.GetLocation(); auto PreviousYawAngle{LocomotionState.Rotation.Yaw}; if (MovementBase.bHasRelativeRotation) { // Offset the angle to keep it relative to the movement base. PreviousYawAngle = FMath::UnwindDegrees(UE_REAL_TO_FLOAT(PreviousYawAngle + MovementBase.DeltaRotation.Yaw)); } // update rotation data LocomotionState.Rotation = ActorTransform.Rotator(); LocomotionState.RotationQuaternion = ActorTransform.GetRotation(); FVector PreviousVelocity{ MovementBase.bHasRelativeRotation ? MovementBase.DeltaRotation.RotateVector(LocomotionState.Velocity) : LocomotionState.Velocity }; if (bFirstUpdate) { LocomotionState.PreviousDisplacement = 0.0f; LocomotionState.DisplacementSpeed = 0.0f; PreviousVelocity = FVector::ZeroVector; } // update velocity data LocomotionState.bHasInput = GameLocomotionState.bHasInput; LocomotionState.Speed = GameLocomotionState.Speed; LocomotionState.DisplacementSpeed = GameLocomotionState.Speed; LocomotionState.Velocity = GameLocomotionState.Velocity; LocomotionState.VelocityAcceleration = bCanCalculateRateOfChange ? (LocomotionState.Velocity - PreviousVelocity) / DeltaTime : FVector::ZeroVector; bool bWasMovingLastUpdate = !LocomotionState.LocalVelocity2D.IsZero(); LocomotionState.LocalVelocity2D = LocomotionState.RotationQuaternion.UnrotateVector({LocomotionState.Velocity.X, LocomotionState.Velocity.Y, 0.0f}); LocomotionState.bHasVelocity = !FMath::IsNearlyZero(LocomotionState.LocalVelocity2D.SizeSquared2D()); LocomotionState.LocalVelocityYawAngle = UKismetAnimationLibrary::CalculateDirection(LocomotionState.Velocity.GetSafeNormal2D(), LocomotionState.Rotation); LocomotionState.LocalVelocityYawAngleWithOffset = LocomotionState.LocalVelocityYawAngle - RootState.YawOffset; //take root yaw offset in account. 考虑到Offset的方向 LocomotionState.LocalVelocityDirection = SelectCardinalDirectionFromAngle(LocomotionState.LocalVelocityYawAngleWithOffset, 10, LocomotionState.LocalVelocityDirection, bWasMovingLastUpdate); LocomotionState.LocalVelocityDirectionNoOffset = SelectCardinalDirectionFromAngle(LocomotionState.LocalVelocityYawAngle, 10, LocomotionState.LocalVelocityDirectionNoOffset, bWasMovingLastUpdate); LocomotionState.LocalVelocityOctagonalDirection = SelectOctagonalDirectionFromAngle(LocomotionState.LocalVelocityYawAngleWithOffset, 10, LocomotionState.LocalVelocityOctagonalDirection, bWasMovingLastUpdate); LocomotionState.LocalAcceleration2D = UKismetMathLibrary::LessLess_VectorRotator({MovementIntent.X, MovementIntent.Y, 0.0f}, LocomotionState.Rotation); LocomotionState.bMoving = GameLocomotionState.bMoving; LocomotionState.YawVelocity = bCanCalculateRateOfChange ? FMath::UnwindDegrees(UE_REAL_TO_FLOAT( LocomotionState.Rotation.Yaw - PreviousYawAngle)) / ActorDeltaTime : 0.0f; bFirstUpdate = false; } void UGMS_MainAnimInstance::RefreshBlock() { bBlocked = UKismetMathLibrary::VSizeXY(MovementIntent) > 0.1 && LocomotionState.Speed < 200.0f && UKismetMathLibrary::InRange_FloatFloat(FVector::DotProduct(MovementIntent.GetSafeNormal(0.0001), LocomotionState.Velocity.GetSafeNormal(0.0001)), -0.6f, 0.6, true, true); } void UGMS_MainAnimInstance::RefreshStateOnGameThread() { LocomotionMode = MovementSystem->GetLocomotionMode(); LocomotionModeContainer = LocomotionMode.GetSingleTagContainer(); MovementSet = MovementSystem->GetMovementSet(); MovementSetContainer = MovementSet.GetSingleTagContainer(); MovementState = MovementSystem->GetMovementState(); MovementStateContainer = MovementState.GetSingleTagContainer(); RotationMode = MovementSystem->GetRotationMode(); RotationModeContainer = MovementSystem->GetRotationMode().GetSingleTagContainer(); OverlayMode = MovementSystem->GetOverlayMode(); OverlayModeContainer = MovementSystem->GetOverlayMode().GetSingleTagContainer(); OwnedTags = MovementSystem->GetGameplayTags(); ControlSetting = MovementSystem->GetControlSetting(); GeneralSetting = MovementSystem->GetMovementSetSetting().AnimDataSetting_General; // Apply latest root setting if diff. if (!GeneralSetting.bEnableOffsetRootBoneRotation && RootState.RotationMode != EOffsetRootBoneMode::Release) { RootState.RotationMode = EOffsetRootBoneMode::Release; } if (!GeneralSetting.bEnableOffsetRootBoneTranslation && RootState.TranslationMode != EOffsetRootBoneMode::Release) { RootState.TranslationMode = EOffsetRootBoneMode::Release; } const auto& View{MovementSystem->GetViewState()}; ViewState.Rotation = View.Rotation; ViewState.YawSpeed = View.YawSpeed; MovementBase = MovementSystem->GetMovementBase(); GameLocomotionState = MovementSystem->GetLocomotionState(); MovementIntent = MovementSystem->GetMovementIntent(); LocomotionState.MaxAcceleration = MovementSystem->GetMaxAcceleration(); LocomotionState.MaxBrakingDeceleration = MovementSystem->GetMaxBrakingDeceleration(); LocomotionState.WalkableFloorZ = MovementSystem->GetWalkableFloorZ(); LocomotionState.Scale = UE_REAL_TO_FLOAT(GetSkelMeshComponent()->GetComponentScale().Z); LocomotionState.CapsuleRadius = MovementSystem->GetScaledCapsuleRadius(); LocomotionState.CapsuleHalfHeight = MovementSystem->GetScaledCapsuleHalfHeight(); } void UGMS_MainAnimInstance::RefreshRelevanceOnGameThread() { if (!IsValid(this)) { return; } FGameplayTagContainer TagsToAdd; for (int i = 0; i < AnimStateNameToTagMapping.Num(); ++i) { if (AnimStateNameToTagMapping[i].State.IsRelevant(*this)) { TagsToAdd.AddTagFast(AnimStateNameToTagMapping[i].Tag); } } for (const TTuple, FGMS_AnimStateNameToTagWrapper>& Pair : RuntimeAnimStateNameToTagMappings) { if (IsValid(Pair.Key)) { for (int i = 0; i < Pair.Value.AnimStateNameToTagMapping.Num(); ++i) { if (Pair.Value.AnimStateNameToTagMapping[i].State.IsRelevant(*Pair.Key)) { TagsToAdd.AddTagFast(Pair.Value.AnimStateNameToTagMapping[i].Tag); } } } } NodeRelevanceTags = TagsToAdd; } void UGMS_MainAnimInstance::RefreshGrounded() { #if WITH_EDITOR if (!IsValid(GetWorld()) || !GetWorld()->IsGameWorld()) { return; } #endif DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGMS_MainAnimInstance::RefreshGrounded"), STAT_GMS_MainAnimInstance_RefreshGrounded, STATGROUP_GMS) TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__) if (LocomotionMode != GMS_MovementModeTags::Grounded) { return; } RefreshBlock(); RefreshGroundedLean(); } void UGMS_MainAnimInstance::RefreshGroundedLean() { const auto TargetLeanAmount{GetRelativeAccelerationAmount()}; const auto DeltaTime{GetDeltaSeconds()}; LeanState.RightAmount = FMath::FInterpTo(LeanState.RightAmount, TargetLeanAmount.Y, DeltaTime, GeneralSetting.LeanInterpolationSpeed); LeanState.ForwardAmount = FMath::FInterpTo(LeanState.ForwardAmount, TargetLeanAmount.X, DeltaTime, GeneralSetting.LeanInterpolationSpeed); } FVector2f UGMS_MainAnimInstance::GetRelativeAccelerationAmount() const { // This value represents the current amount of acceleration / deceleration relative to the // character rotation. It is normalized to a range of -1 to 1 so that -1 equals the max // braking deceleration and 1 equals the max acceleration of the character movement component. const auto MaxAcceleration{ (MovementIntent | LocomotionState.Velocity) >= 0.0f ? LocomotionState.MaxAcceleration : LocomotionState.MaxBrakingDeceleration }; // relative to root bone transform. const FVector3f RelativeAcceleration{ RootState.RootTransform.GetRotation().UnrotateVector(LocomotionState.VelocityAcceleration) }; return FVector2f{UGMS_Vector::ClampMagnitude01(RelativeAcceleration / MaxAcceleration)}; } void UGMS_MainAnimInstance::RefreshInAir() { #if WITH_EDITOR if (!IsValid(GetWorld()) || !GetWorld()->IsGameWorld()) { return; } #endif DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGMS_MainAnimInstance::RefreshInAir"), STAT_GMS_MainAnimInstance_RefreshInAir, STATGROUP_GMS) TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__) // InAirState.bJumping = false; // InAirState.bFalling = false; if (LocomotionMode != GMS_MovementModeTags::InAir) { // InAirState.VerticalSpeed = 0.0f; Land calculation need it. return; } // A separate variable for vertical speed is used to determine at what speed the character landed on the ground. if (LocomotionState.Velocity.Z > 0) { InAirState.bJumping = true; InAirState.TimeToJumpApex = (0 - LocomotionState.Velocity.Z) / MovementSystem->GetGravityZ(); InAirState.FallingTime = 0; } else { InAirState.bFalling = true; InAirState.TimeToJumpApex = 0; InAirState.FallingTime += GetDeltaSeconds(); } InAirState.VerticalSpeed = UE_REAL_TO_FLOAT(LocomotionState.Velocity.Z); RefreshGroundPrediction(); RefreshInAirLean(); } void UGMS_MainAnimInstance::RefreshGroundPrediction() { if (!bEnableGroundPrediction) { return; } static constexpr auto VerticalVelocityThreshold{-200.0f}; if (InAirState.VerticalSpeed > VerticalVelocityThreshold) { InAirState.bValidGround = false; InAirState.GroundDistance = -1.0f; return; } const auto SweepStartLocation{LocomotionState.Location}; static constexpr auto MinVerticalVelocity{-4000.0f}; static constexpr auto MaxVerticalVelocity{-200.0f}; auto VelocityDirection{LocomotionState.Velocity}; VelocityDirection.Z = FMath::Clamp(VelocityDirection.Z, MinVerticalVelocity, MaxVerticalVelocity); VelocityDirection.Normalize(); static constexpr auto MinSweepDistance{150.0f}; static constexpr auto MaxSweepDistance{2000.0f}; const auto SweepVector{ VelocityDirection * FMath::GetMappedRangeValueClamped(FVector2f{MaxVerticalVelocity, MinVerticalVelocity}, {MinSweepDistance, MaxSweepDistance}, InAirState.VerticalSpeed) * LocomotionState.Scale }; FHitResult Hit; GetWorld()->SweepSingleByChannel(Hit, SweepStartLocation, SweepStartLocation + SweepVector, FQuat::Identity, GeneralSetting.GroundPredictionSweepChannel, FCollisionShape::MakeCapsule(LocomotionState.CapsuleRadius, LocomotionState.CapsuleHalfHeight), {__FUNCTION__, false, PawnOwner}, GeneralSetting.GroundPredictionSweepResponses); const auto bGroundValid{Hit.IsValidBlockingHit() && Hit.ImpactNormal.Z >= LocomotionState.WalkableFloorZ}; InAirState.bValidGround = bGroundValid; InAirState.GroundDistance = Hit.Distance; } void UGMS_MainAnimInstance::RefreshInAirLean() { if (GeneralSetting.InAirLeanAmountCurve == nullptr) { return; } // Use the relative velocity direction and amount to determine how much the character should lean // while in air. The lean amount curve gets the vertical velocity and is used as a multiplier to // smoothly reverse the leaning direction when transitioning from moving upwards to moving downwards. static constexpr auto ReferenceSpeed{350.0f}; const auto RelativeVelocity{ FVector3f{LocomotionState.RotationQuaternion.UnrotateVector(LocomotionState.Velocity)} / ReferenceSpeed * GeneralSetting.InAirLeanAmountCurve->GetFloatValue(InAirState.VerticalSpeed) }; const auto DeltaTime{GetDeltaSeconds()}; LeanState.RightAmount = FMath::FInterpTo(LeanState.RightAmount, RelativeVelocity.Y, DeltaTime, GeneralSetting.LeanInterpolationSpeed); LeanState.ForwardAmount = FMath::FInterpTo(LeanState.ForwardAmount, RelativeVelocity.X, DeltaTime, GeneralSetting.LeanInterpolationSpeed); } void UGMS_MainAnimInstance::RefreshOffsetRootBone_Implementation(FAnimUpdateContext& Context, FAnimNodeReference& Node) { if (GetMovementSystemComponent() && GetMovementSystemComponent()->GetControlSetting()) { // 获取 OffsetRootBone 节点的根骨骼变换(世界空间) auto RootBoneTransform = UAnimationWarpingLibrary::GetOffsetRootTransform(Node); // 始终将 RootState.RootTransform 设置为世界空间变换(应用 +90 度 Yaw 调整) FRotator RootBoneRotation = FRotator(RootBoneTransform.Rotator().Pitch, RootBoneTransform.Rotator().Yaw + 90.0f, RootBoneTransform.Rotator().Roll); RootState.RootTransform = FTransform(RootBoneRotation, RootBoneTransform.GetTranslation(), RootBoneTransform.GetScale3D()); // 直接使用世界空间旋转计算 YawOffset RootState.YawOffset = UKismetMathLibrary::NormalizeAxis(RootBoneRotation.Yaw - LocomotionState.Rotation.Yaw); if (RotationMode == GMS_RotationModeTags::ViewDirection) { if (const FGMS_ViewDirectionSetting_Aiming* Setting = GetMovementSystemComponent()->GetControlSetting()->ViewDirectionSetting.GetPtr()) { if (FMath::Abs(RootState.YawOffset) <= Setting->MinAimingYawAngleLimit + UE_KINDA_SMALL_NUMBER) { RootState.MaxRotationError = Setting->MinAimingYawAngleLimit; return; } } } // no limit. RootState.MaxRotationError = -1.0f; } } float UGMS_MainAnimInstance::GetCurveValueClamped01(const FName& CurveName) const { return UGMS_Math::Clamp01(GetCurveValue(CurveName)); } UBlendProfile* UGMS_MainAnimInstance::GetNamedBlendProfile(const FName& BlendProfileName) const { if (CurrentSkeleton) { return CurrentSkeleton->GetBlendProfile(BlendProfileName); } return nullptr; } FGameplayTagContainer UGMS_MainAnimInstance::GetAggregatedTags() const { FGameplayTagContainer Result = OwnedTags; Result.AppendTags(NodeRelevanceTags); return Result; } float UGMS_MainAnimInstance::GetAOYawValue() const { if (RootState.RotationMode == EOffsetRootBoneMode::Release) { return ViewState.YawAngle; } return -RootState.YawOffset; } EGMS_MovementDirection UGMS_MainAnimInstance::SelectCardinalDirectionFromAngle(float Angle, float DeadZone, EGMS_MovementDirection CurrentDirection, bool bUseCurrentDirection) const { const float AbsAngle = FMath::Abs(Angle); float FwdDeadZone = DeadZone; float BwdDeadZone = DeadZone; if (bUseCurrentDirection) { if (CurrentDirection == EGMS_MovementDirection::Forward) { FwdDeadZone *= 2; } if (CurrentDirection == EGMS_MovementDirection::Backward) { BwdDeadZone *= 2; } } if (AbsAngle <= 45 + FwdDeadZone) { return EGMS_MovementDirection::Forward; } if (AbsAngle >= 135 - BwdDeadZone) { return EGMS_MovementDirection::Backward; } if (Angle > 0) { return EGMS_MovementDirection::Right; } return EGMS_MovementDirection::Left; } EGMS_MovementDirection_8Way UGMS_MainAnimInstance::SelectOctagonalDirectionFromAngle(float Angle, float DeadZone, EGMS_MovementDirection_8Way CurrentDirection, bool bUseCurrentDirection) const { const float AbsAngle = FMath::Abs(Angle); float FwdDeadZone = DeadZone; float BwdDeadZone = DeadZone; if (bUseCurrentDirection) { if (CurrentDirection == EGMS_MovementDirection_8Way::Forward) { FwdDeadZone *= 2; } if (CurrentDirection == EGMS_MovementDirection_8Way::Backward) { BwdDeadZone *= 2; } } if (AbsAngle <= 22.5f + FwdDeadZone) { return EGMS_MovementDirection_8Way::Forward; } if (AbsAngle >= 157.5f - BwdDeadZone) { return EGMS_MovementDirection_8Way::Backward; } if (Angle >= 22.5f && Angle < 67.5f) { return EGMS_MovementDirection_8Way::ForwardRight; } if (Angle >= 67.5f && Angle < 112.5f) { return EGMS_MovementDirection_8Way::Right; } if (Angle >= 112.5f && Angle < 157.5f) { return EGMS_MovementDirection_8Way::BackwardRight; } if (Angle >= -157.5f && Angle < -112.5f) { return EGMS_MovementDirection_8Way::BackwardLeft; } if (Angle >= -112.5f && Angle < -67.5f) { return EGMS_MovementDirection_8Way::Left; } return EGMS_MovementDirection_8Way::ForwardLeft; } EGMS_MovementDirection UGMS_MainAnimInstance::GetOppositeCardinalDirection(EGMS_MovementDirection CurrentDirection) const { switch (CurrentDirection) { case EGMS_MovementDirection::Forward: return EGMS_MovementDirection::Backward; case EGMS_MovementDirection::Backward: return EGMS_MovementDirection::Forward; case EGMS_MovementDirection::Left: return EGMS_MovementDirection::Right; case EGMS_MovementDirection::Right: return EGMS_MovementDirection::Left; default: return CurrentDirection; } } bool UGMS_MainAnimInstance::HasCoreStateChanges() const { return bMovementSetChanged || bMovementStateChanged || bLocomotionModeChanged || bOverlayModeChanged || bRotationModeChanged; } bool UGMS_MainAnimInstance::CheckCoreStateChanges(bool bCheckLocomotionMode, bool bCheckMovementSet, bool bCheckRotationMode, bool bCheckMovementState, bool bCheckOverlayMode) const { return (bCheckLocomotionMode && bLocomotionModeChanged) || (bCheckMovementSet && bMovementSetChanged) || (bCheckRotationMode && bRotationModeChanged) || (bCheckMovementState && bMovementStateChanged) || (bCheckOverlayMode && bOverlayModeChanged); }