959 lines
32 KiB
C++
959 lines
32 KiB
C++
// 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<FGMS_AnimStateNameToTag> 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<FGameplayTag> 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<APawn>(GetOwningActor());
|
|
|
|
#if WITH_EDITOR
|
|
if (GetWorld() && !GetWorld()->IsGameWorld() && !IsValid(PawnOwner))
|
|
{
|
|
// Use default objects for editor preview.
|
|
|
|
PawnOwner = GetMutableDefault<APawn>();
|
|
MovementSystem = PawnOwner->FindComponentByClass<UGMS_MovementSystemComponent>();
|
|
}
|
|
#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<UGMS_MovementSystemComponent>();
|
|
|
|
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<UGMS_CharacterMovementSystemComponent>(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<UGMS_AnimLayer>& 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<UGMS_AnimLayer> 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<UGMS_AnimLayer>(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<IPoseSearchTrajectoryPredictorInterface> 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<TObjectPtr<UAnimInstance>, 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<FGMS_ViewDirectionSetting_Aiming>())
|
|
{
|
|
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);
|
|
}
|