第一次提交

This commit is contained in:
不明不惑
2026-03-03 01:23:02 +08:00
commit 3e434877e8
1053 changed files with 102411 additions and 0 deletions

View File

@@ -0,0 +1,898 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GMS_MovementSystemComponent.h"
#include "GameFramework/Pawn.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "GameplayTagAssetInterface.h"
#include "GameFramework/Actor.h"
#include "GameFramework/Pawn.h"
#include "Animation/AnimInstance.h"
#include "Misc/DataValidation.h"
#include "Net/Core/PushModel/PushModel.h"
#include "Net/UnrealNetwork.h"
#include "Settings/GMS_SettingObjectLibrary.h"
#include "Utility/GMS_Log.h"
#include "Utility/GMS_Math.h"
#include "Utility/GMS_Vector.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_MovementSystemComponent)
UGMS_MovementSystemComponent::UGMS_MovementSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
PrimaryComponentTick.bCanEverTick = true;
bWantsInitializeComponent = true;
}
void UGMS_MovementSystemComponent::PostLoad()
{
Super::PostLoad();
#if WITH_EDITOR
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (!MovementDefinitions.IsEmpty())
{
MovementDefinition = MovementDefinitions.Last();
MovementDefinitions.Empty();
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
#endif
}
void UGMS_MovementSystemComponent::InitializeComponent()
{
Super::InitializeComponent();
OwnerPawn = Cast<APawn>(GetOwner());
check(OwnerPawn)
if (OwnerPawn)
{
// Set some default values here to ensure that the animation instance and the
// camera component can read the most up-to-date values during their initialization.
SetReplicatedViewRotation(OwnerPawn->GetViewRotation().GetNormalized(), false);
ViewState.Rotation = ReplicatedViewRotation;
ViewState.PreviousYawAngle = UE_REAL_TO_FLOAT(OwnerPawn->GetViewRotation().Yaw);
const auto YawAngle{UE_REAL_TO_FLOAT(OwnerPawn->GetActorRotation().Yaw)};
LocomotionState.InputYawAngle = YawAngle;
LocomotionState.VelocityYawAngle = YawAngle;
}
}
void UGMS_MovementSystemComponent::BeginPlay()
{
Super::BeginPlay();
if (GetOwner()->GetClass()->ImplementsInterface(UGameplayTagAssetInterface::StaticClass()))
{
SetGameplayTagsProvider(GetOwner());
}
else
{
TArray<UActorComponent*> Components = GetOwner()->GetComponentsByInterface(UGameplayTagAssetInterface::StaticClass());
if (Components.IsValidIndex(0))
{
SetGameplayTagsProvider(Components[0]);
}
}
}
void UGMS_MovementSystemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
FDoRepLifetimeParams Parameters;
Parameters.bIsPushBased = true;
Parameters.Condition = COND_SkipOwner;
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, LocomotionMode, Parameters)
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, OverlayMode, Parameters)
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, MovementSet, Parameters)
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, MovementDefinition, Parameters)
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, ReplicatedViewRotation, Parameters)
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, DesiredVelocityYawAngle, Parameters)
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, OwnedTags, Parameters)
}
UGMS_MovementSystemComponent* UGMS_MovementSystemComponent::GetMovementSystemComponent(const AActor* Actor)
{
return Actor != nullptr ? Actor->FindComponentByClass<UGMS_MovementSystemComponent>() : nullptr;
}
bool UGMS_MovementSystemComponent::K2_FindMovementComponent(const AActor* Actor, UGMS_MovementSystemComponent*& Instance)
{
if (Actor == nullptr)
{
return false;
}
Instance = GetMovementSystemComponent(Actor);
return Instance != nullptr;
}
bool UGMS_MovementSystemComponent::K2_FindMovementComponentExt(const AActor* Actor, TSubclassOf<UGMS_MovementSystemComponent> DesiredClass, UGMS_MovementSystemComponent*& Instance)
{
if (DesiredClass)
{
Instance = GetMovementSystemComponent(Actor);
return Instance != nullptr && Instance->GetClass()->IsChildOf(DesiredClass);
}
return false;
}
FGameplayTagContainer UGMS_MovementSystemComponent::GetGameplayTags() const
{
if (IGameplayTagAssetInterface* TagAssetInterface = Cast<IGameplayTagAssetInterface>(GameplayTagsProvider))
{
FGameplayTagContainer RetTags;
TagAssetInterface->GetOwnedGameplayTags(RetTags);
if (!OwnedTags.IsEmpty())
{
RetTags.AppendTags(OwnedTags);
}
RetTags.AddTagFast(GetMovementState());
RetTags.AddTagFast(GetRotationMode());
return RetTags;
}
return OwnedTags;
}
void UGMS_MovementSystemComponent::SetGameplayTagsProvider(UObject* Provider)
{
if (!IsValid(Provider))
{
GMS_CLOG(Warning, "Passed invalid GameplayTagsProvider.");
return;
}
if (IGameplayTagAssetInterface* TagAssetInterface = Cast<IGameplayTagAssetInterface>(Provider))
{
GameplayTagsProvider = Provider;
}
else
{
GMS_CLOG(Warning, "Passed in GameplayTagsProvider(%s) Doesn't implement GameplayTagAssetInterface, it can't provide gameplay tags.", *Provider->GetName());
}
}
#pragma region GameplayTags
void UGMS_MovementSystemComponent::AddGameplayTag(FGameplayTag TagToAdd)
{
AddGameplayTag(TagToAdd, true);
}
void UGMS_MovementSystemComponent::RemoveGameplay(FGameplayTag TagToRemove)
{
RemoveGameplayTag(TagToRemove, true);
}
void UGMS_MovementSystemComponent::SetGameplayTags(FGameplayTagContainer TagsToSet)
{
SetGameplayTags(TagsToSet, true);
}
void UGMS_MovementSystemComponent::AddGameplayTag(const FGameplayTag& TagToAdd, bool bSendRpc)
{
if (GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy)
{
return;
}
OwnedTags.AddTag(TagToAdd);
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, OwnedTags, this)
if (bSendRpc)
{
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
{
ClientAddGameplayTag(TagToAdd);
}
else
{
ServerAddGameplayTag(TagToAdd);
}
}
}
void UGMS_MovementSystemComponent::RemoveGameplayTag(const FGameplayTag& TagToRemove, bool bSendRpc)
{
if (GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy)
{
return;
}
OwnedTags.RemoveTag(TagToRemove);
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, OwnedTags, this)
if (bSendRpc)
{
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
{
ClientRemoveGameplayTag(TagToRemove);
}
else
{
ServerRemoveGameplayTag(TagToRemove);
}
}
}
void UGMS_MovementSystemComponent::SetGameplayTags(const FGameplayTagContainer& TagsToSet, bool bSendRpc)
{
if (GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy)
{
return;
}
OwnedTags = TagsToSet;
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, OwnedTags, this)
if (bSendRpc)
{
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
{
ClientSetGameplayTags(TagsToSet);
}
else
{
ServerSetGameplayTags(TagsToSet);
}
}
}
void UGMS_MovementSystemComponent::ClientAddGameplayTag_Implementation(const FGameplayTag& TagToAdd)
{
AddGameplayTag(TagToAdd, false);
}
void UGMS_MovementSystemComponent::ServerAddGameplayTag_Implementation(const FGameplayTag& TagToAdd)
{
AddGameplayTag(TagToAdd, false);
}
void UGMS_MovementSystemComponent::ClientRemoveGameplayTag_Implementation(const FGameplayTag& TagToRemove)
{
RemoveGameplayTag(TagToRemove, false);
}
void UGMS_MovementSystemComponent::ServerRemoveGameplayTag_Implementation(const FGameplayTag& TagToRemove)
{
RemoveGameplayTag(TagToRemove, false);
}
void UGMS_MovementSystemComponent::ClientSetGameplayTags_Implementation(const FGameplayTagContainer& TagsToSet)
{
SetGameplayTags(TagsToSet, false);
}
void UGMS_MovementSystemComponent::ServerSetGameplayTags_Implementation(const FGameplayTagContainer& TagsToSet)
{
SetGameplayTags(TagsToSet, false);
}
#pragma endregion GameplayTags
#pragma region Locomotion
const FGMS_LocomotionState& UGMS_MovementSystemComponent::GetLocomotionState() const
{
return LocomotionState;
}
TScriptInterface<IPoseSearchTrajectoryPredictorInterface> UGMS_MovementSystemComponent::GetTrajectoryPredictor() const
{
return nullptr;
}
bool UGMS_MovementSystemComponent::IsCrouching() const
{
return false;
}
float UGMS_MovementSystemComponent::GetMaxSpeed() const
{
return 0.0;
}
float UGMS_MovementSystemComponent::GetMaxAcceleration() const
{
return 1000.0f;
}
bool UGMS_MovementSystemComponent::IsMovingOnGround() const
{
return true;
}
void UGMS_MovementSystemComponent::SetDesiredVelocityYawAngle(float NewDesiredVelocityYawAngle)
{
COMPARE_ASSIGN_AND_MARK_PROPERTY_DIRTY(ThisClass, DesiredVelocityYawAngle, NewDesiredVelocityYawAngle, this);
}
void UGMS_MovementSystemComponent::ServerSetDesiredVelocityYawAngle_Implementation(float NewDesiredVelocityYawAngle)
{
SetDesiredVelocityYawAngle(NewDesiredVelocityYawAngle);
}
const FGameplayTag& UGMS_MovementSystemComponent::GetLocomotionMode() const
{
return LocomotionMode;
}
const FGMS_MovementBaseState& UGMS_MovementSystemComponent::GetMovementBase() const
{
return MovementBase;
}
void UGMS_MovementSystemComponent::SetLocomotionMode(const FGameplayTag& NewLocomotionMode)
{
if (LocomotionMode != NewLocomotionMode)
{
const auto PreviousLocomotionMode{LocomotionMode};
LocomotionMode = NewLocomotionMode;
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, LocomotionMode, this)
OnLocomotionModeChanged(PreviousLocomotionMode);
}
}
void UGMS_MovementSystemComponent::OnReplicated_LocomotionMode(const FGameplayTag& PreviousLocomotionMode)
{
OnLocomotionModeChanged(PreviousLocomotionMode);
}
void UGMS_MovementSystemComponent::OnLocomotionModeChanged_Implementation(const FGameplayTag& PreviousLocomotionMode)
{
GMS_CLOG(Verbose, "locomotion mode changed from %s to %s", *PreviousLocomotionMode.ToString(), *LocomotionMode.ToString());
OnLocomotionModeChangedEvent.Broadcast(PreviousLocomotionMode);
}
#pragma endregion
void UGMS_MovementSystemComponent::RefreshMovementBase()
{
}
#pragma region MovementSet
const FGameplayTag& UGMS_MovementSystemComponent::GetMovementSet() const
{
return MovementSet;
}
const FGMS_MovementSetSetting& UGMS_MovementSystemComponent::GetMovementSetSetting() const
{
return MovementSetSetting;
}
const FGMS_MovementStateSetting& UGMS_MovementSystemComponent::GetMovementStateSetting() const
{
return MovementStateSetting;
}
const UGMS_MovementControlSetting_Default* UGMS_MovementSystemComponent::GetControlSetting() const
{
return ControlSetting;
}
int32 UGMS_MovementSystemComponent::GetNumOfMovementStateSettings() const
{
return ControlSetting->MovementStates.Num();
}
TSoftObjectPtr<const UGMS_MovementDefinition> UGMS_MovementSystemComponent::GetMovementDefinition() const
{
return MovementDefinition;
}
TSoftObjectPtr<const UGMS_MovementDefinition> UGMS_MovementSystemComponent::GetPrevMovementDefinition() const
{
return PrevMovementDefinition;
}
const UGMS_MovementDefinition* UGMS_MovementSystemComponent::GetLoadedMovementDefinition() const
{
return MovementDefinition.IsValid() ? MovementDefinition.Get() : nullptr;
}
void UGMS_MovementSystemComponent::SetMovementSet(const FGameplayTag& NewMovementSet)
{
SetMovementSet(NewMovementSet, true);
}
void UGMS_MovementSystemComponent::SetMovementDefinition(TSoftObjectPtr<UGMS_MovementDefinition> NewDefinition)
{
if (NewDefinition.IsNull())
{
return;
}
if (NewDefinition != MovementDefinition)
{
auto LoadedDefinition = NewDefinition.LoadSynchronous();
if (LoadedDefinition->MovementSets.Contains(MovementSet))
{
InternalSetMovementDefinition(NewDefinition, true);
}
}
}
void UGMS_MovementSystemComponent::LocalSetMovementDefinition(TSoftObjectPtr<UGMS_MovementDefinition> NewDefinition)
{
InternalSetMovementDefinition(NewDefinition, false);
}
void UGMS_MovementSystemComponent::PushAvailableMovementDefinition(TSoftObjectPtr<UGMS_MovementDefinition> NewDefinition, bool bPopCurrent)
{
InternalSetMovementDefinition(NewDefinition, true);
}
void UGMS_MovementSystemComponent::PopAvailableMovementDefinition()
{
// PopMovementDefinition(true);
}
void UGMS_MovementSystemComponent::InternalSetMovementDefinition(const TSoftObjectPtr<UGMS_MovementDefinition> NewDefinition, bool bSendRpc)
{
if (!NewDefinition.IsNull() && NewDefinition != MovementDefinition)
{
PrevMovementDefinition = MovementDefinition;
MovementDefinition = NewDefinition;
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, MovementDefinition, this)
OnMovementSetChanged(MovementSet);
if (bSendRpc)
{
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
{
ClientSetMovementDefinition(NewDefinition);
}
else
{
ServerSetMovementDefinition(NewDefinition);
}
}
}
}
void UGMS_MovementSystemComponent::ClientSetMovementDefinition_Implementation(const TSoftObjectPtr<UGMS_MovementDefinition>& NewDefinition)
{
InternalSetMovementDefinition(NewDefinition, false);
}
void UGMS_MovementSystemComponent::ServerSetMovementDefinition_Implementation(const TSoftObjectPtr<UGMS_MovementDefinition>& NewDefinition)
{
InternalSetMovementDefinition(NewDefinition, false);
}
void UGMS_MovementSystemComponent::OnReplicated_MovementDefinition()
{
OnMovementSetChanged(MovementSet);
}
void UGMS_MovementSystemComponent::SetMovementSet(const FGameplayTag& NewMovementSet, bool bSendRpc)
{
if (MovementSet == NewMovementSet || GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy)
{
return;
}
const auto PreviousMovementSet{MovementSet};
MovementSet = NewMovementSet;
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, MovementSet, this)
OnMovementSetChanged(PreviousMovementSet);
if (bSendRpc)
{
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
{
ClientSetMovementSet(MovementSet);
}
else
{
ServerSetMovementSet(MovementSet);
}
}
}
void UGMS_MovementSystemComponent::ClientSetMovementSet_Implementation(const FGameplayTag& NewMovementSet)
{
SetMovementSet(NewMovementSet, false);
}
void UGMS_MovementSystemComponent::ServerSetMovementSet_Implementation(const FGameplayTag& NewMovementSet)
{
SetMovementSet(NewMovementSet, false);
}
void UGMS_MovementSystemComponent::OnReplicated_MovementSet(const FGameplayTag& PreviousMovementSet)
{
OnMovementSetChanged(PreviousMovementSet);
}
void UGMS_MovementSystemComponent::RefreshMovementSetSetting()
{
const UGMS_MovementDefinition* LoadedDefinition = MovementDefinition.LoadSynchronous();
if (!LoadedDefinition)
{
GMS_CLOG(Warning, "Missing valid movement definition!")
return;
}
if (!LoadedDefinition->MovementSets.Contains(MovementSet))
{
GMS_CLOG(Warning, "No movement set(%s) found in movement definition(%s)!", *MovementSet.ToString(), *GetNameSafe(LoadedDefinition))
return;
}
MovementSetSetting = MovementDefinition->MovementSets[MovementSet];
RefreshControlSetting();
// bool bFoundMovementSet{false};
// for (int32 i = MovementDefinitions.Num() - 1; i >= 0; i--)
// {
// if (MovementDefinitions[i].IsNull())
// {
// continue;
// }
// if (!MovementDefinitions[i].IsValid())
// {
// MovementDefinitions[i].LoadSynchronous();
// }
// if (MovementDefinitions[i]->MovementSets.Contains(MovementSet))
// {
// MovementDefinition = MovementDefinitions[i].Get();
// MovementSetSetting = MovementDefinition->MovementSets[MovementSet];
// bFoundMovementSet = true;
// RefreshControlSetting();
// break;
// }
// }
// if (!bFoundMovementSet)
// {
// GMS_CLOG(Error, "No movement set(%s) found in movement definitions!", *MovementSet.ToString())
// }
}
void UGMS_MovementSystemComponent::RefreshControlSetting()
{
const UGMS_MovementControlSetting_Default* NewSetting = MovementSetSetting.bControlSettingPerOverlayMode && MovementSetSetting.ControlSettings.Contains(OverlayMode)
? MovementSetSetting.ControlSettings[OverlayMode]
: MovementSetSetting.ControlSetting;
if (NewSetting != nullptr && !NewSetting->MovementStates.IsEmpty())
{
ControlSetting = NewSetting;
RefreshMovementStateSetting();
ApplyMovementSetting();
}
else
{
ControlSetting = nullptr;
GMS_CLOG(Error, "Empty MovementState settings are found in the movement set(%s) of definition(%s), which is not allowed!", *MovementSet.ToString(),
*MovementDefinition->GetName())
}
}
void UGMS_MovementSystemComponent::OnMovementSetChanged_Implementation(const FGameplayTag& PreviousMovementSet)
{
RefreshMovementSetSetting();
OnMovementSetChangedEvent.Broadcast(PreviousMovementSet);
}
const FGameplayTag& UGMS_MovementSystemComponent::GetDesiredMovementState() const
{
return FGameplayTag::EmptyTag;
}
void UGMS_MovementSystemComponent::RefreshMovementStateSetting()
{
if (!IsValid(ControlSetting))
{
return;
}
FGMS_MovementStateSetting NewStateSetting;
if (!ControlSetting->GetStateByTag(GetMovementState(), NewStateSetting))
{
checkf(!ControlSetting->MovementStates.IsEmpty(), TEXT("Found empty MovementState Settings on %s!"), *ControlSetting->GetName())
NewStateSetting = ControlSetting->MovementStates.Last();
SetDesiredMovement(NewStateSetting.Tag);
GMS_CLOG(Verbose, "No MovementState setting for current movement state(%s), Change desired last one(%s) in list.", *GetMovementState().ToString(),
*NewStateSetting.Tag.ToString());
}
MovementStateSetting = NewStateSetting;
if (bRespectAllowedRotationModesSettings)
{
if (!MovementStateSetting.AllowedRotationModes.Contains(GetDesiredRotationMode()))
{
FGameplayTag AdjustedRotationMode = MovementStateSetting.AllowedRotationModes.Last();
GMS_CLOG(Warning, "current movement state(%s) doesn't allow current desired rotation mode(%s), adjusted to:%s", *GetMovementState().ToString(), *GetDesiredRotationMode().ToString(),
*AdjustedRotationMode.ToString());
SetDesiredRotationMode(AdjustedRotationMode);
}
}
}
void UGMS_MovementSystemComponent::ApplyMovementSetting()
{
}
#pragma endregion
#pragma region MovementState
void UGMS_MovementSystemComponent::SetDesiredMovement(const FGameplayTag& NewDesiredMovement)
{
}
void UGMS_MovementSystemComponent::CycleDesiredMovementState(bool bForward)
{
if (GetNumOfMovementStateSettings() == 1)
{
return;
}
int32 Index = ControlSetting->MovementStates.IndexOfByKey(GetMovementState());
if (Index == INDEX_NONE)
{
return;
}
if (bForward && ControlSetting->MovementStates.IsValidIndex(Index + 1))
{
SetDesiredMovement(ControlSetting->MovementStates[Index + 1].Tag);
}
if (!bForward && ControlSetting->MovementStates.IsValidIndex(Index - 1))
{
SetDesiredMovement(ControlSetting->MovementStates[Index - 1].Tag);
}
}
const FGameplayTag& UGMS_MovementSystemComponent::GetMovementState() const
{
return FGameplayTag::EmptyTag;
}
int32 UGMS_MovementSystemComponent::GetSpeedLevel() const
{
return MovementStateSetting.SpeedLevel;
}
float UGMS_MovementSystemComponent::GetMappedMovementSpeedLevel(float Speed) const
{
if (!ControlSetting)
{
return 0.0f;
}
float SpeedLevelAmount = ControlSetting->MovementStates.Last().SpeedLevel;
for (int32 i = ControlSetting->MovementStates.Num() - 2; i >= 0; i--)
{
const FGMS_MovementStateSetting& Max = ControlSetting->MovementStates[i + 1];
const FGMS_MovementStateSetting& Min = ControlSetting->MovementStates[i];
// In current range.
if (Min.Speed > 0 && Speed >= Min.Speed && Speed <= Max.Speed)
{
SpeedLevelAmount = FMath::GetMappedRangeValueClamped(FVector2f{Min.Speed, Max.Speed}, {static_cast<float>(Min.SpeedLevel), static_cast<float>(Max.SpeedLevel)}, Speed);
}
}
GMS_CLOG(VeryVerbose, "Mapped speed(%f) to speed level(%f)", Speed, SpeedLevelAmount)
return SpeedLevelAmount;
}
void UGMS_MovementSystemComponent::OnMovementStateChanged_Implementation(const FGameplayTag& PreviousMovementState)
{
RefreshMovementStateSetting();
ApplyMovementSetting();
OnMovementStateChangedEvent.Broadcast(PreviousMovementState);
}
#pragma endregion
#pragma region Input
FVector UGMS_MovementSystemComponent::GetMovementIntent() const
{
checkf(0, TEXT("Not implemented"));
return FVector::ZeroVector;
}
void UGMS_MovementSystemComponent::RefreshInput(float DeltaTime)
{
LocomotionState.bHasInput = GetMovementIntent().SizeSquared() > UE_KINDA_SMALL_NUMBER;
if (LocomotionState.bHasInput)
{
LocomotionState.InputYawAngle = UE_REAL_TO_FLOAT(UGMS_Vector::DirectionToAngleXY(GetMovementIntent()));
}
}
void UGMS_MovementSystemComponent::TurnAtRate(float Direction)
{
if (Direction != 0 && GetRotationMode() == GMS_RotationModeTags::VelocityDirection &&
OwnerPawn->GetLocalRole() >= ROLE_AutonomousProxy)
{
if (const auto Setting = ControlSetting->VelocityDirectionSetting.GetPtr<FGMS_VelocityDirectionSetting_RateBased>())
{
const float TurnRate =
IsValid(Setting->TurnRateSpeedCurve)
? Setting->TurnRateSpeedCurve->GetFloatValue(FMath::Max(1.0f, GetMappedMovementSpeedLevel(UE_REAL_TO_FLOAT(LocomotionState.Velocity.Size2D()))))
: Setting->TurnRate;
float YawDelta = Direction * TurnRate * GetWorld()->GetDeltaSeconds();
if (FMath::Abs(YawDelta) > UE_SMALL_NUMBER)
{
float TargetYawAngle = FMath::UnwindDegrees(OwnerPawn->GetActorRotation().Yaw + YawDelta);
// 或者方法2使用 RotateAngleAxis如果需要保持四元数精度
// FQuat DeltaRotation = FQuat(FVector::UpVector, FMath::DegreesToRadians(YawDelta));
// FQuat NewRotation = DeltaRotation * LocomotionState.RotationQuaternion;
// NewDesiredVelocityYawAngle = NewRotation.Rotator().Yaw;
// FRotator RotationDelta = FRotator(0,Direction * GetVelocityDirectionSetting().TurningRate * DeltaTime,0);
//
// NewDesiredVelocityYawAngle = (RotationDelta.Quaternion() * LocomotionState.RotationQuaternion).Rotator().Yaw;
SetDesiredVelocityYawAngle(TargetYawAngle);
if (OwnerPawn->GetLocalRole() < ROLE_Authority)
{
ServerSetDesiredVelocityYawAngle(TargetYawAngle);
}
}
}
}
}
#pragma endregion
#pragma region ViewSystem
const FGMS_ViewState& UGMS_MovementSystemComponent::GetViewState() const
{
return ViewState;
}
void UGMS_MovementSystemComponent::SetReplicatedViewRotation(const FRotator& NewViewRotation, bool bSendRpc)
{
if (!ReplicatedViewRotation.Equals(NewViewRotation))
{
ReplicatedViewRotation = NewViewRotation;
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, ReplicatedViewRotation, this)
if (bSendRpc && GetOwner()->GetLocalRole() == ROLE_AutonomousProxy)
{
ServerSetReplicatedViewRotation(ReplicatedViewRotation);
}
}
}
void UGMS_MovementSystemComponent::ServerSetReplicatedViewRotation_Implementation(const FRotator& NewViewRotation)
{
SetReplicatedViewRotation(NewViewRotation, false);
}
void UGMS_MovementSystemComponent::OnReplicated_ReplicatedViewRotation()
{
ViewState.Rotation = MovementBase.bHasRelativeRotation ? (MovementBase.Rotation * ReplicatedViewRotation.Quaternion()).Rotator() : ReplicatedViewRotation;
}
#pragma endregion
#pragma region Rotation Mode
const FGameplayTag& UGMS_MovementSystemComponent::GetDesiredRotationMode() const
{
return FGameplayTag::EmptyTag;
}
void UGMS_MovementSystemComponent::SetDesiredRotationMode(const FGameplayTag& NewDesiredRotationMode)
{
}
const FGameplayTag& UGMS_MovementSystemComponent::GetRotationMode() const
{
return FGameplayTag::EmptyTag;
}
void UGMS_MovementSystemComponent::OnRotationModeChanged_Implementation(const FGameplayTag& PreviousRotationMode)
{
ApplyMovementSetting();
OnRotationModeChangedEvent.Broadcast(PreviousRotationMode);
}
#pragma endregion
#pragma region OverlayMode
const FGameplayTag& UGMS_MovementSystemComponent::GetOverlayMode() const
{
return OverlayMode;
}
void UGMS_MovementSystemComponent::SetOverlayMode(const FGameplayTag& NewOverlayMode)
{
SetOverlayMode(NewOverlayMode, true);
}
void UGMS_MovementSystemComponent::SetOverlayMode(const FGameplayTag& NewOverlayMode, bool bSendRpc)
{
if (OverlayMode == NewOverlayMode || OwnerPawn->GetLocalRole() <= ROLE_SimulatedProxy)
{
return;
}
const auto PreviousOverlayMode{OverlayMode};
OverlayMode = NewOverlayMode;
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, OverlayMode, this)
OnOverlayModeChanged(PreviousOverlayMode);
if (bSendRpc)
{
if (OwnerPawn->GetLocalRole() >= ROLE_Authority)
{
ClientSetOverlayMode(OverlayMode);
}
else
{
ServerSetOverlayMode(OverlayMode);
}
}
}
void UGMS_MovementSystemComponent::ClientSetOverlayMode_Implementation(const FGameplayTag& NewOverlayMode)
{
SetOverlayMode(NewOverlayMode, false);
}
void UGMS_MovementSystemComponent::ServerSetOverlayMode_Implementation(const FGameplayTag& NewOverlayMode)
{
SetOverlayMode(NewOverlayMode, false);
}
void UGMS_MovementSystemComponent::OnReplicated_OverlayMode(const FGameplayTag& PreviousOverlayMode)
{
OnOverlayModeChanged(PreviousOverlayMode);
}
void UGMS_MovementSystemComponent::OnOverlayModeChanged_Implementation(const FGameplayTag& PreviousOverlayMode)
{
if (MovementSetSetting.bControlSettingPerOverlayMode)
{
RefreshControlSetting();
}
OnOverlayModeChangedEvent.Broadcast(PreviousOverlayMode);
}
#pragma endregion
#if WITH_EDITOR
EDataValidationResult UGMS_MovementSystemComponent::IsDataValid(class FDataValidationContext& Context) const
{
if (IsTemplate() && AnimGraphSetting == nullptr)
{
Context.AddError(FText::FromString("AnimGraphSetting is required!"));
return EDataValidationResult::Invalid;
}
return Super::IsDataValid(Context);
}
#endif

View File

@@ -0,0 +1,753 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GMS_MoverMovementSystemComponent.h"
#include "PoseSearch/PoseSearchTrajectoryPredictor.h"
#include "Runtime/Launch/Resources/Version.h"
#include "GameFramework/Pawn.h"
#include "Components/SkeletalMeshComponent.h"
#include "MoverComponent.h"
#include "MoverPoseSearchTrajectoryPredictor.h"
#include "BoneControllers/AnimNode_OffsetRootBone.h"
#include "Components/CapsuleComponent.h"
#include "DefaultMovementSet/CharacterMoverComponent.h"
#include "DefaultMovementSet/NavMoverComponent.h"
#include "DefaultMovementSet/Settings/CommonLegacyMovementSettings.h"
#include "Locomotions/GMS_MainAnimInstance.h"
#include "MoveLibrary/MovementMixer.h"
#include "Mover/GMS_MoverStructLibrary.h"
#include "Mover/Modifers/GMS_MovementStateModifer.h"
#include "Net/UnrealNetwork.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_Utility.h"
#include "Utility/GMS_Vector.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_MoverMovementSystemComponent)
UGMS_MoverMovementSystemComponent::UGMS_MoverMovementSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
SetIsReplicatedByDefault(true);
PrimaryComponentTick.bCanEverTick = true;
bWantsInitializeComponent = true;
bReplicateUsingRegisteredSubObjectList = true;
MovementModeToTagMapping = {
{TEXT("None"), GMS_MovementModeTags::None},
{TEXT("Walking"), GMS_MovementModeTags::Grounded},
{TEXT("NavWalking"), GMS_MovementModeTags::Grounded},
{TEXT("Falling"), GMS_MovementModeTags::InAir},
{TEXT("Swimming"), GMS_MovementModeTags::Swimming},
{TEXT("Flying"), GMS_MovementModeTags::Flying},
};
}
void UGMS_MoverMovementSystemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
FDoRepLifetimeParams Parameters;
Parameters.bIsPushBased = true;
Parameters.Condition = COND_SkipOwner;
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, MovementState, Parameters)
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, RotationMode, Parameters)
}
void UGMS_MoverMovementSystemComponent::InitializeComponent()
{
Super::InitializeComponent();
MovementState = DesiredMovementState;
RotationMode = DesiredRotationMode;
MoverComponent = OwnerPawn->FindComponentByClass<UMoverComponent>();
if (MoverComponent)
{
TrajectoryPredictor = NewObject<UMoverTrajectoryPredictor>(this, UMoverTrajectoryPredictor::StaticClass());
TrajectoryPredictor->Setup(MoverComponent);
MoverComponent->InputProducer = this;
if (!MoverComponent->MovementMixer)
{
// Prevent crash by early create this object on initialzie component.
MoverComponent->MovementMixer = NewObject<UMovementMixer>(this, TEXT("Default Movement Mixer"));
}
// Make sure this component are ticking after the mover component so this component can access the most up-to-date mover state.
AddTickPrerequisiteComponent(MoverComponent);
}
NavMoverComponent = OwnerPawn->FindComponentByClass<UNavMoverComponent>();
MeshComponent = OwnerPawn->FindComponentByClass<USkeletalMeshComponent>();
if (MeshComponent)
{
AnimationInstance = MeshComponent->GetAnimInstance();
// Make sure the mesh and animation blueprint are ticking after the character so they can access the most up-to-date character state.
MeshComponent->AddTickPrerequisiteComponent(this);
}
}
// Called when the game starts
void UGMS_MoverMovementSystemComponent::BeginPlay()
{
Super::BeginPlay();
//This callback fires only on predicting client and server, not simulated pawn.
MoverComponent->OnMovementModeChanged.AddDynamic(this, &ThisClass::OnMoverMovementModeChanged);
RefreshMovementSetSetting();
MoverComponent->OnPreSimulationTick.AddDynamic(this, &ThisClass::OnMoverPreSimulationTick);
}
void UGMS_MoverMovementSystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (IsValid(MoverComponent))
{
MoverComponent->OnMovementModeChanged.RemoveDynamic(this, &ThisClass::OnMoverMovementModeChanged);
}
Super::EndPlay(EndPlayReason);
}
// Called every frame
void UGMS_MoverMovementSystemComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGMS_MoverMovementSystemComponent::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);
RefreshLocomotion(DeltaTime);
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
RefreshLocomotionLate(DeltaTime);
}
void UGMS_MoverMovementSystemComponent::OnMoverPreSimulationTick(const FMoverTimeStep& TimeStep, const FMoverInputCmdContext& InputCmd)
{
const FCharacterDefaultInputs* CharacterInputs = InputCmd.InputCollection.FindDataByType<FCharacterDefaultInputs>();
const FGMS_MoverMovementControlInputs* ControlInputs = InputCmd.InputCollection.FindDataByType<FGMS_MoverMovementControlInputs>();
if (ControlInputs)
{
// update movement state, settings. before actually do it.
ApplyMovementState(ControlInputs->DesiredMovementState);
ApplyRotationMode(ControlInputs->DesiredRotationMode);
}
}
void UGMS_MoverMovementSystemComponent::OnMoverMovementModeChanged(const FName& PreviousMovementModeName, const FName& NewMovementModeName)
{
// Use the mover movement mode to set the locomotion mode to the right value.
if (NewMovementModeName != NAME_None)
{
if (MovementModeToTagMapping.Contains(NewMovementModeName) && MovementModeToTagMapping[NewMovementModeName].IsValid())
{
if (LocomotionMode != MovementModeToTagMapping[NewMovementModeName])
{
SetLocomotionMode(MovementModeToTagMapping[NewMovementModeName]);
}
}
else
{
GMS_CLOG(Error, "No locomotion mode mapping for MovementMode:%s", *NewMovementModeName.ToString());
}
}
}
void UGMS_MoverMovementSystemComponent::ApplyMovementSetting()
{
if (IsValid(MoverComponent) && IsValid(ControlSetting))
{
if (const FGMS_MovementStateSetting* TempMS = ControlSetting->GetMovementStateSetting(DesiredMovementState, true))
{
if (UCommonLegacyMovementSettings* LegacyMovementSettings = MoverComponent->FindSharedSettings_Mutable<UCommonLegacyMovementSettings>())
{
LegacyMovementSettings->MaxSpeed = TempMS->Speed;
LegacyMovementSettings->Acceleration = TempMS->Acceleration;
LegacyMovementSettings->Deceleration = TempMS->BrakingDeceleration;
}
}
}
}
void UGMS_MoverMovementSystemComponent::RefreshView(float DeltaTime)
{
ViewState.PreviousYawAngle = UE_REAL_TO_FLOAT(ViewState.Rotation.Yaw);
if (OwnerPawn->IsLocallyControlled() || (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(), true);
}
ViewState.Rotation = 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;
}
}
void UGMS_MoverMovementSystemComponent::ServerSetReplicatedViewRotation_Implementation(const FRotator& NewViewRotation)
{
Super::ServerSetReplicatedViewRotation_Implementation(NewViewRotation);
// Mover doesn't send control rotation to server, so we do it.
if (OwnerPawn->GetController() && !OwnerPawn->GetController()->GetControlRotation().Equals(NewViewRotation))
{
OwnerPawn->GetController()->SetControlRotation(NewViewRotation);
}
}
TScriptInterface<IPoseSearchTrajectoryPredictorInterface> UGMS_MoverMovementSystemComponent::GetTrajectoryPredictor() const
{
return TrajectoryPredictor;
}
bool UGMS_MoverMovementSystemComponent::IsCrouching() const
{
if (UCharacterMoverComponent* CharacterMover = Cast<UCharacterMoverComponent>(MoverComponent))
{
return CharacterMover->IsCrouching();
}
return false;
}
float UGMS_MoverMovementSystemComponent::GetMaxSpeed() const
{
const UCommonLegacyMovementSettings* CommonLegacySettings = MoverComponent->FindSharedSettings<UCommonLegacyMovementSettings>();
return CommonLegacySettings->MaxSpeed;
}
float UGMS_MoverMovementSystemComponent::GetScaledCapsuleRadius() const
{
if (UCapsuleComponent* Capsule = Cast<UCapsuleComponent>(MoverComponent->GetUpdatedComponent()))
{
return Capsule->GetScaledCapsuleRadius();
}
return GetDefault<UCapsuleComponent>()->GetUnscaledCapsuleRadius();
}
float UGMS_MoverMovementSystemComponent::GetScaledCapsuleHalfHeight() const
{
if (UCapsuleComponent* Capsule = Cast<UCapsuleComponent>(MoverComponent->GetUpdatedComponent()))
{
return Capsule->GetScaledCapsuleHalfHeight();
}
return GetDefault<UCapsuleComponent>()->GetUnscaledCapsuleHalfHeight();
}
float UGMS_MoverMovementSystemComponent::GetMaxAcceleration() const
{
const UCommonLegacyMovementSettings* CommonLegacySettings = MoverComponent->FindSharedSettings<UCommonLegacyMovementSettings>();
return CommonLegacySettings->Acceleration;
}
float UGMS_MoverMovementSystemComponent::GetMaxBrakingDeceleration() const
{
const UCommonLegacyMovementSettings* CommonLegacySettings = MoverComponent->FindSharedSettings<UCommonLegacyMovementSettings>();
return CommonLegacySettings->Deceleration;
}
float UGMS_MoverMovementSystemComponent::GetWalkableFloorZ() const
{
const UCommonLegacyMovementSettings* CommonLegacySettings = MoverComponent->FindSharedSettings<UCommonLegacyMovementSettings>();
return CommonLegacySettings->MaxWalkSlopeCosine;
}
float UGMS_MoverMovementSystemComponent::GetGravityZ() const
{
return MoverComponent->GetGravityAcceleration().Z;
}
USkeletalMeshComponent* UGMS_MoverMovementSystemComponent::GetMesh() const
{
return MeshComponent;
}
bool UGMS_MoverMovementSystemComponent::IsMovingOnGround() const
{
return IsValid(MoverComponent) ? MoverComponent->HasGameplayTag(Mover_IsOnGround, true) : false;
}
void UGMS_MoverMovementSystemComponent::RefreshLocomotionEarly()
{
}
void UGMS_MoverMovementSystemComponent::RefreshLocomotion(float DeltaTime)
{
LocomotionState.Velocity = MoverComponent->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_MoverMovementSystemComponent::RefreshDynamicMovementState()
{
}
void UGMS_MoverMovementSystemComponent::RefreshLocomotionLate(float DeltaTime)
{
if (!LocomotionMode.IsValid())
{
RefreshTargetYawAngleUsingActorRotation();
}
}
void UGMS_MoverMovementSystemComponent::RefreshTargetYawAngleUsingActorRotation()
{
const auto YawAngle{UE_REAL_TO_FLOAT(OwnerPawn->GetActorRotation().Yaw)};
SetTargetYawAngle(YawAngle);
}
void UGMS_MoverMovementSystemComponent::SetTargetYawAngle(const float TargetYawAngle)
{
LocomotionState.TargetYawAngle = FRotator3f::NormalizeAxis(TargetYawAngle);
RefreshViewRelativeTargetYawAngle();
LocomotionState.SmoothTargetYawAngle = LocomotionState.TargetYawAngle;
}
void UGMS_MoverMovementSystemComponent::RefreshViewRelativeTargetYawAngle()
{
LocomotionState.ViewRelativeTargetYawAngle = FRotator3f::NormalizeAxis(UE_REAL_TO_FLOAT(
ViewState.Rotation.Yaw - LocomotionState.TargetYawAngle));
}
FGMS_PredictGroundMovementPivotLocationParams UGMS_MoverMovementSystemComponent::GetPredictGroundMovementPivotLocationParams() const
{
FGMS_PredictGroundMovementPivotLocationParams Params;
if (MoverComponent)
{
if (const UCommonLegacyMovementSettings* CommonLegacySettings = MoverComponent->FindSharedSettings<UCommonLegacyMovementSettings>())
{
Params.Acceleration = MoverComponent->GetMovementIntent() * CommonLegacySettings->Acceleration;
Params.Velocity = MoverComponent->GetVelocity();
Params.GroundFriction = CommonLegacySettings->GroundFriction;
}
}
return Params;
}
FGMS_PredictGroundMovementStopLocationParams UGMS_MoverMovementSystemComponent::GetPredictGroundMovementStopLocationParams() const
{
FGMS_PredictGroundMovementStopLocationParams Params;
if (MoverComponent)
{
if (const UCommonLegacyMovementSettings* CommonLegacySettings = MoverComponent->FindSharedSettings<UCommonLegacyMovementSettings>())
{
Params.Velocity = MoverComponent->GetVelocity();
Params.bUseSeparateBrakingFriction = true;
Params.BrakingFriction = CommonLegacySettings->BrakingFriction;
Params.GroundFriction = CommonLegacySettings->GroundFriction;
Params.BrakingFrictionFactor = CommonLegacySettings->BrakingFrictionFactor;
Params.BrakingDecelerationWalking = CommonLegacySettings->Deceleration;
}
}
return Params;
}
void UGMS_MoverMovementSystemComponent::RefreshMovementBase()
{
UPrimitiveComponent* BasePrimitive = MoverComponent->GetMovementBase();
FName BaseBoneName = MoverComponent->GetMovementBaseBoneName();
if (BasePrimitive != MovementBase.Primitive || BaseBoneName != MovementBase.BoneName)
{
MovementBase.Primitive = BasePrimitive;
MovementBase.BoneName = BaseBoneName;
MovementBase.bBaseChanged = true;
}
else
{
MovementBase.bBaseChanged = false;
}
MovementBase.bHasRelativeLocation = UBasedMovementUtils::IsADynamicBase(BasePrimitive);
MovementBase.bHasRelativeRotation = MovementBase.bHasRelativeLocation && bUseBaseRelativeMovement;
const auto PreviousRotation{MovementBase.Rotation};
UBasedMovementUtils::GetMovementBaseTransform(BasePrimitive, BaseBoneName,
MovementBase.Location, MovementBase.Rotation);
MovementBase.DeltaRotation = MovementBase.bHasRelativeLocation && !MovementBase.bBaseChanged
? (MovementBase.Rotation * PreviousRotation.Inverse()).Rotator()
: FRotator::ZeroRotator;
}
void UGMS_MoverMovementSystemComponent::ProduceInput_Implementation(int32 SimTimeMs, FMoverInputCmdContext& InputCmdResult)
{
InputCmdResult = OnProduceInput(static_cast<float>(SimTimeMs), InputCmdResult);
}
#pragma region MovementState
const FGameplayTag& UGMS_MoverMovementSystemComponent::GetDesiredMovementState() const
{
return DesiredMovementState;
}
void UGMS_MoverMovementSystemComponent::SetDesiredMovement(const FGameplayTag& NewDesiredMovement)
{
DesiredMovementState = NewDesiredMovement;
}
const FGameplayTag& UGMS_MoverMovementSystemComponent::GetMovementState() const
{
return MovementState;
}
void UGMS_MoverMovementSystemComponent::ApplyMovementState(const FGameplayTag& NewMovementState)
{
if (MovementState == NewMovementState || GetOwner()->GetLocalRole() < ROLE_AutonomousProxy)
{
return;
}
FGameplayTag Prev = MovementState;
MovementState = NewMovementState;
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, MovementState, this)
OnMovementStateChanged(Prev);
}
#pragma endregion
#pragma region RotationMode
const FGameplayTag& UGMS_MoverMovementSystemComponent::GetDesiredRotationMode() const
{
return DesiredRotationMode;
}
void UGMS_MoverMovementSystemComponent::SetDesiredRotationMode(const FGameplayTag& NewDesiredRotationMode)
{
DesiredRotationMode = NewDesiredRotationMode;
}
const FGameplayTag& UGMS_MoverMovementSystemComponent::GetRotationMode() const
{
return RotationMode;
}
void UGMS_MoverMovementSystemComponent::ApplyRotationMode(const FGameplayTag& NewRotationMode)
{
if (RotationMode == NewRotationMode || GetOwner()->GetLocalRole() < ROLE_AutonomousProxy)
{
return;
}
FGameplayTag Prev = RotationMode;
RotationMode = NewRotationMode;
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, RotationMode, this)
OnRotationModeChanged(Prev);
}
#pragma endregion
FVector UGMS_MoverMovementSystemComponent::GetMovementIntent() const
{
if (const FCharacterDefaultInputs* CharacterInputs = MoverComponent->GetLastInputCmd().InputCollection.FindDataByType<FCharacterDefaultInputs>())
{
return CharacterInputs->GetMoveInput_WorldSpace();
}
return MoverComponent->GetMovementIntent();
}
FVector UGMS_MoverMovementSystemComponent::AdjustOrientationIntent(float DeltaSeconds, const FVector& OrientationIntent) const
{
FVector Intent = OrientationIntent;
if (GetRotationMode() == GMS_RotationModeTags::VelocityDirection)
{
if (const FGMS_VelocityDirectionSetting_RateBased* Setting = GetControlSetting()->VelocityDirectionSetting.GetPtr<FGMS_VelocityDirectionSetting_RateBased>())
{
float YawDelta = CachedTurnInput.Yaw * DeltaSeconds * Setting->TurnRate;
Intent.Z += YawDelta;
}
}
if (GetRotationMode() == GMS_RotationModeTags::ViewDirection)
{
// Use curve to drive actor rotation only if no root bone rotation. 仅在没有使用根骨旋转时采用曲线驱动Actor旋转。
if (UGMS_MainAnimInstance* AnimInst = Cast<UGMS_MainAnimInstance>(MainAnimInstance))
{
if (AnimInst->GetOffsetRootBoneRotationMode() == EOffsetRootBoneMode::Release)
{
const float CurveValue = AnimationInstance->GetCurveValue(UGMS_Constants::RotationYawSpeedCurveName());
const float DeltaYawAngle{CurveValue * DeltaSeconds};
if (FMath::Abs(DeltaYawAngle) > UE_SMALL_NUMBER)
{
Intent = Intent.RotateAngleAxis(DeltaYawAngle, FVector::ZAxisVector);
}
}
}
}
return Intent;
}
FMoverInputCmdContext UGMS_MoverMovementSystemComponent::OnProduceInput_Implementation(float DeltaMs, FMoverInputCmdContext InputCmdResult)
{
check(OwnerPawn)
FCharacterDefaultInputs& CharacterInputs = InputCmdResult.InputCollection.FindOrAddMutableDataByType<FCharacterDefaultInputs>();
FGMS_MoverMovementControlInputs& ControlInputs = InputCmdResult.InputCollection.FindOrAddMutableDataByType<FGMS_MoverMovementControlInputs>();
ControlInputs.DesiredMovementSet = MovementSet;
ControlInputs.DesiredRotationMode = DesiredRotationMode;
ControlInputs.DesiredMovementState = DesiredMovementState;
if (OwnerPawn->Controller == nullptr)
{
if (OwnerPawn->GetLocalRole() == ROLE_Authority && OwnerPawn->GetRemoteRole() == ROLE_SimulatedProxy)
{
static const FCharacterDefaultInputs DoNothingInput;
// If we get here, that means this pawn is not currently possessed and we're choosing to provide default do-nothing input
CharacterInputs = DoNothingInput;
}
// We don't have a local controller so we can't run the code below. This is ok. Simulated proxies will just use previous input when extrapolating
return InputCmdResult;
}
CharacterInputs.ControlRotation = FRotator::ZeroRotator;
// APlayerController* PC = Cast<APlayerController>(OwnerPawn->Controller);
// if (PC)
// {
// CharacterInputs.ControlRotation = PC->GetControlRotation();
// }
CharacterInputs.ControlRotation = ViewState.Rotation;
bool bRequestedNavMovement = false;
if (NavMoverComponent)
{
bRequestedNavMovement = NavMoverComponent->ConsumeNavMovementData(CachedMoveInputIntent, CachedMoveInputVelocity);
}
// setup move input based on velocity/raw input.
{
// Favor velocity input
bool bUsingInputIntentForMove = CachedMoveInputVelocity.IsZero();
if (bUsingInputIntentForMove)
{
const FVector FinalDirectionalIntent = CharacterInputs.ControlRotation.RotateVector(CachedMoveInputIntent);
// FRotator Rotator = CharacterInputs.ControlRotation;
// FVector FinalDirectionalIntent;
// if (const UCharacterMoverComponent* MoverComp = Cast<UCharacterMoverComponent>(MoverComponent))
// {
// if (MoverComp->IsOnGround() || MoverComp->IsFalling())
// {
// const FVector RotationProjectedOntoUpDirection = FVector::VectorPlaneProject(Rotator.Vector(), MoverComp->GetUpDirection()).GetSafeNormal();
// Rotator = RotationProjectedOntoUpDirection.Rotation();
// }
// FinalDirectionalIntent = Rotator.RotateVector(CachedMoveInputIntent);
// }
CharacterInputs.SetMoveInput(EMoveInputType::DirectionalIntent, FinalDirectionalIntent);
}
else
{
CharacterInputs.SetMoveInput(EMoveInputType::Velocity, CachedMoveInputVelocity);
}
// Normally cached input is cleared by OnMoveCompleted input event but that won't be called if movement came from nav movement
if (bRequestedNavMovement)
{
CachedMoveInputIntent = FVector::ZeroVector;
CachedMoveInputVelocity = FVector::ZeroVector;
}
}
static float RotationMagMin(1e-3);
const bool bHasAffirmativeMoveInput = (CharacterInputs.GetMoveInput().Size() >= RotationMagMin);
// Figure out intended orientation
CharacterInputs.OrientationIntent = FVector::ZeroVector;
// setup orientation.
{
const bool bVelocityDirection = GetRotationMode() == GMS_RotationModeTags::VelocityDirection;
const bool bViewDirection = !bVelocityDirection;
if (!bHasAffirmativeMoveInput && bVelocityDirection)
{
if (const FGMS_VelocityDirectionSetting_RateBased* Setting = GetControlSetting()->VelocityDirectionSetting.GetPtr<FGMS_VelocityDirectionSetting_RateBased>())
{
const float DeltaSeconds = DeltaMs * 0.001f;
CharacterInputs.OrientationIntent = AdjustOrientationIntent(DeltaSeconds, MoverComponent->GetTargetOrientation().Vector());
}
else if (bMaintainLastInputOrientation)
{
// There is no movement intent, so use the last-known affirmative move input
CharacterInputs.OrientationIntent = LastAffirmativeMoveInput;
}
}
if (bHasAffirmativeMoveInput && bVelocityDirection)
{
if (const FGMS_VelocityDirectionSetting_RateBased* Setting = GetControlSetting()->VelocityDirectionSetting.GetPtr<FGMS_VelocityDirectionSetting_RateBased>())
{
const float DeltaSeconds = DeltaMs * 0.001f;
CharacterInputs.OrientationIntent = AdjustOrientationIntent(DeltaSeconds, MoverComponent->GetTargetOrientation().Vector());
}
else
{
// set the intent to the actors movement direction
CharacterInputs.OrientationIntent = CharacterInputs.GetMoveInput().GetSafeNormal();
}
}
if (!bHasAffirmativeMoveInput && bViewDirection)
{
if (GetControlSetting()->ViewDirectionSetting.Get().bEnableRotationWhenNotMoving)
{
// set intent to the the control rotation - often a player's camera rotation
CharacterInputs.OrientationIntent = CharacterInputs.ControlRotation.Vector().GetSafeNormal();
}
else
{
const float DeltaSeconds = DeltaMs * 0.001f;
CharacterInputs.OrientationIntent = AdjustOrientationIntent(DeltaSeconds, MoverComponent->GetTargetOrientation().Vector());
}
}
if (bHasAffirmativeMoveInput && bViewDirection)
{
// set intent to the control rotation - often a player's camera rotation
CharacterInputs.OrientationIntent = CharacterInputs.ControlRotation.Vector().GetSafeNormal();
}
}
if (bHasAffirmativeMoveInput)
{
LastAffirmativeMoveInput = CharacterInputs.GetMoveInput();
}
if (bShouldRemainVertical)
{
// canceling out any z intent if the actor is supposed to remain vertical
CharacterInputs.OrientationIntent = CharacterInputs.OrientationIntent.GetSafeNormal2D();
}
CharacterInputs.bIsJumpPressed = bIsJumpPressed;
CharacterInputs.bIsJumpJustPressed = bIsJumpJustPressed;
if (bShouldToggleFlying)
{
if (!bIsFlyingActive)
{
CharacterInputs.SuggestedMovementMode = DefaultModeNames::Flying;
}
else
{
CharacterInputs.SuggestedMovementMode = DefaultModeNames::Falling;
}
bIsFlyingActive = !bIsFlyingActive;
}
else
{
CharacterInputs.SuggestedMovementMode = NAME_None;
}
// Convert inputs to be relative to the current movement base (depending on options and state)
CharacterInputs.bUsingMovementBase = false;
if (bUseBaseRelativeMovement)
{
if (const UCharacterMoverComponent* MoverComp = OwnerPawn->GetComponentByClass<UCharacterMoverComponent>())
{
if (UPrimitiveComponent* MovementBasePtr = MoverComp->GetMovementBase())
{
FName MovementBaseBoneName = MoverComp->GetMovementBaseBoneName();
FVector RelativeMoveInput, RelativeOrientDir;
UBasedMovementUtils::TransformWorldDirectionToBased(MovementBasePtr, MovementBaseBoneName, CharacterInputs.GetMoveInput(), RelativeMoveInput);
UBasedMovementUtils::TransformWorldDirectionToBased(MovementBasePtr, MovementBaseBoneName, CharacterInputs.OrientationIntent, RelativeOrientDir);
CharacterInputs.SetMoveInput(CharacterInputs.GetMoveInputType(), RelativeMoveInput);
CharacterInputs.OrientationIntent = RelativeOrientDir;
CharacterInputs.bUsingMovementBase = true;
CharacterInputs.MovementBase = MovementBasePtr;
CharacterInputs.MovementBaseBoneName = MovementBaseBoneName;
}
}
}
// Clear/consume temporal movement inputs. We are not consuming others in the event that the game world is ticking at a lower rate than the Mover simulation.
// In that case, we want most input to carry over between simulation frames.
{
bIsJumpJustPressed = false;
bShouldToggleFlying = false;
}
return InputCmdResult;
}
#if WITH_EDITOR
EDataValidationResult UGMS_MoverMovementSystemComponent::IsDataValid(class FDataValidationContext& Context) const
{
// if (IsTemplate() && InputProducerClass.IsNull())
// {
// Context.AddError(FText::FromString("Input Producer Class is required!"));
// return EDataValidationResult::Invalid;
// }
return Super::IsDataValid(Context);
}
#endif

View File

@@ -0,0 +1,60 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GenericMovementSystem.h"
#include "Utility/GMS_Log.h"
#if ALLOW_CONSOLE
#include "Engine/Console.h"
#endif
#if WITH_EDITOR
#include "MessageLogModule.h"
#endif
#define LOCTEXT_NAMESPACE "FGenericMovementSystemModule"
void FGenericMovementSystemModule::StartupModule()
{
#if ALLOW_CONSOLE
UConsole::RegisterConsoleAutoCompleteEntries.AddRaw(this, &FGenericMovementSystemModule::Console_OnRegisterAutoCompleteEntries);
#endif
#if WITH_EDITOR
// Register dedicated message log category for GMS.
auto& MessageLog{FModuleManager::LoadModuleChecked<FMessageLogModule>(FName{TEXTVIEW("MessageLog")})};
FMessageLogInitializationOptions MessageLogOptions;
MessageLogOptions.bShowFilters = true;
MessageLogOptions.bAllowClear = true;
MessageLogOptions.bDiscardDuplicates = true;
MessageLog.RegisterLogListing(GMSLog::MessageLogName, LOCTEXT("MessageLogLabel", "GMS"), MessageLogOptions);
#endif
}
void FGenericMovementSystemModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#if ALLOW_CONSOLE
void FGenericMovementSystemModule::Console_OnRegisterAutoCompleteEntries(TArray<FAutoCompleteCommand>& AutoCompleteCommands)
{
const auto CommandColor{GetDefault<UConsoleSettings>()->AutoCompleteCommandColor};
auto* Command{&AutoCompleteCommands.AddDefaulted_GetRef()};
Command->Command = FString{TEXTVIEW("Stat GMS")};
Command->Desc = FString{TEXTVIEW("Displays GMS performance statistics.")};
Command->Color = CommandColor;
// Visual debugging will continue in 1.6
}
#endif
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FGenericMovementSystemModule, GenericMovementSystem)

View File

@@ -0,0 +1,128 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimLayer.h"
#include "GameFramework/Pawn.h"
#include "GMS_MovementSystemComponent.h"
#include "Locomotions/GMS_MainAnimInstance.h"
#include "Misc/DataValidation.h"
#include "Settings/GMS_SettingObjectLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimLayer)
bool UGMS_AnimLayerSetting::GetOverrideAnimLayerClass_Implementation(TSubclassOf<UGMS_AnimLayer>& OutLayerClass) const
{
return false;
}
bool UGMS_AnimLayerSetting::K2_IsDataValid_Implementation(FText& ErrorText) const
{
return true;
}
#if WITH_EDITOR
EDataValidationResult UGMS_AnimLayerSetting::IsDataValid(class FDataValidationContext& Context) const
{
FText ErrorText;
if (!IsTemplate() && !K2_IsDataValid(ErrorText))
{
Context.AddError(ErrorText);
return EDataValidationResult::Invalid;
}
return Super::IsDataValid(Context);
}
#endif
void UGMS_AnimLayer::OnLinked_Implementation()
{
// make sure to get reference to parent when linked.
if (!Parent.IsValid())
{
Parent = Cast<UGMS_MainAnimInstance>(GetSkelMeshComponent()->GetAnimInstance());
checkf(Parent!=nullptr, TEXT("Parent is not GMS_MainAnimInstance!"));
}
if (!PawnOwner)
{
PawnOwner = Cast<APawn>(GetOwningActor());
checkf(PawnOwner!=nullptr, TEXT("PawnOwner is not valid!"));
}
if (!MovementSystem)
{
MovementSystem = PawnOwner->FindComponentByClass<UGMS_MovementSystemComponent>();
checkf(MovementSystem!=nullptr, TEXT("Movement Sysytem Component is not valid!"));
}
if (!AnimStateNameToTagMapping.IsEmpty())
{
Parent->RegisterStateNameToTagMapping(this, AnimStateNameToTagMapping);
}
}
void UGMS_AnimLayer::OnUnlinked_Implementation()
{
if (Parent.IsValid())
{
if (!AnimStateNameToTagMapping.IsEmpty())
{
Parent->UnregisterStateNameToTagMapping(this);
}
Parent = nullptr;
}
}
UGMS_AnimLayer::UGMS_AnimLayer()
{
bUseMainInstanceMontageEvaluationData = true;
}
UGMS_MainAnimInstance* UGMS_AnimLayer::GetParent() const
{
return Parent.Get();
}
void UGMS_AnimLayer::ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting)
{
}
void UGMS_AnimLayer::ResetSetting_Implementation()
{
}
void UGMS_AnimLayer::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
Parent = Cast<UGMS_MainAnimInstance>(GetSkelMeshComponent()->GetAnimInstance());
PawnOwner = Cast<APawn>(GetOwningActor());
#if WITH_EDITOR
if (!GetWorld()->IsGameWorld())
{
// Use default objects for editor preview.
if (!Parent.IsValid())
{
Parent = GetMutableDefault<UGMS_MainAnimInstance>();
}
if (!IsValid(PawnOwner))
{
PawnOwner = GetMutableDefault<APawn>();
}
}
#endif
}
void UGMS_AnimLayer::NativeBeginPlay()
{
Super::NativeBeginPlay();
ensure(PawnOwner);
MovementSystem = PawnOwner->FindComponentByClass<UGMS_MovementSystemComponent>();
}

View File

@@ -0,0 +1,6 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimLayer_Additive.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimLayer_Additive)

View File

@@ -0,0 +1,7 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimLayer_Overlay.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimLayer_Overlay)

View File

@@ -0,0 +1,448 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimLayer_Overlay_ParallelPoseStack.h"
#include "Animation/AnimSequence.h"
#include "SequenceEvaluatorLibrary.h"
#include "Locomotions/GMS_MainAnimInstance.h"
#include "UObject/ObjectSaveContext.h"
#include "Utility/GMS_Log.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimLayer_Overlay_ParallelPoseStack)
void FGMS_AnimData_BodyPose_Full::UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const
{
if (Pose != nullptr)
{
LayeringState.HeadBlendAmount = HeadBlend.BlendAmount;
LayeringState.HeadAdditiveBlendAmount = HeadBlend.AdditiveBlendAmount;
LayeringState.HeadSlotBlendAmount = HeadBlend.SlotBlendAmount;
LayeringState.ArmLeftBlendAmount = ArmLeftBlend.BlendAmount;
LayeringState.ArmLeftAdditiveBlendAmount = ArmLeftBlend.AdditiveBlendAmount;
LayeringState.ArmLeftSlotBlendAmount = ArmLeftBlend.SlotBlendAmount;
LayeringState.ArmLeftLocalSpaceBlendAmount = ArmLeftBlend.bMeshSpace ? 0.f : 1.f;
LayeringState.ArmLeftMeshSpaceBlendAmount = ArmLeftBlend.bMeshSpace ? 1.f : 0.f;
LayeringState.ArmRightBlendAmount = ArmRightBlend.BlendAmount;
LayeringState.ArmRightAdditiveBlendAmount = ArmRightBlend.AdditiveBlendAmount;
LayeringState.ArmRightSlotBlendAmount = ArmRightBlend.SlotBlendAmount;
LayeringState.ArmRightLocalSpaceBlendAmount = ArmRightBlend.bMeshSpace ? 0.f : 1.f;
LayeringState.ArmRightMeshSpaceBlendAmount = ArmRightBlend.bMeshSpace ? 1.f : 0.f;
LayeringState.HandLeftBlendAmount = HandLeftBlend.BlendAmount;
LayeringState.HandRightBlendAmount = HandRightBlend.BlendAmount;
LayeringState.SpineBlendAmount = SpineBlend.BlendAmount;
LayeringState.SpineAdditiveBlendAmount = SpineBlend.AdditiveBlendAmount;
LayeringState.SpineSlotBlendAmount = SpineBlend.SlotBlendAmount;
LayeringState.PelvisBlendAmount = PelvisBlend.BlendAmount;
LayeringState.PelvisSlotBlendAmount = PelvisBlend.SlotBlendAmount;
LayeringState.LegsBlendAmount = LegsBlend.BlendAmount;
LayeringState.LegsSlotBlendAmount = LegsBlend.SlotBlendAmount;
}
}
void FGMS_AnimData_BodyPose_Upper::UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const
{
if (Pose != nullptr)
{
LayeringState.HeadBlendAmount = HeadBlend.BlendAmount;
LayeringState.HeadAdditiveBlendAmount = HeadBlend.AdditiveBlendAmount;
LayeringState.HeadSlotBlendAmount = HeadBlend.SlotBlendAmount;
LayeringState.ArmLeftBlendAmount = ArmLeftBlend.BlendAmount;
LayeringState.ArmLeftAdditiveBlendAmount = ArmLeftBlend.AdditiveBlendAmount;
LayeringState.ArmLeftSlotBlendAmount = ArmLeftBlend.SlotBlendAmount;
LayeringState.ArmLeftLocalSpaceBlendAmount = ArmLeftBlend.bMeshSpace ? 0.f : 1.f;
LayeringState.ArmLeftMeshSpaceBlendAmount = ArmLeftBlend.bMeshSpace ? 1.f : 0.f;
LayeringState.ArmRightBlendAmount = ArmRightBlend.BlendAmount;
LayeringState.ArmRightAdditiveBlendAmount = ArmRightBlend.AdditiveBlendAmount;
LayeringState.ArmRightSlotBlendAmount = ArmRightBlend.SlotBlendAmount;
LayeringState.ArmRightLocalSpaceBlendAmount = ArmRightBlend.bMeshSpace ? 0.f : 1.f;
LayeringState.ArmRightMeshSpaceBlendAmount = ArmRightBlend.bMeshSpace ? 1.f : 0.f;
LayeringState.HandLeftBlendAmount = HandLeftBlend.BlendAmount;
LayeringState.HandRightBlendAmount = HandRightBlend.BlendAmount;
LayeringState.SpineBlendAmount = SpineBlend.BlendAmount;
LayeringState.SpineAdditiveBlendAmount = SpineBlend.AdditiveBlendAmount;
LayeringState.SpineSlotBlendAmount = SpineBlend.SlotBlendAmount;
}
}
void FGMS_AnimData_BodyPose_Head::UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const
{
if (Pose != nullptr)
{
LayeringState.HeadBlendAmount = Blend.BlendAmount;
LayeringState.HeadAdditiveBlendAmount = Blend.AdditiveBlendAmount;
LayeringState.HeadSlotBlendAmount = Blend.SlotBlendAmount;
}
}
void FGMS_AnimData_BodyPose_ArmLeft::UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const
{
if (Pose != nullptr)
{
LayeringState.ArmLeftBlendAmount = Blend.BlendAmount;
LayeringState.ArmLeftAdditiveBlendAmount = Blend.AdditiveBlendAmount;
LayeringState.ArmLeftSlotBlendAmount = Blend.SlotBlendAmount;
LayeringState.ArmLeftLocalSpaceBlendAmount = Blend.bMeshSpace ? 0.f : 1.f;
LayeringState.ArmLeftMeshSpaceBlendAmount = Blend.bMeshSpace ? 1.f : 0.f;
LayeringState.HandLeftBlendAmount = HandBlend.BlendAmount;
}
}
void FGMS_AnimData_BodyPose_ArmRight::UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const
{
if (Pose != nullptr)
{
LayeringState.ArmRightBlendAmount = Blend.BlendAmount;
LayeringState.ArmRightAdditiveBlendAmount = Blend.AdditiveBlendAmount;
LayeringState.ArmRightSlotBlendAmount = Blend.SlotBlendAmount;
LayeringState.ArmRightLocalSpaceBlendAmount = Blend.bMeshSpace ? 0.f : 1.f;
LayeringState.ArmRightMeshSpaceBlendAmount = Blend.bMeshSpace ? 1.f : 0.f;
LayeringState.HandRightBlendAmount = HandBlend.BlendAmount;
}
}
void FGMS_AnimData_BodyPose_Lower::UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const
{
if (Pose != nullptr)
{
LayeringState.PelvisBlendAmount = PelvisBlend.BlendAmount;
LayeringState.PelvisSlotBlendAmount = PelvisBlend.SlotBlendAmount;
LayeringState.LegsBlendAmount = LegsBlend.BlendAmount;
LayeringState.LegsSlotBlendAmount = LegsBlend.SlotBlendAmount;
}
}
// FGMS_BodyPartOverridePolicy 实现
FGMS_BodyPartOverridePolicy::FGMS_BodyPartOverridePolicy()
{
FallbackChain = {
{EGMS_BodyMask::Head, {EGMS_BodyMask::UpperBody, EGMS_BodyMask::FullBody}},
// {EGMS_BodyMask::HandLeft, {EGMS_BodyMask::ArmLeft, EGMS_BodyMask::UpperBody, EGMS_BodyMask::FullBody}},
// {EGMS_BodyMask::HandRight, {EGMS_BodyMask::ArmRight, EGMS_BodyMask::UpperBody, EGMS_BodyMask::FullBody}},
{EGMS_BodyMask::ArmLeft, {EGMS_BodyMask::UpperBody, EGMS_BodyMask::FullBody}},
{EGMS_BodyMask::ArmRight, {EGMS_BodyMask::UpperBody, EGMS_BodyMask::FullBody}},
{EGMS_BodyMask::UpperBody, {EGMS_BodyMask::FullBody}},
{EGMS_BodyMask::LowerBody, {EGMS_BodyMask::FullBody}},
{EGMS_BodyMask::FullBody, {}},
};
}
#if WITH_EDITORONLY_DATA
void FGMS_AnimData_BodyPose::PreSave()
{
bool bValid = Pose != nullptr && PoseExplicitTime >= 0 && Pose->GetSkeleton() != nullptr;
EditorFriendlyName = bValid ? GetNameSafe(Pose) : TEXT("Invalid Pose!");
}
void FGMS_OverlayModeSetting_ParallelPoseStack::PreSave()
{
for (auto& Pose : FullBodyPoses)
{
Pose.GetMutable().PreSave();
}
}
void UGMS_AnimLayerSetting_Overlay_ParallelPoseStack::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
AcceleratedOverlayModes.Empty();
for (FGMS_OverlayModeSetting_ParallelPoseStack& Mode : OverlayModes)
{
Mode.PreSave();
AcceleratedOverlayModes.Emplace(Mode.Tag, Mode);
}
}
#endif
bool FGMS_BodyPartOverridePolicy::CanOverride(EGMS_BodyMask NewPart, EGMS_BodyMask ExistingPart, int32 NewPriority, int32 ExistingPriority) const
{
return static_cast<uint8>(NewPart) < static_cast<uint8>(ExistingPart) ||
(NewPart == ExistingPart && NewPriority < ExistingPriority);
}
void FGMS_BodyPartOverridePolicy::ApplyCoverage(EGMS_BodyMask BodyPart, TArray<TInstancedStruct<FGMS_AnimData_BodyPose>>& SelectedPoses,
const TInstancedStruct<FGMS_AnimData_BodyPose>& NewPose, int32 NewPriority) const
{
int32 BodyPartIndex = static_cast<int32>(BodyPart);
SelectedPoses[BodyPartIndex] = NewPose;
}
void UGMS_AnimLayer_Overlay_ParallelPoseStack::ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting)
{
check(IsValid(Setting));
if (CurrentSetting != Setting || CurrentOverlayMode != GetParent()->OverlayMode)
{
ResetSetting();
}
if (const UGMS_AnimLayerSetting_Overlay_ParallelPoseStack* DS = Cast<UGMS_AnimLayerSetting_Overlay_ParallelPoseStack>(Setting))
{
if (CurrentSetting == DS && CurrentOverlayMode == GetParent()->OverlayMode)
{
return;
}
if (!DS->AcceleratedOverlayModes.Contains(GetParent()->OverlayMode))
{
return;
}
const FGMS_OverlayModeSetting_ParallelPoseStack& ModeSetting = DS->AcceleratedOverlayModes[GetParent()->OverlayMode];
if (ModeSetting.BasePose == nullptr)
{
bHasValidSetting = false;
UE_LOG(LogGMS, Warning, TEXT("BasePose is null for overlay mode %s"), *GetParent()->OverlayMode.ToString());
return;
}
CurrentSetting = DS;
CurrentOverlayMode = GetParent()->OverlayMode;
bHasValidSetting = true;
BasePose = ModeSetting.BasePose;
}
}
void UGMS_AnimLayer_Overlay_ParallelPoseStack::ResetSetting_Implementation()
{
bHasValidSetting = false;
CurrentOverlayMode = FGameplayTag::EmptyTag;
CurrentSetting = nullptr;
BasePose = nullptr;
for (auto& Pose : SelectedBodyPoses)
{
Pose.GetMutable().Reset();
}
LayeringState.ZeroOut();
}
void UGMS_AnimLayer_Overlay_ParallelPoseStack::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
// 预分配 SelectedBodyPoses
SelectedBodyPoses.SetNum(static_cast<int32>(EGMS_BodyMask::MAX));
// 保证数组足够。
SelectedBodyPoses = {
TInstancedStruct<FGMS_AnimData_BodyPose>::Make(FGMS_AnimData_BodyPose_Head()),
TInstancedStruct<FGMS_AnimData_BodyPose>::Make(FGMS_AnimData_BodyPose_ArmLeft()),
TInstancedStruct<FGMS_AnimData_BodyPose>::Make(FGMS_AnimData_BodyPose_ArmRight()),
TInstancedStruct<FGMS_AnimData_BodyPose>::Make(FGMS_AnimData_BodyPose_Upper()),
TInstancedStruct<FGMS_AnimData_BodyPose>::Make(FGMS_AnimData_BodyPose_Lower()),
TInstancedStruct<FGMS_AnimData_BodyPose>::Make(FGMS_AnimData_BodyPose_Full()),
};
}
void UGMS_AnimLayer_Overlay_ParallelPoseStack::NativeThreadSafeUpdateAnimation(float DeltaSeconds)
{
Super::NativeThreadSafeUpdateAnimation(DeltaSeconds);
if (!bHasValidSetting)
{
LayeringState.ZeroOut();
for (auto& Pose : SelectedBodyPoses)
{
Pose.GetMutable().Reset();
}
return;
}
// Check if tags or nodes have changed
SelectPoses(GetParent()->OwnedTags, GetParent()->NodeRelevanceTags);
// Update LayeringState based on selected poses
UpdateLayeringState(DeltaSeconds);
UpdateLayeringSmoothState(DeltaSeconds);
}
#define SELECT_POSE(PoseArray, PoseType, TargetPose, Nodes, Tags) \
for (int32 Index = 0; Index < PoseArray.Num(); ++Index) \
{ \
const TInstancedStruct<PoseType>& PoseInst = PoseArray[Index];; \
const PoseType& Pose = PoseInst.Get<PoseType>(); \
if (!Pose.IsValid()) \
{ \
continue; \
} \
if (!Pose.RelevanceQuery.IsEmpty() && !Pose.RelevanceQuery.Matches(Nodes)) \
{ \
continue; \
} \
if (!Pose.TagQuery.IsEmpty() && !Pose.TagQuery.Matches(Tags)) \
{ \
continue; \
} \
if (!TargetPose.GetMutable().IsValid() || Index < TargetPose.GetMutable().Priority) \
{ \
TargetPose = PoseInst; \
TargetPose.GetMutable().Priority = Index; \
} \
if (!TargetPose.GetMutable().IsValid()) \
{ \
TargetPose.GetMutable().Reset(); \
} \
}
void UGMS_AnimLayer_Overlay_ParallelPoseStack::SelectPoses(const FGameplayTagContainer& Tags, const FGameplayTagContainer& Nodes)
{
//Reset SelectedBodyPoses
for (auto& Pose : SelectedBodyPoses)
{
Pose.GetMutable().Reset();
}
SELECT_POSE(GetOverlayModeSetting().FullBodyPoses, FGMS_AnimData_BodyPose_Full, SelectedBodyPoses[static_cast<int32>(EGMS_BodyMask::FullBody)], Nodes, Tags)
SELECT_POSE(GetOverlayModeSetting().UpperBodyPoses, FGMS_AnimData_BodyPose_Upper, SelectedBodyPoses[static_cast<int32>(EGMS_BodyMask::UpperBody)], Nodes, Tags)
SELECT_POSE(GetOverlayModeSetting().ArmLeftPoses, FGMS_AnimData_BodyPose_ArmLeft, SelectedBodyPoses[static_cast<int32>(EGMS_BodyMask::ArmLeft)], Nodes, Tags)
SELECT_POSE(GetOverlayModeSetting().ArmRightPoses, FGMS_AnimData_BodyPose_ArmRight, SelectedBodyPoses[static_cast<int32>(EGMS_BodyMask::ArmRight)], Nodes, Tags)
SELECT_POSE(GetOverlayModeSetting().HeadPoses, FGMS_AnimData_BodyPose_Head, SelectedBodyPoses[static_cast<int32>(EGMS_BodyMask::Head)], Nodes, Tags)
}
void UGMS_AnimLayer_Overlay_ParallelPoseStack::UpdateLayeringState(float DeltaSeconds)
{
// Initialize LayeringState
// LayeringState.ZeroOut();
LayeringState.HeadBlendAmount = 0.0f;
LayeringState.HeadAdditiveBlendAmount = 0.0f;
LayeringState.HeadSlotBlendAmount = 0.0f;
LayeringState.ArmLeftBlendAmount = 0.0f;
LayeringState.ArmLeftAdditiveBlendAmount = 0.0f;
LayeringState.ArmLeftSlotBlendAmount = 0.0f;
//Make this layer always enabled.(and override later)
// LayeringState.ArmLeftLocalSpaceBlendAmount = 1.0f;
// LayeringState.ArmLeftMeshSpaceBlendAmount = 1.0f;
LayeringState.ArmRightBlendAmount = 0.0f;
LayeringState.ArmRightAdditiveBlendAmount = 0.0f;
LayeringState.ArmRightSlotBlendAmount = 0.0f;
//Make this layer always enabled.(and override later)
// LayeringState.ArmRightLocalSpaceBlendAmount = 1.0f;
// LayeringState.ArmRightMeshSpaceBlendAmount = 1.0f;
LayeringState.HandLeftBlendAmount = 0.0f;
LayeringState.HandRightBlendAmount = 0.0f;
LayeringState.SpineBlendAmount = 0.0f;
LayeringState.SpineAdditiveBlendAmount = 0.0f;
LayeringState.SpineSlotBlendAmount = 0.0f;
LayeringState.PelvisBlendAmount = 0.0f;
LayeringState.PelvisSlotBlendAmount = 0.0f;
LayeringState.LegsBlendAmount = 0.0f;
LayeringState.LegsSlotBlendAmount = 0.0f;
for (int32 i = SelectedBodyPoses.Num() - 1; i >= 0; i--)
{
if (!SelectedBodyPoses[i].IsValid())
{
continue;
}
const FGMS_AnimData_BodyPose& Pose = SelectedBodyPoses[i].Get();
Pose.UpdateLayeringState(LayeringState);
}
}
void UGMS_AnimLayer_Overlay_ParallelPoseStack::UpdateLayeringSmoothState(float DeltaSeconds)
{
static constexpr float Speed = 2.0f;
LayeringSmoothState.HeadBlendAmount = LayeringState.HeadBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.HeadBlendAmount, 0.0f, DeltaSeconds, Speed)
: LayeringState.HeadBlendAmount;
LayeringSmoothState.ArmLeftBlendAmount = LayeringState.ArmLeftBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.ArmLeftBlendAmount, 0.0f, DeltaSeconds, Speed)
: LayeringState.ArmLeftBlendAmount;
LayeringSmoothState.ArmRightBlendAmount = LayeringState.ArmRightBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.ArmRightBlendAmount, 0.0f, DeltaSeconds, Speed)
: LayeringState.ArmRightBlendAmount;
LayeringSmoothState.SpineBlendAmount = LayeringState.SpineBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.SpineBlendAmount, 0.0f, DeltaSeconds, Speed)
: LayeringState.SpineBlendAmount;
LayeringSmoothState.PelvisBlendAmount = LayeringState.PelvisBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.PelvisBlendAmount, 0.0f, DeltaSeconds, Speed)
: LayeringState.PelvisBlendAmount;
LayeringSmoothState.LegsBlendAmount = LayeringState.LegsBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.LegsBlendAmount, 0.0f, DeltaSeconds, Speed)
: LayeringState.LegsBlendAmount;
}
const FGMS_OverlayModeSetting_ParallelPoseStack& UGMS_AnimLayer_Overlay_ParallelPoseStack::GetOverlayModeSetting() const
{
return CurrentSetting->AcceleratedOverlayModes[CurrentOverlayMode];
}
void UGMS_AnimLayer_Overlay_ParallelPoseStack::UpdateAnim(const FAnimUpdateContext& Context, const FAnimNodeReference& Node, const EGMS_BodyMask& BodyMask, const FGMS_AnimData_BodyPose& BodyPose)
{
if (BodyPose.Pose == nullptr)
{
GMS_LOG(Warning, "Trying to update an empty pose for BodyMask %s.", *UEnum::GetValueAsString(BodyMask));
return;
}
EAnimNodeReferenceConversionResult Result = EAnimNodeReferenceConversionResult::Succeeded;
FSequenceEvaluatorReference SequenceEvaluator = USequenceEvaluatorLibrary::ConvertToSequenceEvaluator(Node, Result);
if (Result == EAnimNodeReferenceConversionResult::Succeeded && USequenceEvaluatorLibrary::GetSequence(SequenceEvaluator) != BodyPose.Pose)
{
GMS_LOG(Verbose, "Updated pose %s for BodyMask %s ", *GetNameSafe(BodyPose.Pose), *UEnum::GetValueAsString(BodyMask));
USequenceEvaluatorLibrary::SetExplicitTime(SequenceEvaluator, BodyPose.PoseExplicitTime);
USequenceEvaluatorLibrary::SetSequenceWithInertialBlending(Context, SequenceEvaluator, BodyPose.Pose, 0.1f);
}
}
void UGMS_AnimLayer_Overlay_ParallelPoseStack::BasePose_AnimUpdate_Implementation(FAnimUpdateContext& Context, FAnimNodeReference& Node)
{
if (!bHasValidSetting)
{
return;
}
const FGMS_OverlayModeSetting_ParallelPoseStack& ModeSetting = GetOverlayModeSetting();
EAnimNodeReferenceConversionResult Result = EAnimNodeReferenceConversionResult::Succeeded;
FSequenceEvaluatorReference SequenceEvaluator = USequenceEvaluatorLibrary::ConvertToSequenceEvaluator(Node, Result);
if (Result == EAnimNodeReferenceConversionResult::Succeeded)
{
USequenceEvaluatorLibrary::SetExplicitTime(SequenceEvaluator, 0.0f);
USequenceEvaluatorLibrary::SetSequenceWithInertialBlending(Context, SequenceEvaluator, ModeSetting.BasePose);
}
}
void UGMS_AnimLayer_Overlay_ParallelPoseStack::BodyPart_AnimUpdate_Implementation(FAnimUpdateContext& Context, FAnimNodeReference& Node, EGMS_BodyMask BodyMask)
{
if (!bHasValidSetting)
{
return;
}
int32 BodyPartIndex = static_cast<int32>(BodyMask);
if (SelectedBodyPoses[BodyPartIndex].Get().IsValid())
{
UpdateAnim(Context, Node, BodyMask, SelectedBodyPoses[BodyPartIndex].Get());
return;
}
// 回退逻辑
if (const TArray<EGMS_BodyMask>* FallbackParts = OverridePolicy.FallbackChain.Find(BodyMask))
{
for (EGMS_BodyMask FallbackPart : *FallbackParts)
{
int32 FallbackIndex = static_cast<int32>(FallbackPart);
if (SelectedBodyPoses[FallbackIndex].Get().IsValid())
{
UpdateAnim(Context, Node, BodyMask, SelectedBodyPoses[FallbackIndex].Get());
return;
}
}
}
}

View File

@@ -0,0 +1,237 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimLayer_Overlay_ParallelSequenceStack.h"
#include "Locomotions/GMS_MainAnimInstance.h"
#include "UObject/ObjectSaveContext.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimLayer_Overlay_ParallelSequenceStack)
void UGMS_AnimLayer_Overlay_ParallelSequenceStack::NativeBeginPlay()
{
Super::NativeBeginPlay();
if (OverlayStackStates.IsEmpty())
{
OverlayStackStates.AddDefaulted(MaxLayers);
}
}
void UGMS_AnimLayer_Overlay_ParallelSequenceStack::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
}
void UGMS_AnimLayer_Overlay_ParallelSequenceStack::NativeThreadSafeUpdateAnimation(float DeltaSeconds)
{
Super::NativeThreadSafeUpdateAnimation(DeltaSeconds);
RefreshRelevance();
RefreshBlend(DeltaSeconds);
}
void UGMS_AnimLayer_Overlay_ParallelSequenceStack::ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting)
{
check(IsValid(Setting))
//setting changes or invalid, reset.
if (PrevSetting != Setting || PrevOverlayMode != GetParent()->OverlayMode)
{
PrevOverlayMode = FGameplayTag::EmptyTag;
ResetSetting();
PrevSetting = nullptr;
}
// Apply new data to anim instance.
if (const UGMS_AnimLayerSetting_Overlay_ParallelSequenceStack* DS = Cast<UGMS_AnimLayerSetting_Overlay_ParallelSequenceStack>(Setting))
{
//same setting and overlay.
if (PrevSetting == DS && PrevOverlayMode == GetParent()->OverlayMode)
{
return;
}
PrevSetting = DS;
PrevOverlayMode = GetParent()->OverlayMode;
if (OverlayStackStates.IsEmpty())
{
OverlayStackStates.AddDefaulted(MaxLayers);
}
if (!DS->AcceleratedOverlayModes.Contains(GetParent()->OverlayMode))
{
return;
}
const TArray<FGMS_ParallelSequenceStack>& Stacks = DS->AcceleratedOverlayModes[GetParent()->OverlayMode].Stacks;
for (int32 i = 0; i < OverlayStackStates.Num(); i++)
{
if (Stacks.IsValidIndex(i))
{
OverlayStacks.EmplaceAt(i, Stacks[i]);
}
else
{
OverlayStacks.EmplaceAt(i, FGMS_ParallelSequenceStack());
}
}
}
}
void UGMS_AnimLayer_Overlay_ParallelSequenceStack::ResetSetting_Implementation()
{
OverlayStacks.Reset(MaxLayers);
for (FGMS_ParallelSequenceStackState& OverlayStackState : OverlayStackStates)
{
OverlayStackState.Overlay.bValid = false;
}
}
void UGMS_AnimLayer_Overlay_ParallelSequenceStack::RefreshRelevance()
{
if (IsValid(GetParent()))
{
for (int32 i = 0; i < OverlayStackStates.Num(); i++)
{
if (OverlayStacks.IsValidIndex(i))
{
OverlayStackStates[i].bRelevant = GetParent()->NodeRelevanceTags.HasAny(OverlayStacks[i].TargetAnimNodes);
}
else
{
OverlayStackStates[i].bRelevant = false;
}
}
}
}
void UGMS_AnimLayer_Overlay_ParallelSequenceStack::RefreshBlend(float DeltaSeconds)
{
if (IsValid(GetParent()))
{
const FGameplayTagContainer& Tags = GetParent()->OwnedTags;
//Refresh current overlay.
for (int32 i = 0; i < OverlayStackStates.Num(); i++)
{
int32 foundJ = INDEX_NONE;
if (OverlayStacks.IsValidIndex(i))
{
for (int32 j = 0; j < OverlayStacks[i].Overlays.Num(); j++)
{
const FGMS_ParallelSequenceStackEntry& Overlay = OverlayStacks[i].Overlays[j];
if (Overlay.bValid && (Overlay.TagQuery.IsEmpty() || Overlay.TagQuery.Matches(Tags)))
{
foundJ = j;
break;
}
}
}
if (foundJ != INDEX_NONE)
{
if (OverlayStacks[i].Overlays[foundJ] != OverlayStackStates[i].Overlay)
{
OverlayStackStates[i].Overlay = OverlayStacks[i].Overlays[foundJ];
OverlayStackStates[i].BlendOutSpeed = OverlayStacks[i].Overlays[foundJ].BlendOutSpeed;
}
}
else
{
OverlayStackStates[i].Overlay.bValid = false;
}
}
//Refresh blends.
for (int32 i = 0; i < OverlayStackStates.Num(); i++)
{
FGMS_ParallelSequenceStackState& CurrentState = OverlayStackStates[i];
if (CurrentState.Overlay.bValid && CurrentState.bRelevant)
{
if (CurrentState.Overlay.BlendInSpeed > 0)
{
CurrentState.BlendWeight = FMath::FInterpTo(CurrentState.BlendWeight, CurrentState.Overlay.BlendWeight, DeltaSeconds, CurrentState.Overlay.BlendInSpeed);
}
else
{
CurrentState.BlendWeight = CurrentState.Overlay.BlendWeight;
}
}
else
{
if (CurrentState.BlendOutSpeed > 0)
{
CurrentState.BlendWeight = FMath::FInterpTo(CurrentState.BlendWeight, 0.0f, DeltaSeconds, CurrentState.BlendOutSpeed);
}
else
{
CurrentState.BlendWeight = 0.0f;
}
}
}
}
}
#if WITH_EDITORONLY_DATA
void FGMS_ParallelSequenceStackEntry::Validate()
{
bValid = true;
if (Sequence == nullptr)
{
bValid = false;
EditorMessage = TEXT("Invalid Overlay,No valid sequence");
return;
}
if (BlendMode == EGMS_LayeredBoneBlendMode::BlendMask)
{
if (Sequence->GetSkeleton() == nullptr || BlendMaskName == NAME_None)
{
bValid = false;
EditorMessage = TEXT("Invalid Overlay,No valid blend mask name!");
return;
}
UBlendProfile* BlendMask = Sequence->GetSkeleton()->GetBlendProfile(BlendMaskName);
if (BlendMask == nullptr)
{
bValid = false;
EditorMessage = TEXT("Invalid Overlay,The skeleton of animation doesn't have specified BlendMask");
return;
}
}
if (bValid)
{
EditorMessage = FString::Format(TEXT("Play ({0}) on ({1}) with condition({2})"), {Sequence->GetName(), BlendMaskName.ToString(), TagQuery.GetDescription()});
}
else
{
EditorMessage = TEXT("Invalid Overlay");
}
}
void UGMS_AnimLayerSetting_Overlay_ParallelSequenceStack::PreSave(FObjectPreSaveContext SaveContext)
{
AcceleratedOverlayModes.Empty();
for (FGMS_OverlayModeSetting_ParallelSequenceStack& Mode : OverlayModes)
{
for (FGMS_ParallelSequenceStack& Stack : Mode.Stacks)
{
for (FGMS_ParallelSequenceStackEntry& Overlay : Stack.Overlays)
{
Overlay.Validate();
}
Stack.EditorFriendlyName = Stack.TargetAnimNodes.IsEmpty()
? TEXT("Invalid Overlay")
: FString::Format(TEXT("Play overlay for states:[{0}]"), {Stack.TargetAnimNodes.ToStringSimple(false).Replace(TEXT("GMS.SM."), TEXT(""))});
}
AcceleratedOverlayModes.Emplace(Mode.Tag, Mode);
}
Super::PreSave(SaveContext);
}
#endif

View File

@@ -0,0 +1,387 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimLayer_Overlay_PoseStack.h"
#include "AnimationWarpingLibrary.h"
#include "Locomotions/GMS_MainAnimInstance.h"
#include "Utility/GMS_Log.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimLayer_Overlay_PoseStack)
#if WITH_EDITOR
#include "UObject/ObjectSaveContext.h"
void UGMS_AnimLayerSetting_Overlay_PoseStack::RunDataMigration(bool bResetDeprecatedSettings)
{
Modify();
PRAGMA_DISABLE_DEPRECATION_WARNINGS
for (FGMS_OverlayModeSetting_PoseStack& Mode : OverlayModes)
{
Mode.Poses.Empty();
// migrate simple pose.
if (Mode.PoseOverlaySettingType == EGMS_PoseOverlaySettingType::Simple)
{
if (Mode.SimplePoseSetting.IdlePose)
{
FGMS_PoseStackEntry Entry;
Entry.Pose = Mode.SimplePoseSetting.IdlePose;
Entry.ExplicitTime = Mode.SimplePoseSetting.IdlePoseExplicitTime;
Entry.AimingSweepPose = Mode.SimplePoseSetting.AimingSweepPose;
Entry.PoseBlend.ApplyFromSequence(Mode.SimplePoseSetting.IdlePose, Mode.SimplePoseSetting.IdlePoseExplicitTime);
Entry.RelevanceQuery = FGameplayTagQuery::BuildQuery(FGameplayTagQueryExpression().AnyTagsMatch().AddTag(FGameplayTag::RequestGameplayTag(TEXT("GMS.SM.Grounded.Idle"))));
Mode.Poses.Add(Entry);
}
if (Mode.SimplePoseSetting.MovingPose)
{
FGMS_PoseStackEntry Entry;
Entry.Pose = Mode.SimplePoseSetting.MovingPose;
Entry.ExplicitTime = Mode.SimplePoseSetting.MovingPoseExplicitTime;
Entry.AimingSweepPose = Mode.SimplePoseSetting.AimingSweepPose;
Entry.PoseBlend.ApplyFromSequence(Mode.SimplePoseSetting.MovingPose, Mode.SimplePoseSetting.MovingPoseExplicitTime);
Mode.Poses.Add(Entry);
}
}
// migrate layered pose.
if (Mode.PoseOverlaySettingType == EGMS_PoseOverlaySettingType::Layered)
{
for (const FGMS_AnimData_PoseOverlay_Layered& OldSetting : Mode.LayeredPoseSetting)
{
if (OldSetting.MovingPose)
{
// Has no idle tag.
FGameplayTagQueryExpression RelevanceExp = FGameplayTagQueryExpression().NoTagsMatch().AddTag(FGameplayTag::RequestGameplayTag(TEXT("GMS.SM.Grounded.Idle")));
FGMS_PoseStackEntry Entry;
Entry.Pose = OldSetting.MovingPose;
Entry.ExplicitTime = OldSetting.MovingPoseExplicitTime;
Entry.AimingSweepPose = OldSetting.AimingSweepPose;
Entry.PoseBlend.ApplyFromSequence(OldSetting.MovingPose, OldSetting.MovingPoseExplicitTime);
Entry.TagQuery = OldSetting.TagQuery;
Entry.RelevanceQuery = FGameplayTagQuery::BuildQuery(RelevanceExp);
Mode.Poses.Add(Entry);
}
if (OldSetting.IdlePose)
{
// Must have idle tag.
FGameplayTagQueryExpression RelevanceExp = FGameplayTagQueryExpression().AllTagsMatch().AddTag(FGameplayTag::RequestGameplayTag(TEXT("GMS.SM.Grounded.Idle")));
FGameplayTagQueryExpression Exp2;
OldSetting.TagQuery.GetQueryExpr(Exp2);
FGMS_PoseStackEntry Entry;
Entry.Pose = OldSetting.IdlePose;
Entry.ExplicitTime = OldSetting.IdlePoseExplicitTime;
Entry.AimingSweepPose = OldSetting.AimingSweepPose;
Entry.PoseBlend.ApplyFromSequence(OldSetting.IdlePose, OldSetting.IdlePoseExplicitTime);
Entry.RelevanceQuery = FGameplayTagQuery::BuildQuery(FGameplayTagQueryExpression().AllExprMatch().AddExpr(RelevanceExp).AddExpr(Exp2));
Mode.Poses.Add(Entry);
}
}
}
if (bResetDeprecatedSettings)
{
Mode.SimplePoseSetting = FGMS_AnimData_PoseOverlay_Simple();
Mode.LayeredPoseSetting.Empty();
}
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
void UGMS_AnimLayerSetting_Overlay_PoseStack::RunDataMigrationFromDefinition(UGMS_MovementDefinition* InDefinition, bool bResetDeprecatedSettings)
{
if (IsValid(InDefinition))
{
InDefinition->Modify();
for (TPair<FGameplayTag, FGMS_MovementSetSetting>& MovementSet : InDefinition->MovementSets)
{
FGMS_MovementSetSetting& MSS = MovementSet.Value;
if (MSS.bUseInstancedOverlaySetting && MSS.AnimLayerSetting_Overlay)
{
if (auto Overlay = Cast<UGMS_AnimLayerSetting_Overlay_PoseStack>(MSS.AnimLayerSetting_Overlay))
{
Overlay->RunDataMigration(bResetDeprecatedSettings);
}
}
else if (!MSS.bUseInstancedOverlaySetting && MSS.DA_AnimLayerSetting_Overlay)
{
if (auto Overlay = Cast<UGMS_AnimLayerSetting_Overlay_PoseStack>(MSS.DA_AnimLayerSetting_Overlay))
{
Overlay->RunDataMigration(bResetDeprecatedSettings);
}
}
}
}
}
void UGMS_AnimLayerSetting_Overlay_PoseStack::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
for (FGMS_OverlayModeSetting_PoseStack& OverlayMode : OverlayModes)
{
for (FGMS_PoseStackEntry& Entry : OverlayMode.Poses)
{
Entry.EditorFriendlyName = FString::Format(TEXT("{0} at {1}"), {
GetNameSafe(Entry.Pose), Entry.ExplicitTime
});
}
}
AcceleratedOverlayModes.Empty();
for (const FGMS_OverlayModeSetting_PoseStack& PoseOverlay : OverlayModes)
{
AcceleratedOverlayModes.Emplace(PoseOverlay.Tag, PoseOverlay);
}
}
#endif
void FGMS_PerBodyPoseBlendSetting::ApplyFromSequence(const UAnimSequence* InSequence, float ExplicitTime)
{
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerHead", ExplicitTime, HeadBlend.BlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerHeadAdditive", ExplicitTime, HeadBlend.AdditiveBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerHeadSlot", ExplicitTime, HeadBlend.SlotBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmLeft", ExplicitTime, ArmLeftBlend.BlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmLeftAdditive", ExplicitTime, ArmLeftBlend.AdditiveBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmLeftSlot", ExplicitTime, ArmLeftBlend.SlotBlendAmount);
float ArmLeftLocalSpaceBlendAmount = 0;
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmLeftLocalSpace", ExplicitTime, ArmLeftLocalSpaceBlendAmount);
ArmLeftBlend.bMeshSpace = !FAnimWeight::IsFullWeight(ArmLeftLocalSpaceBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmRight", ExplicitTime, ArmRightBlend.BlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmRightAdditive", ExplicitTime, ArmRightBlend.AdditiveBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmRightSlot", ExplicitTime, ArmRightBlend.SlotBlendAmount);
float ArmRightLocalSpaceBlendAmount = 0;
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmRightLocalSpace", ExplicitTime, ArmRightLocalSpaceBlendAmount);
ArmRightBlend.bMeshSpace = !FAnimWeight::IsFullWeight(ArmRightLocalSpaceBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerHandLeft", ExplicitTime, HandLeftBlend.BlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerHandRight", ExplicitTime, HandLeftBlend.BlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerSpine", ExplicitTime, SpineBlend.BlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerSpineAdditive", ExplicitTime, SpineBlend.AdditiveBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerSpineSlot", ExplicitTime, SpineBlend.SlotBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerPelvis", ExplicitTime, PelvisBlend.BlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerPelvisSlot", ExplicitTime, PelvisBlend.SlotBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerLegs", ExplicitTime, LegsBlend.BlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerLegsSlot", ExplicitTime, LegsBlend.BlendAmount);
}
void FGMS_PerBodyPoseBlendSetting::ApplyToLayeringState(FGMS_AnimState_Layering& InLayeringState) const
{
InLayeringState.HeadBlendAmount = HeadBlend.BlendAmount;
InLayeringState.HeadAdditiveBlendAmount = HeadBlend.AdditiveBlendAmount;
InLayeringState.HeadSlotBlendAmount = HeadBlend.SlotBlendAmount;
InLayeringState.ArmLeftBlendAmount = ArmLeftBlend.BlendAmount;
InLayeringState.ArmLeftAdditiveBlendAmount = ArmLeftBlend.AdditiveBlendAmount;
InLayeringState.ArmLeftSlotBlendAmount = ArmLeftBlend.SlotBlendAmount;
InLayeringState.ArmLeftLocalSpaceBlendAmount = ArmLeftBlend.bMeshSpace ? 0.f : 1.f;
InLayeringState.ArmLeftMeshSpaceBlendAmount = ArmLeftBlend.bMeshSpace ? 1.f : 0.f;
InLayeringState.ArmRightBlendAmount = ArmRightBlend.BlendAmount;
InLayeringState.ArmRightAdditiveBlendAmount = ArmRightBlend.AdditiveBlendAmount;
InLayeringState.ArmRightSlotBlendAmount = ArmRightBlend.SlotBlendAmount;
InLayeringState.ArmRightLocalSpaceBlendAmount = ArmRightBlend.bMeshSpace ? 0.f : 1.f;
InLayeringState.ArmRightMeshSpaceBlendAmount = ArmRightBlend.bMeshSpace ? 1.f : 0.f;
InLayeringState.HandLeftBlendAmount = HandLeftBlend.BlendAmount;
InLayeringState.HandRightBlendAmount = HandRightBlend.BlendAmount;
InLayeringState.SpineBlendAmount = SpineBlend.BlendAmount;
InLayeringState.SpineAdditiveBlendAmount = SpineBlend.AdditiveBlendAmount;
InLayeringState.SpineSlotBlendAmount = SpineBlend.SlotBlendAmount;
InLayeringState.PelvisBlendAmount = PelvisBlend.BlendAmount;
InLayeringState.PelvisSlotBlendAmount = PelvisBlend.SlotBlendAmount;
InLayeringState.LegsBlendAmount = LegsBlend.BlendAmount;
InLayeringState.LegsSlotBlendAmount = LegsBlend.SlotBlendAmount;
}
void FGMS_PerBodyPoseBlendSetting::ApplyFromLayeringState(const FGMS_AnimState_Layering& InLayeringState)
{
HeadBlend.BlendAmount = InLayeringState.HeadBlendAmount;
HeadBlend.AdditiveBlendAmount = InLayeringState.HeadAdditiveBlendAmount;
HeadBlend.SlotBlendAmount = InLayeringState.HeadSlotBlendAmount;
ArmLeftBlend.BlendAmount = InLayeringState.ArmLeftBlendAmount;
ArmLeftBlend.AdditiveBlendAmount = InLayeringState.ArmLeftAdditiveBlendAmount;
ArmLeftBlend.SlotBlendAmount = InLayeringState.ArmLeftSlotBlendAmount;
ArmLeftBlend.bMeshSpace = InLayeringState.ArmLeftMeshSpaceBlendAmount > 0.f;
ArmRightBlend.BlendAmount = InLayeringState.ArmRightBlendAmount;
ArmRightBlend.AdditiveBlendAmount = InLayeringState.ArmRightAdditiveBlendAmount;
ArmRightBlend.SlotBlendAmount = InLayeringState.ArmRightSlotBlendAmount;
ArmRightBlend.bMeshSpace = InLayeringState.ArmRightMeshSpaceBlendAmount > 0.f;
HandLeftBlend.BlendAmount = InLayeringState.HandLeftBlendAmount;
HandRightBlend.BlendAmount = InLayeringState.HandRightBlendAmount;
SpineBlend.BlendAmount = InLayeringState.SpineBlendAmount;
SpineBlend.AdditiveBlendAmount = InLayeringState.SpineAdditiveBlendAmount;
SpineBlend.SlotBlendAmount = InLayeringState.SpineSlotBlendAmount;
PelvisBlend.BlendAmount = InLayeringState.PelvisBlendAmount;
PelvisBlend.SlotBlendAmount = InLayeringState.PelvisSlotBlendAmount;
LegsBlend.BlendAmount = InLayeringState.LegsBlendAmount;
LegsBlend.SlotBlendAmount = InLayeringState.LegsSlotBlendAmount;
}
bool UGMS_AnimLayerSetting_Overlay_PoseStack::IsValidForOverlayMode(const FGameplayTag& NewOverlayMode) const
{
return AcceleratedOverlayModes.Contains(NewOverlayMode) && AcceleratedOverlayModes[NewOverlayMode].BasePose != nullptr;
}
void UGMS_AnimLayer_Overlay_PoseStack::ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting)
{
check(IsValid(Setting));
if (CurrentSetting != Setting || CurrentOverlayMode != GetParent()->OverlayMode)
{
ResetSetting();
}
if (const UGMS_AnimLayerSetting_Overlay_PoseStack* DS = Cast<UGMS_AnimLayerSetting_Overlay_PoseStack>(Setting))
{
if (CurrentSetting == DS && CurrentOverlayMode == GetParent()->OverlayMode)
{
return;
}
if (!DS->AcceleratedOverlayModes.Contains(GetParent()->OverlayMode))
{
return;
}
const FGMS_OverlayModeSetting_PoseStack& ModeSetting = DS->AcceleratedOverlayModes[GetParent()->OverlayMode];
if (ModeSetting.BasePose == nullptr)
{
bHasValidSetting = false;
UE_LOG(LogGMS, Warning, TEXT("BasePose is null for overlay mode %s"), *GetParent()->OverlayMode.ToString());
return;
}
CurrentSetting = DS;
CurrentOverlayMode = GetParent()->OverlayMode;
bHasValidSetting = true;
BasePose = ModeSetting.BasePose;
}
}
void UGMS_AnimLayer_Overlay_PoseStack::ResetSetting_Implementation()
{
bHasValidSetting = false;
CurrentOverlayMode = FGameplayTag::EmptyTag;
CurrentSetting = nullptr;
BasePose = nullptr;
LayeringState.ZeroOut();
}
bool UGMS_AnimLayer_Overlay_PoseStack::SelectPose()
{
const FGMS_OverlayModeSetting_PoseStack& OS = GetOverlayModeSetting();
BasePose = OS.BasePose;
for (int32 i = 0; i < OS.Poses.Num(); i++)
{
const FGMS_PoseStackEntry& Entry = OS.Poses[i];
bool bMatchesOwnedTags = Entry.TagQuery.IsEmpty() || Entry.TagQuery.Matches(GetParent()->OwnedTags);
bool bMatchesRelevanceTags = Entry.RelevanceQuery.IsEmpty() || Entry.RelevanceQuery.Matches(GetParent()->NodeRelevanceTags);
if (bMatchesOwnedTags && bMatchesRelevanceTags)
{
Pose = Entry.Pose;
ExplicitTime = Entry.ExplicitTime;
AimingSweepPose = Entry.AimingSweepPose;
bValidAimingPose = AimingSweepPose != nullptr;
Entry.PoseBlend.ApplyToLayeringState(LayeringState);
return true;
}
}
return false;
}
void UGMS_AnimLayer_Overlay_PoseStack::NativeThreadSafeUpdateAnimation(float DeltaSeconds)
{
Super::NativeThreadSafeUpdateAnimation(DeltaSeconds);
if (!bHasValidSetting)
{
return;
}
if (!SelectPose())
{
Pose = nullptr;
ExplicitTime = 0.0f;
bValidPose = false;
AimingSweepPose = nullptr;
bValidAimingPose = false;
LayeringState.ZeroOut();
}
bValidPose = Pose ? true : false;
UpdateLayeringSmoothState(DeltaSeconds);
}
const FGMS_OverlayModeSetting_PoseStack& UGMS_AnimLayer_Overlay_PoseStack::GetOverlayModeSetting() const
{
return CurrentSetting->AcceleratedOverlayModes[CurrentOverlayMode];
}
void UGMS_AnimLayer_Overlay_PoseStack::UpdateLayeringSmoothState(float DeltaSeconds)
{
if (LayeringSmoothSpeed <= 0.0f)
{
LayeringSmoothState.HeadBlendAmount = LayeringState.HeadBlendAmount;
LayeringSmoothState.ArmLeftBlendAmount = LayeringState.ArmLeftBlendAmount;
LayeringSmoothState.ArmRightBlendAmount = LayeringState.ArmRightBlendAmount;
LayeringSmoothState.SpineBlendAmount = LayeringState.SpineBlendAmount;
LayeringSmoothState.PelvisBlendAmount = LayeringState.PelvisBlendAmount;
LayeringSmoothState.LegsBlendAmount = LayeringState.LegsBlendAmount;
return;
}
LayeringSmoothState.HeadBlendAmount = LayeringState.HeadBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.HeadBlendAmount, 0.0f, DeltaSeconds, LayeringSmoothSpeed)
: LayeringState.HeadBlendAmount;
LayeringSmoothState.ArmLeftBlendAmount = LayeringState.ArmLeftBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.ArmLeftBlendAmount, 0.0f, DeltaSeconds, LayeringSmoothSpeed)
: LayeringState.ArmLeftBlendAmount;
LayeringSmoothState.ArmRightBlendAmount = LayeringState.ArmRightBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.ArmRightBlendAmount, 0.0f, DeltaSeconds, LayeringSmoothSpeed)
: LayeringState.ArmRightBlendAmount;
LayeringSmoothState.SpineBlendAmount = LayeringState.SpineBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.SpineBlendAmount, 0.0f, DeltaSeconds, LayeringSmoothSpeed)
: LayeringState.SpineBlendAmount;
LayeringSmoothState.PelvisBlendAmount = LayeringState.PelvisBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.PelvisBlendAmount, 0.0f, DeltaSeconds, LayeringSmoothSpeed)
: LayeringState.PelvisBlendAmount;
LayeringSmoothState.LegsBlendAmount = LayeringState.LegsBlendAmount <= 0.0f
? FMath::FInterpConstantTo(LayeringSmoothState.LegsBlendAmount, 0.0f, DeltaSeconds, LayeringSmoothSpeed)
: LayeringState.LegsBlendAmount;
// LayeringSmoothState.HeadBlendAmount = FMath::FInterpConstantTo(LayeringSmoothState.HeadBlendAmount, LayeringState.HeadBlendAmount, DeltaSeconds, LayeringSmoothSpeed);
//
// LayeringSmoothState.ArmLeftBlendAmount = FMath::FInterpConstantTo(LayeringSmoothState.ArmLeftBlendAmount, LayeringState.ArmLeftBlendAmount, DeltaSeconds, LayeringSmoothSpeed);
//
// LayeringSmoothState.ArmRightBlendAmount = FMath::FInterpConstantTo(LayeringSmoothState.ArmRightBlendAmount, LayeringState.ArmRightBlendAmount, DeltaSeconds, LayeringSmoothSpeed);
//
// LayeringSmoothState.SpineBlendAmount = FMath::FInterpConstantTo(LayeringSmoothState.SpineBlendAmount, LayeringState.SpineBlendAmount, DeltaSeconds, LayeringSmoothSpeed);
//
//
// LayeringSmoothState.PelvisBlendAmount = FMath::FInterpConstantTo(LayeringSmoothState.PelvisBlendAmount, LayeringState.PelvisBlendAmount, DeltaSeconds, LayeringSmoothSpeed);
//
// LayeringSmoothState.LegsBlendAmount = FMath::FInterpConstantTo(LayeringSmoothState.LegsBlendAmount, LayeringState.LegsBlendAmount, DeltaSeconds, LayeringSmoothSpeed);
}

View File

@@ -0,0 +1,207 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimLayer_Overlay_SequenceStack.h"
#include "Locomotions/GMS_MainAnimInstance.h"
#include "Utility/GMS_Log.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimLayer_Overlay_SequenceStack)
#if WITH_EDITOR
#include "Locomotions/GMS_AnimLayer_Overlay_ParallelSequenceStack.h"
#include "UObject/ObjectSaveContext.h"
void UGMS_AnimLayerSetting_Overlay_SequenceStack::ConvertToSequenceStack(const UGMS_AnimLayerSetting_Overlay_ParallelSequenceStack* Src)
{
if (!IsValid(Src))
{
return;
}
for (auto& Pair : Src->AcceleratedOverlayModes)
{
auto& OldMode = Pair.Value;
FGMS_OverlayModeSetting_SequenceStack NewMode;
NewMode.Tag = OldMode.Tag;
for (int32 i = OldMode.Stacks.Num() - 1; i >= 0; i--)
{
const FGMS_ParallelSequenceStack& OldStack = OldMode.Stacks[i];
for (int32 j = 0; j < OldStack.Overlays.Num(); j++)
{
const FGMS_ParallelSequenceStackEntry& OldEntry = OldStack.Overlays[j];
UAnimSequence* OldSequence = Cast<UAnimSequence>(OldEntry.Sequence);
if (OldSequence == nullptr)
{
continue;
}
FGMS_SequenceStackEntry NewEntry;
NewEntry.RelevanceQuery = FGameplayTagQuery::BuildQuery(FGameplayTagQueryExpression().AnyTagsMatch().AddTags(OldStack.TargetAnimNodes));
NewEntry.TagQuery = OldEntry.TagQuery;
NewEntry.Sequence = OldSequence;
NewEntry.PlayMode = OldEntry.PlayMode;
NewEntry.BlendWeight = OldEntry.BlendWeight;
NewEntry.MeshSpaceBlend = OldEntry.MeshSpaceBlend;
NewEntry.AnimationTime = OldEntry.PlayMode == EGMS_OverlayPlayMode::SequenceEvaluator ? OldEntry.ExplicitTime : OldEntry.StartPosition;
NewEntry.BlendMode = OldEntry.BlendMode;
NewEntry.BlendMaskName = OldEntry.BlendMaskName;
NewEntry.BranchFilters = OldEntry.BranchFilters;
NewEntry.BlendTime = 0.2f;
NewMode.Sequences.Add(NewEntry);
}
}
OverlayModes.Add(NewMode);
}
}
void UGMS_AnimLayerSetting_Overlay_SequenceStack::ConvertToSequenceStackFromDefinition(UGMS_MovementDefinition* InDefinition)
{
if (IsValid(InDefinition))
{
InDefinition->Modify();
for (TPair<FGameplayTag, FGMS_MovementSetSetting>& MovementSet : InDefinition->MovementSets)
{
FGMS_MovementSetSetting& MSS = MovementSet.Value;
if (MSS.bUseInstancedOverlaySetting && MSS.AnimLayerSetting_Overlay)
{
if (auto Src = Cast<UGMS_AnimLayerSetting_Overlay_ParallelSequenceStack>(MSS.AnimLayerSetting_Overlay))
{
auto NewSequenceStack = NewObject<UGMS_AnimLayerSetting_Overlay_SequenceStack>(InDefinition, StaticClass());
NewSequenceStack->ConvertToSequenceStack(Src);;
MSS.AnimLayerSetting_Overlay = NewSequenceStack;
}
}
else if (!MSS.bUseInstancedOverlaySetting && MSS.DA_AnimLayerSetting_Overlay)
{
if (auto Src = Cast<UGMS_AnimLayerSetting_Overlay_ParallelSequenceStack>(MSS.DA_AnimLayerSetting_Overlay))
{
auto NewSequenceStack = NewObject<UGMS_AnimLayerSetting_Overlay_SequenceStack>(InDefinition, StaticClass());
NewSequenceStack->ConvertToSequenceStack(Src);;
MSS.DA_AnimLayerSetting_Overlay = nullptr;
MSS.bUseInstancedOverlaySetting = true;
MSS.AnimLayerSetting_Overlay = NewSequenceStack;
}
}
}
}
}
void UGMS_AnimLayerSetting_Overlay_SequenceStack::PreSave(FObjectPreSaveContext SaveContext)
{
AcceleratedOverlayModes.Empty();
for (FGMS_OverlayModeSetting_SequenceStack& Mode : OverlayModes)
{
for (FGMS_SequenceStackEntry& Entry : Mode.Sequences)
{
Entry.EditorFriendlyName = FString::Format(TEXT("{0} "), {
GetNameSafe(Entry.Sequence)
});
}
AcceleratedOverlayModes.Emplace(Mode.Tag, Mode);
}
Super::PreSave(SaveContext);
}
#endif
void UGMS_AnimLayer_Overlay_SequenceStack::ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting)
{
check(IsValid(Setting));
if (CurrentSetting != Setting || CurrentOverlayMode != GetParent()->OverlayMode)
{
ResetSetting();
}
if (const UGMS_AnimLayerSetting_Overlay_SequenceStack* DS = Cast<UGMS_AnimLayerSetting_Overlay_SequenceStack>(Setting))
{
if (CurrentSetting == DS && CurrentOverlayMode == GetParent()->OverlayMode)
{
return;
}
if (!DS->AcceleratedOverlayModes.Contains(GetParent()->OverlayMode))
{
return;
}
const FGMS_OverlayModeSetting_SequenceStack& ModeSetting = DS->AcceleratedOverlayModes[GetParent()->OverlayMode];
CurrentSetting = DS;
CurrentOverlayMode = GetParent()->OverlayMode;
bHasValidSetting = true;
}
}
void UGMS_AnimLayer_Overlay_SequenceStack::ResetSetting_Implementation()
{
Definition = FGMS_SequenceStackEntry();
BlendWeight = 0.0f;
BlendProfile = nullptr;
bHasValidSetting = false;
CurrentOverlayMode = FGameplayTag::EmptyTag;
CurrentSetting = nullptr;
}
bool UGMS_AnimLayer_Overlay_SequenceStack::SelectSequence()
{
const FGMS_OverlayModeSetting_SequenceStack& OS = GetOverlayModeSetting();
for (int32 i = 0; i < OS.Sequences.Num(); i++)
{
const FGMS_SequenceStackEntry& Entry = OS.Sequences[i];
bool bMatchesOwnedTags = Entry.TagQuery.IsEmpty() || Entry.TagQuery.Matches(GetParent()->OwnedTags);
bool bMatchesRelevanceTags = Entry.RelevanceQuery.IsEmpty() || Entry.RelevanceQuery.Matches(GetParent()->NodeRelevanceTags);
if (bMatchesOwnedTags && bMatchesRelevanceTags)
{
Definition = Entry;
BlendOutSpeed = Definition.BlendOutSpeed;
BlendProfile = GetParent()->GetNamedBlendProfile(Definition.BlendProfile);
bHasValidDefinition = true;
return true;
}
}
return false;
}
void UGMS_AnimLayer_Overlay_SequenceStack::NativeThreadSafeUpdateAnimation(float DeltaSeconds)
{
Super::NativeThreadSafeUpdateAnimation(DeltaSeconds);
if (!bHasValidSetting)
{
bHasValidDefinition = false;
return;
}
if (!SelectSequence())
{
bHasValidDefinition = false;
}
if (bHasValidDefinition)
{
if (Definition.BlendInSpeed > 0)
{
BlendWeight = FMath::FInterpTo(BlendWeight, Definition.BlendWeight, DeltaSeconds, Definition.BlendInSpeed);
}
else
{
BlendWeight = Definition.BlendWeight;
}
}
else
{
if (BlendOutSpeed > 0)
{
BlendWeight = FMath::FInterpTo(BlendWeight, 0.0f, DeltaSeconds, BlendOutSpeed);
}
else
{
BlendWeight = 0.0f;
}
}
}
const FGMS_OverlayModeSetting_SequenceStack& UGMS_AnimLayer_Overlay_SequenceStack::GetOverlayModeSetting() const
{
return CurrentSetting->AcceleratedOverlayModes[CurrentOverlayMode];
}

View File

@@ -0,0 +1,6 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimLayer_SkeletalControls.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimLayer_SkeletalControls)

View File

@@ -0,0 +1,12 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimLayer_States.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimLayer_States)
void FGMS_AnimData::Validate()
{
}

View File

@@ -0,0 +1,6 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimLayer_View.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimLayer_View)

View File

@@ -0,0 +1,29 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimLayer_View_Default.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimLayer_View_Default)
void UGMS_AnimLayer_View_Default::ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting)
{
if (const UGMS_AnimLayerSetting_View_Default* DS = Cast<UGMS_AnimLayerSetting_View_Default>(Setting))
{
ResetSetting();
BlendSpace = DS->BlendSpace;
YawAngleOffset = DS->YawAngleOffset;
YawAngleLimit = DS->YawAngleLimit;
SmoothInterpSpeed = DS->SmoothInterpSpeed;
bValidBlendSpace = BlendSpace != nullptr;
}
}
void UGMS_AnimLayer_View_Default::ResetSetting_Implementation()
{
bValidBlendSpace = false;
YawAngleOffset = 0.0f;
YawAngleLimit = FVector2D(-90.0f, 90.0f);
SmoothInterpSpeed = 0.0f;
BlendSpace = nullptr;
}

View File

@@ -0,0 +1,73 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_AnimState.h"
#include "AnimationWarpingLibrary.h"
#include "Animation/AnimSequenceBase.h"
#include "Animation/AnimSequence.h"
#include "Utility/GMS_Constants.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimState)
void FGMS_AnimState_Layering::ApplyValueFromSequence(const UAnimSequence* InSequence, float ExplicitTime)
{
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerHead", ExplicitTime, HeadBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerHeadAdditive", ExplicitTime, HeadAdditiveBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerHeadSlot", ExplicitTime, HeadSlotBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmLeft", ExplicitTime, ArmLeftBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmLeftAdditive", ExplicitTime, ArmLeftAdditiveBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmLeftSlot", ExplicitTime, ArmLeftSlotBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmLeftLocalSpace", ExplicitTime, ArmLeftLocalSpaceBlendAmount);
ArmLeftMeshSpaceBlendAmount = 1 - ArmLeftLocalSpaceBlendAmount;
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmRight", ExplicitTime, ArmRightBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmRightAdditive", ExplicitTime, ArmRightAdditiveBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmRightSlot", ExplicitTime, ArmRightSlotBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerArmRightLocalSpace", ExplicitTime, ArmRightLocalSpaceBlendAmount);
ArmRightMeshSpaceBlendAmount = 1 - ArmRightLocalSpaceBlendAmount;
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerHandLeft", ExplicitTime, HandLeftBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerHandRight", ExplicitTime, HandRightBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerSpine", ExplicitTime, SpineBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerSpineAdditive", ExplicitTime, SpineAdditiveBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerSpineSlot", ExplicitTime, SpineSlotBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerPelvis", ExplicitTime, PelvisBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerPelvisSlot", ExplicitTime, PelvisSlotBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerLegs", ExplicitTime, LegsBlendAmount);
UAnimationWarpingLibrary::GetCurveValueFromAnimation(InSequence, "LayerLegsSlot", ExplicitTime, LegsSlotBlendAmount);
}
void FGMS_AnimState_Layering::ZeroOut()
{
HeadBlendAmount = 0.0f;
HeadAdditiveBlendAmount = 0.0f;
HeadSlotBlendAmount = 0.0f;
ArmLeftBlendAmount = 0.0f;
ArmLeftAdditiveBlendAmount = 0.0f;
ArmLeftSlotBlendAmount = 0.0f;
ArmLeftLocalSpaceBlendAmount = 0.0f;
ArmLeftMeshSpaceBlendAmount = 0.0f;
ArmRightBlendAmount = 0.0f;
ArmRightAdditiveBlendAmount = 0.0f;
ArmRightSlotBlendAmount = 0.0f;
ArmRightLocalSpaceBlendAmount = 0.0f;
ArmRightMeshSpaceBlendAmount = 0.0f;
HandLeftBlendAmount = 0.0f;
HandRightBlendAmount = 0.0f;
SpineBlendAmount = 0.0f;
SpineAdditiveBlendAmount = 0.0f;
SpineSlotBlendAmount = 0.0f;
PelvisBlendAmount = 0.0f;
PelvisSlotBlendAmount = 0.0f;
LegsBlendAmount = 0.0f;
LegsSlotBlendAmount = 0.0f;
}

View File

@@ -0,0 +1,40 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Locomotions/GMS_LocomotionStructLibrary.h"
#include "Animation/AimOffsetBlendSpace.h"
#include "Animation/AnimSequenceBase.h"
#include "Animation/AnimSequence.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_LocomotionStructLibrary)
bool FGMS_Animations_4Direction::ValidAnimations() const
{
return Forward && Backward && Left && Right;
}
bool FGMS_Animations_4Direction::HasRootMotion() const
{
if (ValidAnimations())
{
return Forward->HasRootMotion() && Backward->HasRootMotion() && Left->HasRootMotion() && Right->HasRootMotion();
}
return false;
}
bool FGMS_Animations_8Direction::ValidAnimations() const
{
return Forward && ForwardLeft && ForwardRight && Backward && BackwardLeft && BackwardRight && Left && Right;
}
bool FGMS_Animations_8Direction::HasRootMotion() const
{
if (ValidAnimations())
{
return Forward->HasRootMotion() && ForwardLeft->HasRootMotion() && ForwardRight->HasRootMotion() && Backward->HasRootMotion() && BackwardLeft->HasRootMotion() && BackwardRight->
HasRootMotion() && Left->
HasRootMotion() && Right->HasRootMotion();
}
return false;
}

View File

@@ -0,0 +1,958 @@
// 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);
}

View File

@@ -0,0 +1,478 @@
// // Copyright 2025 https://yuewu.dev/en All Rights Reserved.
//
//
// #include "Movement/GMS_CharacterMovementComponent.h"
//
// #include "GMS_CharacterMovementSystemComponent.h"
// #include "GMS_MovementSystemComponent.h"
// #include "GameFramework/Character.h"
// #include "Locomotions/GMS_MainAnimInstance.h"
// #include "Settings/GMS_SettingObjectLibrary.h"
// #include "Utility/GMS_Constants.h"
// #include "Utility/GMS_Log.h"
// #include "Utility/GMS_Math.h"
//
// #include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_CharacterMovementComponent)
//
// UGMS_CharacterMovementComponent::UGMS_CharacterMovementComponent(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
// {
// }
//
// void UGMS_CharacterMovementComponent::InitializeComponent()
// {
// Super::InitializeComponent();
// if (CharacterOwner)
// {
// UGMS_CharacterMovementSystemComponent* NewMovementSystem = CharacterOwner->FindComponentByClass<UGMS_CharacterMovementSystemComponent>();
// if (NewMovementSystem == nullptr)
// {
// GMS_CLOG(Warning, "Requires GMS Character Movement System Component to function!")
// return;
// }
// MovementSystem = NewMovementSystem;
// }
// }
//
// void UGMS_CharacterMovementComponent::SetUpdatedComponent(USceneComponent* NewUpdatedComponent)
// {
// Super::SetUpdatedComponent(NewUpdatedComponent);
// if (CharacterOwner)
// {
// UGMS_CharacterMovementSystemComponent* NewMovementSystem = CharacterOwner->FindComponentByClass<UGMS_CharacterMovementSystemComponent>();
// if (NewMovementSystem == nullptr)
// {
// GMS_CLOG(Warning, "Requires GMS Character Movement System Component to function!")
// return;
// }
// MovementSystem = NewMovementSystem;
// }
// }
//
// bool UGMS_CharacterMovementComponent::HasValidData() const
// {
// return Super::HasValidData() && IsValid(MovementSystem);
// }
//
// void UGMS_CharacterMovementComponent::TickCharacterPose(float DeltaTime)
// {
// Super::TickCharacterPose(DeltaTime);
// }
//
// void UGMS_CharacterMovementComponent::PhysicsRotation(float DeltaTime)
// {
// if (bUseNativeRotation)
// {
// Super::PhysicsRotation(DeltaTime);
// }
// else
// {
// GMS_PhysicsRotation(DeltaTime);
// }
// }
//
// void UGMS_CharacterMovementComponent::GMS_TurnToDesiredRotation(const FRotator& CurrentRotation, FRotator DesiredRotation, FRotator DeltaRot)
// {
// const bool bWantsToBeVertical = ShouldRemainVertical();
//
// if (bWantsToBeVertical)
// {
// if (HasCustomGravity())
// {
// FRotator GravityRelativeDesiredRotation = (GetWorldToGravityTransform() * DesiredRotation.Quaternion()).Rotator();
// GravityRelativeDesiredRotation.Pitch = 0.f;
// GravityRelativeDesiredRotation.Yaw = FRotator::NormalizeAxis(GravityRelativeDesiredRotation.Yaw);
// GravityRelativeDesiredRotation.Roll = 0.f;
// DesiredRotation = (GetWorldToGravityTransform() * GravityRelativeDesiredRotation.Quaternion()).Rotator();
// }
// else
// {
// DesiredRotation.Pitch = 0.f;
// DesiredRotation.Yaw = FRotator::NormalizeAxis(DesiredRotation.Yaw);
// DesiredRotation.Roll = 0.f;
// }
// }
// else
// {
// DesiredRotation.Normalize();
// }
//
// // Accumulate a desired new rotation.
// constexpr float AngleTolerance = 1e-3f;
//
// if (!CurrentRotation.Equals(DesiredRotation, AngleTolerance))
// {
// // If we'd be prevented from becoming vertical, override the non-yaw rotation rates to allow the character to snap upright
//
// static bool PreventNonVerticalOrientationBlock = true;
// if (PreventNonVerticalOrientationBlock && bWantsToBeVertical)
// {
// if (FMath::IsNearlyZero(DeltaRot.Pitch))
// {
// DeltaRot.Pitch = 360.0;
// }
// if (FMath::IsNearlyZero(DeltaRot.Roll))
// {
// DeltaRot.Roll = 360.0;
// }
// }
//
// if (HasCustomGravity())
// {
// FRotator GravityRelativeCurrentRotation = (GetWorldToGravityTransform() * CurrentRotation.Quaternion()).Rotator();
// FRotator GravityRelativeDesiredRotation = (GetWorldToGravityTransform() * DesiredRotation.Quaternion()).Rotator();
//
// // PITCH
// if (!FMath::IsNearlyEqual(GravityRelativeCurrentRotation.Pitch, GravityRelativeDesiredRotation.Pitch, AngleTolerance))
// {
// // GravityRelativeDesiredRotation.Pitch = UGMS_Math::ExponentialDecayAngle(GravityRelativeCurrentRotation.Pitch,
// // GravityRelativeDesiredRotation.Pitch, DeltaTime, DeltaRot.Pitch);
// GravityRelativeDesiredRotation.Pitch = FMath::FixedTurn(GravityRelativeCurrentRotation.Pitch, GravityRelativeDesiredRotation.Pitch, DeltaRot.Pitch);
// }
//
// // YAW
// if (!FMath::IsNearlyEqual(GravityRelativeCurrentRotation.Yaw, GravityRelativeDesiredRotation.Yaw, AngleTolerance))
// {
// // GravityRelativeDesiredRotation.Yaw = UGMS_Math::ExponentialDecayAngle(GravityRelativeCurrentRotation.Yaw,
// // GravityRelativeDesiredRotation.Yaw, DeltaTime, DeltaRot.Yaw);
// GravityRelativeDesiredRotation.Yaw = FMath::FixedTurn(GravityRelativeCurrentRotation.Yaw, GravityRelativeDesiredRotation.Yaw, DeltaRot.Yaw);
// }
//
// // ROLL
// if (!FMath::IsNearlyEqual(GravityRelativeCurrentRotation.Roll, GravityRelativeDesiredRotation.Roll, AngleTolerance))
// {
// // GravityRelativeDesiredRotation.Roll = UGMS_Math::ExponentialDecayAngle(GravityRelativeCurrentRotation.Roll,
// // GravityRelativeDesiredRotation.Roll, DeltaTime, DeltaRot.Roll);
// GravityRelativeDesiredRotation.Roll = FMath::FixedTurn(GravityRelativeCurrentRotation.Roll, GravityRelativeDesiredRotation.Roll, DeltaRot.Roll);
// }
//
// DesiredRotation = (GetWorldToGravityTransform() * GravityRelativeDesiredRotation.Quaternion()).Rotator();
// }
// else
// {
// // PITCH
// if (!FMath::IsNearlyEqual(CurrentRotation.Pitch, DesiredRotation.Pitch, AngleTolerance))
// {
// // DesiredRotation.Pitch = UGMS_Math::ExponentialDecayAngle(CurrentRotation.Pitch, DesiredRotation.Pitch, DeltaTime, DeltaRot.Pitch);
// DesiredRotation.Pitch = FMath::FixedTurn(CurrentRotation.Pitch, DesiredRotation.Pitch, DeltaRot.Pitch);
// }
//
// // YAW
// if (!FMath::IsNearlyEqual(CurrentRotation.Yaw, DesiredRotation.Yaw, AngleTolerance))
// {
// // DesiredRotation.Yaw = UGMS_Math::ExponentialDecayAngle(CurrentRotation.Yaw, DesiredRotation.Yaw, DeltaTime, DeltaRot.Yaw);
// DesiredRotation.Yaw = FMath::FixedTurn(CurrentRotation.Yaw, DesiredRotation.Yaw, DeltaRot.Yaw);
// }
//
// // ROLL
// if (!FMath::IsNearlyEqual(CurrentRotation.Roll, DesiredRotation.Roll, AngleTolerance))
// {
// // DesiredRotation.Roll = UGMS_Math::ExponentialDecayAngle(CurrentRotation.Roll, DesiredRotation.Roll, DeltaTime, DeltaRot.Yaw);
// DesiredRotation.Roll = FMath::FixedTurn(CurrentRotation.Roll, DesiredRotation.Roll, DeltaRot.Roll);
// }
// }
//
// // Set the new rotation.
// DesiredRotation.DiagnosticCheckNaN(TEXT("CharacterMovementComponent::PhysicsRotation(): DesiredRotation"));
// MoveUpdatedComponent(FVector::ZeroVector, DesiredRotation, /*bSweep*/ false);
// }
// }
//
// void UGMS_CharacterMovementComponent::GMS_TurnToDesiredRotationWithRotationRate(const FRotator& CurrentRotation, FRotator DesiredRotation, FRotator DeltaRot)
// {
// const bool bWantsToBeVertical = ShouldRemainVertical();
//
// if (bWantsToBeVertical)
// {
// if (HasCustomGravity())
// {
// FRotator GravityRelativeDesiredRotation = (GetWorldToGravityTransform() * DesiredRotation.Quaternion()).Rotator();
// GravityRelativeDesiredRotation.Pitch = 0.f;
// GravityRelativeDesiredRotation.Yaw = FRotator::NormalizeAxis(GravityRelativeDesiredRotation.Yaw);
// GravityRelativeDesiredRotation.Roll = 0.f;
// DesiredRotation = (GetWorldToGravityTransform() * GravityRelativeDesiredRotation.Quaternion()).Rotator();
// }
// else
// {
// DesiredRotation.Pitch = 0.f;
// DesiredRotation.Yaw = FRotator::NormalizeAxis(DesiredRotation.Yaw);
// DesiredRotation.Roll = 0.f;
// }
// }
// else
// {
// DesiredRotation.Normalize();
// }
//
// // Accumulate a desired new rotation.
// constexpr float AngleTolerance = 1e-3f;
//
// if (!CurrentRotation.Equals(DesiredRotation, AngleTolerance))
// {
// // If we'd be prevented from becoming vertical, override the non-yaw rotation rates to allow the character to snap upright
//
// static bool PreventNonVerticalOrientationBlock = true;
// if (PreventNonVerticalOrientationBlock && bWantsToBeVertical)
// {
// if (FMath::IsNearlyZero(DeltaRot.Pitch))
// {
// DeltaRot.Pitch = 360.0;
// }
// if (FMath::IsNearlyZero(DeltaRot.Roll))
// {
// DeltaRot.Roll = 360.0;
// }
// }
//
// if (HasCustomGravity())
// {
// FRotator GravityRelativeCurrentRotation = (GetWorldToGravityTransform() * CurrentRotation.Quaternion()).Rotator();
// FRotator GravityRelativeDesiredRotation = (GetWorldToGravityTransform() * DesiredRotation.Quaternion()).Rotator();
//
// // PITCH
// if (!FMath::IsNearlyEqual(GravityRelativeCurrentRotation.Pitch, GravityRelativeDesiredRotation.Pitch, AngleTolerance))
// {
// GravityRelativeDesiredRotation.Pitch = FMath::FixedTurn(GravityRelativeCurrentRotation.Pitch, GravityRelativeDesiredRotation.Pitch, DeltaRot.Pitch);
// }
//
// // YAW
// if (!FMath::IsNearlyEqual(GravityRelativeCurrentRotation.Yaw, GravityRelativeDesiredRotation.Yaw, AngleTolerance))
// {
// GravityRelativeDesiredRotation.Yaw = FMath::FixedTurn(GravityRelativeCurrentRotation.Yaw, GravityRelativeDesiredRotation.Yaw, DeltaRot.Yaw);
// }
//
// // ROLL
// if (!FMath::IsNearlyEqual(GravityRelativeCurrentRotation.Roll, GravityRelativeDesiredRotation.Roll, AngleTolerance))
// {
// GravityRelativeDesiredRotation.Roll = FMath::FixedTurn(GravityRelativeCurrentRotation.Roll, GravityRelativeDesiredRotation.Roll, DeltaRot.Roll);
// }
//
// DesiredRotation = (GetWorldToGravityTransform() * GravityRelativeDesiredRotation.Quaternion()).Rotator();
// }
// else
// {
// // PITCH
// if (!FMath::IsNearlyEqual(CurrentRotation.Pitch, DesiredRotation.Pitch, AngleTolerance))
// {
// DesiredRotation.Pitch = FMath::FixedTurn(CurrentRotation.Pitch, DesiredRotation.Pitch, DeltaRot.Pitch);
// }
//
// // YAW
// if (!FMath::IsNearlyEqual(CurrentRotation.Yaw, DesiredRotation.Yaw, AngleTolerance))
// {
// DesiredRotation.Yaw = FMath::FixedTurn(CurrentRotation.Yaw, DesiredRotation.Yaw, DeltaRot.Yaw);
// }
//
// // ROLL
// if (!FMath::IsNearlyEqual(CurrentRotation.Roll, DesiredRotation.Roll, AngleTolerance))
// {
// DesiredRotation.Roll = FMath::FixedTurn(CurrentRotation.Roll, DesiredRotation.Roll, DeltaRot.Roll);
// }
// }
//
// // Set the new rotation.
// DesiredRotation.DiagnosticCheckNaN(TEXT("CharacterMovementComponent::PhysicsRotation(): DesiredRotation"));
// MoveUpdatedComponent(FVector::ZeroVector, DesiredRotation, /*bSweep*/ false);
// }
// }
//
// void UGMS_CharacterMovementComponent::GMS_PhysicsRotation_Implementation(float DeltaTime)
// {
// if (!(bOrientRotationToMovement || bUseControllerDesiredRotation))
// {
// return;
// }
//
// if (!HasValidData() || (!CharacterOwner->Controller && !bRunPhysicsWithNoController))
// {
// return;
// }
//
// FRotator CurrentRotation = UpdatedComponent->GetComponentRotation(); // Normalized
// CurrentRotation.DiagnosticCheckNaN(TEXT("CharacterMovementComponent::PhysicsRotation(): CurrentRotation"));
//
// FRotator DeltaRot = GetDeltaRotation(DeltaTime);
// DeltaRot.DiagnosticCheckNaN(TEXT("CharacterMovementComponent::PhysicsRotation(): GetDeltaRotation"));
//
// FRotator DesiredRotation = CurrentRotation;
// if (bOrientRotationToMovement)
// {
// DesiredRotation = GMS_ComputeOrientToDesiredMovementRotation(CurrentRotation, DeltaTime, DeltaRot);
// }
// else if (CharacterOwner->Controller && bUseControllerDesiredRotation)
// {
// DesiredRotation = CharacterOwner->Controller->GetDesiredRotation();
// // DesiredRotation = MovementSystem->GetViewState().Rotation;
// }
// else if (!CharacterOwner->Controller && bRunPhysicsWithNoController && bUseControllerDesiredRotation)
// {
// if (AController* ControllerOwner = Cast<AController>(CharacterOwner->GetOwner()))
// {
// DesiredRotation = ControllerOwner->GetDesiredRotation();
// }
// }
// else
// {
// return;
// }
//
// const bool bWantsToBeVertical = ShouldRemainVertical();
//
// if (bWantsToBeVertical)
// {
// if (HasCustomGravity())
// {
// FRotator GravityRelativeDesiredRotation = (GetGravityToWorldTransform() * DesiredRotation.Quaternion()).Rotator();
// GravityRelativeDesiredRotation.Pitch = 0.f;
// GravityRelativeDesiredRotation.Yaw = FRotator::NormalizeAxis(GravityRelativeDesiredRotation.Yaw);
// GravityRelativeDesiredRotation.Roll = 0.f;
// DesiredRotation = (GetWorldToGravityTransform() * GravityRelativeDesiredRotation.Quaternion()).Rotator();
// }
// else
// {
// DesiredRotation.Pitch = 0.f;
// DesiredRotation.Yaw = FRotator::NormalizeAxis(DesiredRotation.Yaw);
// DesiredRotation.Roll = 0.f;
// }
// }
// else
// {
// DesiredRotation.Normalize();
// }
//
// // Accumulate a desired new rotation.
// constexpr float AngleTolerance = 1e-3f;
//
// if (!CurrentRotation.Equals(DesiredRotation, AngleTolerance))
// {
// // If we'd be prevented from becoming vertical, override the non-yaw rotation rates to allow the character to snap upright
// if (true && bWantsToBeVertical)
// {
// if (FMath::IsNearlyZero(DeltaRot.Pitch))
// {
// DeltaRot.Pitch = 360.0;
// }
// if (FMath::IsNearlyZero(DeltaRot.Roll))
// {
// DeltaRot.Roll = 360.0;
// }
// }
//
// if (HasCustomGravity())
// {
// FRotator GravityRelativeCurrentRotation = (GetGravityToWorldTransform() * CurrentRotation.Quaternion()).Rotator();
// FRotator GravityRelativeDesiredRotation = (GetGravityToWorldTransform() * DesiredRotation.Quaternion()).Rotator();
//
// // PITCH
// if (!FMath::IsNearlyEqual(GravityRelativeCurrentRotation.Pitch, GravityRelativeDesiredRotation.Pitch, AngleTolerance))
// {
// GravityRelativeDesiredRotation.Pitch = FMath::FixedTurn(GravityRelativeCurrentRotation.Pitch, GravityRelativeDesiredRotation.Pitch, DeltaRot.Pitch);
// }
//
// // YAW
// if (!FMath::IsNearlyEqual(GravityRelativeCurrentRotation.Yaw, GravityRelativeDesiredRotation.Yaw, AngleTolerance))
// {
// GravityRelativeDesiredRotation.Yaw = FMath::FixedTurn(GravityRelativeCurrentRotation.Yaw, GravityRelativeDesiredRotation.Yaw, DeltaRot.Yaw);
// }
//
// // ROLL
// if (!FMath::IsNearlyEqual(GravityRelativeCurrentRotation.Roll, GravityRelativeDesiredRotation.Roll, AngleTolerance))
// {
// GravityRelativeDesiredRotation.Roll = FMath::FixedTurn(GravityRelativeCurrentRotation.Roll, GravityRelativeDesiredRotation.Roll, DeltaRot.Roll);
// }
//
// DesiredRotation = (GetWorldToGravityTransform() * GravityRelativeDesiredRotation.Quaternion()).Rotator();
// }
// else
// {
// // PITCH
// if (!FMath::IsNearlyEqual(CurrentRotation.Pitch, DesiredRotation.Pitch, AngleTolerance))
// {
// DesiredRotation.Pitch = FMath::FixedTurn(CurrentRotation.Pitch, DesiredRotation.Pitch, DeltaRot.Pitch);
// }
//
// // YAW
// if (!FMath::IsNearlyEqual(CurrentRotation.Yaw, DesiredRotation.Yaw, AngleTolerance))
// {
// DesiredRotation.Yaw = FMath::FixedTurn(CurrentRotation.Yaw, DesiredRotation.Yaw, DeltaRot.Yaw);
// }
//
// // ROLL
// if (!FMath::IsNearlyEqual(CurrentRotation.Roll, DesiredRotation.Roll, AngleTolerance))
// {
// DesiredRotation.Roll = FMath::FixedTurn(CurrentRotation.Roll, DesiredRotation.Roll, DeltaRot.Roll);
// }
// }
//
// // Set the new rotation.
// DesiredRotation.DiagnosticCheckNaN(TEXT("CharacterMovementComponent::PhysicsRotation(): DesiredRotation"));
// MoveUpdatedComponent(FVector::ZeroVector, DesiredRotation, /*bSweep*/ false);
// }
// }
//
// FRotator UGMS_CharacterMovementComponent::GMS_ComputeOrientToDesiredViewRotation_Implementation(const FRotator& CurrentRotation, float DeltaTime, FRotator& DeltaRotation) const
// {
// check(MovementSystem->GetControlSetting());
//
// bool bMoving = MovementSystem->GetLocomotionState().bMoving;
//
// DeltaRotation.Yaw = DeltaRotation.Pitch = DeltaRotation.Roll = MovementSystem->GetControlSetting()->ViewDirectionSetting.Get().RotationInterpolationSpeed;
//
// float DeltaYawAngle{0.0f};
// if (!bMoving && HasAnimationRotationDeltaYawAngle(DeltaTime, DeltaYawAngle))
// {
// auto NewRotation{CurrentRotation};
// NewRotation.Yaw += DeltaYawAngle;
// DeltaRotation.Yaw = -1;
// return NewRotation;
// }
//
// FRotator ControllerDesiredRotation = CharacterOwner->Controller->GetDesiredRotation();
//
// if (const FGMS_ViewDirectionSetting_Default* Setting = MovementSystem->GetControlSetting()->ViewDirectionSetting.GetPtr<FGMS_ViewDirectionSetting_Default>())
// {
// if (bMoving || Setting->bEnableRotationWhenNotMoving)
// {
// auto NewRotation{CurrentRotation};
// NewRotation.Yaw = ControllerDesiredRotation.Yaw;
// return NewRotation;
// }
// return CurrentRotation;
// }
//
// if (const FGMS_ViewDirectionSetting_Aiming* Setting = MovementSystem->GetControlSetting()->ViewDirectionSetting.GetPtr<FGMS_ViewDirectionSetting_Aiming>())
// {
// if (!bMoving && Setting->bEnableRotationWhenNotMoving)
// {
// auto NewRotation{CurrentRotation};
// NewRotation.Yaw = ControllerDesiredRotation.Yaw;
// return NewRotation;
// }
// }
//
// return CurrentRotation;
// }
//
//
// bool UGMS_CharacterMovementComponent::HasAnimationRotationDeltaYawAngle_Implementation(float DeltaTime, float& OutDeltaYawAngle) const
// {
// UAnimInstance* AnimInstance = CharacterOwner->GetMesh()->GetAnimInstance();
// if (UGMS_MainAnimInstance* AnimInst = Cast<UGMS_MainAnimInstance>(AnimInstance))
// {
// if (AnimInst->GetOffsetRootBoneRotationMode() != EOffsetRootBoneMode::Release)
// {
// return false;
// }
// }
//
// const float CurveValue = AnimInstance->GetCurveValue(UGMS_Constants::RotationYawSpeedCurveName());
//
// OutDeltaYawAngle = CurveValue * DeltaTime;
//
// return FMath::Abs(OutDeltaYawAngle) > UE_SMALL_NUMBER;
// }
//
//
// FRotator UGMS_CharacterMovementComponent::GMS_ComputeOrientToDesiredMovementRotation_Implementation(const FRotator& CurrentRotation, float DeltaTime, FRotator& DeltaRotation) const
// {
// return Super::ComputeOrientToMovementRotation(CurrentRotation, DeltaTime, DeltaRotation);
// }

View File

@@ -0,0 +1,6 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Mover/Flying/GMS_FlyingMode.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_FlyingMode)

View File

@@ -0,0 +1,6 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Mover/GMS_MoverSettingObjectLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_MoverSettingObjectLibrary)

View File

@@ -0,0 +1,6 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Mover/GMS_MoverStructLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_MoverStructLibrary)

View File

@@ -0,0 +1,150 @@
// // Copyright 2025 https://yuewu.dev/en All Rights Reserved.
//
//
// #include "Mover/Modifers/GMS_MovementStateModifer.h"
//
// #include "MoverComponent.h"
// #include "DefaultMovementSet/Settings/CommonLegacyMovementSettings.h"
// #include "MoveLibrary/MovementUtils.h"
// #include "Mover/GMS_MoverStructLibrary.h"
//
//
// FGMS_MovementStateModifier::FGMS_MovementStateModifier()
// {
// DurationMs = -1.0f;
// }
//
// void FGMS_MovementStateModifier::OnStart(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep, const FMoverSyncState& SyncState, const FMoverAuxStateContext& AuxState)
// {
// const FGMS_MoverMovementControlInputs* Inputs = SyncState.SyncStateCollection.FindMutableDataByType<FGMS_MoverMovementControlInputs>();
//
// if (UStanceSettings* StanceSettings = MoverComp->FindSharedSettings_Mutable<UStanceSettings>())
// {
// if (const UCapsuleComponent* CapsuleComponent = Cast<UCapsuleComponent>(MoverComp->GetUpdatedComponent()))
// {
// float OldHalfHeight = CapsuleComponent->GetScaledCapsuleHalfHeight();
// float NewHalfHeight = 0;
// float NewEyeHeight = 0;
//
// switch (ActiveStance)
// {
// default:
// case EStanceMode::Crouch:
// NewHalfHeight = StanceSettings->CrouchHalfHeight;
// NewEyeHeight = StanceSettings->CrouchedEyeHeight;
// break;
//
// // Prone isn't currently implemented
// case EStanceMode::Prone:
// UE_LOG(LogMover, Warning, TEXT("Stance got into prone stance - That stance is not currently implemented."));
// // TODO: returning here so we don't apply any bad state to actor in case prone was set. Eventually, the return should be removed once prone is implemented properly
// DurationMs = 0;
// return;
// }
//
// ApplyMovementSettings(MoverComp);
// }
// }
// }
//
// void FGMS_MovementStateModifier::OnEnd(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep, const FMoverSyncState& SyncState, const FMoverAuxStateContext& AuxState)
// {
// const AActor* OwnerCDO = Cast<AActor>(MoverComp->GetOwner()->GetClass()->GetDefaultObject());
//
// if (UCapsuleComponent* CapsuleComponent = Cast<UCapsuleComponent>(MoverComp->GetUpdatedComponent()))
// {
// if (const UCapsuleComponent* OriginalCapsule = UMovementUtils::GetOriginalComponentType<UCapsuleComponent>(MoverComp->GetOwner()))
// {
// if (const APawn* OwnerCDOAsPawn = Cast<APawn>(OwnerCDO))
// {
// AdjustCapsule(MoverComp, CapsuleComponent->GetScaledCapsuleHalfHeight(), OriginalCapsule->GetScaledCapsuleHalfHeight(), OwnerCDOAsPawn->BaseEyeHeight);
// RevertMovementSettings(MoverComp);
// }
// }
// }
// }
//
// void FGMS_MovementStateModifier::OnPreMovement(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep)
// {
// // TODO: Check for different inputs/state here and manage swapping between stances - use AdjustCapsule and Apply/Revert movement settings.
//
// // TODO: Prone isn't currently implemented - so we're just going to cancel the modifier if we got into that state
// if (ActiveStance == EStanceMode::Prone)
// {
// UE_LOG(LogMover, Warning, TEXT("Stance got into prone stance - That stance is not currently implemented."));
// DurationMs = 0;
// }
// }
//
// void FGMS_MovementStateModifier::OnPostMovement(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep, const FMoverSyncState& SyncState, const FMoverAuxStateContext& AuxState)
// {
// FMovementModifierBase::OnPostMovement(MoverComp, TimeStep, SyncState, AuxState);
// }
//
// FMovementModifierBase* FGMS_MovementStateModifier::Clone() const
// {
// FGMS_MovementStateModifier* CopyPtr = new FGMS_MovementStateModifier(*this);
// return CopyPtr;
// }
//
// void FGMS_MovementStateModifier::NetSerialize(FArchive& Ar)
// {
// Super::NetSerialize(Ar);
// }
//
// UScriptStruct* FGMS_MovementStateModifier::GetScriptStruct() const
// {
// return StaticStruct();
// }
//
// FString FGMS_MovementStateModifier::ToSimpleString() const
// {
// return FString::Printf(TEXT("Stance Modifier"));
// }
//
// void FGMS_MovementStateModifier::AddReferencedObjects(FReferenceCollector& Collector)
// {
// Super::AddReferencedObjects(Collector);
// }
//
//
// void FGMS_MovementStateModifier::ApplyMovementSettings(UMoverComponent* MoverComp)
// {
// switch (ActiveStance)
// {
// default:
// case EStanceMode::Crouch:
// if (UStanceSettings* StanceSettings = MoverComp->FindSharedSettings_Mutable<UStanceSettings>())
// {
// // Update relevant movement settings
// if (UCommonLegacyMovementSettings* MovementSettings = MoverComp->FindSharedSettings_Mutable<UCommonLegacyMovementSettings>())
// {
// MovementSettings->Acceleration = StanceSettings->CrouchingMaxAcceleration;
// MovementSettings->MaxSpeed = StanceSettings->CrouchingMaxSpeed;
// }
// }
//
// break;
//
// // Prone isn't currently implemented properly so we're just doing nothing for now
// case EStanceMode::Prone:
// UE_LOG(LogMover, Warning, TEXT("Stance got into prone stance - That mode is not currently implemented fully."));
// break;
// }
// }
//
// void FGMS_MovementStateModifier::RevertMovementSettings(UMoverComponent* MoverComp)
// {
// if (const UMoverComponent* CDOMoverComp = UMovementUtils::GetOriginalComponentType<UMoverComponent>(MoverComp->GetOwner()))
// {
// const UCommonLegacyMovementSettings* OriginalMovementSettings = CDOMoverComp->FindSharedSettings<UCommonLegacyMovementSettings>();
// UCommonLegacyMovementSettings* MovementSettings = MoverComp->FindSharedSettings_Mutable<UCommonLegacyMovementSettings>();
//
// // Revert movement settings back to original settings
// if (MovementSettings && OriginalMovementSettings)
// {
// MovementSettings->Acceleration = OriginalMovementSettings->Acceleration;
// MovementSettings->MaxSpeed = OriginalMovementSettings->MaxSpeed;
// }
// }
// }

View File

@@ -0,0 +1,14 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Mover/Walking/GMS_WalkingMode.h"
#include "Mover/GMS_MoverSettingObjectLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_WalkingMode)
UGMS_WalkingMode::UGMS_WalkingMode(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SharedSettingsClasses.Add(UGMS_MoverGroundedMovementSettings::StaticClass());
}

View File

@@ -0,0 +1,8 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Mover/Zipline/GMS_ZiplineInterface.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_ZiplineInterface)
// Add default functionality here for any IGMS_ZiplineInterface functions that are not pure virtual.

View File

@@ -0,0 +1,93 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Mover/Zipline/GMS_ZiplineModeTransition.h"
#include "GameFramework/Actor.h"
#include "DefaultMovementSet/CharacterMoverComponent.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Mover/GMS_MoverStructLibrary.h"
#include "Mover/Zipline/GMS_ZiplineInterface.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_ZiplineModeTransition)
// UGMS_ZiplineStartTransition //////////////////////////////
UGMS_ZiplineStartTransition::UGMS_ZiplineStartTransition(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
#if ENGINE_MINOR_VERSION >=6
FTransitionEvalResult UGMS_ZiplineStartTransition::Evaluate_Implementation(const FSimulationTickParams& Params) const
#else
FTransitionEvalResult UGMS_ZiplineStartTransition::OnEvaluate(const FSimulationTickParams& Params) const
#endif
{
FTransitionEvalResult EvalResult = FTransitionEvalResult::NoTransition;
UCharacterMoverComponent* MoverComp = Cast<UCharacterMoverComponent>(Params.MovingComps.MoverComponent.Get());
const FMoverSyncState& SyncState = Params.StartState.SyncState;
if (MoverComp && MoverComp->IsAirborne() && SyncState.MovementMode != ZipliningModeName)
{
if (const FGMS_MoverTagInputs* AbilityInputs = Params.StartState.InputCmd.InputCollection.FindDataByType<FGMS_MoverTagInputs>())
{
if (ZipliningInputTag.IsValid() && AbilityInputs->Tags.HasTagExact(ZipliningInputTag))
{
TArray<AActor*> OverlappingActors;
MoverComp->GetOwner()->GetOverlappingActors(OUT OverlappingActors);
for (AActor* CandidateActor : OverlappingActors)
{
bool bIsZipline = UKismetSystemLibrary::DoesImplementInterface(CandidateActor, UGMS_ZiplineInterface::StaticClass());
if (bIsZipline)
{
EvalResult.NextMode = ZipliningModeName;
break;
}
}
}
}
}
return EvalResult;
}
// UGMS_ZiplineEndTransition //////////////////////////////
UGMS_ZiplineEndTransition::UGMS_ZiplineEndTransition(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
#if ENGINE_MINOR_VERSION >=6
FTransitionEvalResult UGMS_ZiplineEndTransition::Evaluate_Implementation(const FSimulationTickParams& Params) const
#else
FTransitionEvalResult UGMS_ZiplineEndTransition::OnEvaluate(const FSimulationTickParams& Params) const
#endif
{
FTransitionEvalResult EvalResult = FTransitionEvalResult::NoTransition;
if (const FCharacterDefaultInputs* DefaultInputs = Params.StartState.InputCmd.InputCollection.FindDataByType<FCharacterDefaultInputs>())
{
if (DefaultInputs->bIsJumpJustPressed)
{
EvalResult.NextMode = AutoExitToMode;
}
}
return EvalResult;
}
#if ENGINE_MINOR_VERSION >=6
void UGMS_ZiplineEndTransition::Trigger_Implementation(const FSimulationTickParams& Params)
#else
void UGMS_ZiplineEndTransition::OnTrigger(const FSimulationTickParams& Params)
#endif
{
//TODO: create a small jump, using current directionality
}

View File

@@ -0,0 +1,243 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Mover/Zipline/GMS_ZipliningMode.h"
#include "MoverComponent.h"
#include "DefaultMovementSet/Settings/CommonLegacyMovementSettings.h"
#include "Kismet/KismetSystemLibrary.h"
#include "MoveLibrary/MovementUtils.h"
#include "Mover/Zipline/GMS_ZiplineInterface.h"
#include "Mover/Zipline/GMS_ZiplineModeTransition.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_ZipliningMode)
// FGMS_ZipliningState //////////////////////////////
FMoverDataStructBase* FGMS_ZipliningState::Clone() const
{
FGMS_ZipliningState* CopyPtr = new FGMS_ZipliningState(*this);
return CopyPtr;
}
bool FGMS_ZipliningState::NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess)
{
Super::NetSerialize(Ar, Map, bOutSuccess);
Ar << ZiplineActor;
Ar.SerializeBits(&bIsMovingAtoB, 1);
bOutSuccess = true;
return true;
}
void FGMS_ZipliningState::ToString(FAnsiStringBuilderBase& Out) const
{
Super::ToString(Out);
Out.Appendf("ZiplineActor: %s\n", *GetNameSafe(ZiplineActor));
Out.Appendf("IsMovingAtoB: %d\n", bIsMovingAtoB);
}
bool FGMS_ZipliningState::ShouldReconcile(const FMoverDataStructBase& AuthorityState) const
{
const FGMS_ZipliningState* AuthorityZiplineState = static_cast<const FGMS_ZipliningState*>(&AuthorityState);
return (ZiplineActor != AuthorityZiplineState->ZiplineActor) ||
(bIsMovingAtoB != AuthorityZiplineState->bIsMovingAtoB);
}
void FGMS_ZipliningState::Interpolate(const FMoverDataStructBase& From, const FMoverDataStructBase& To, float Pct)
{
const FGMS_ZipliningState* FromState = static_cast<const FGMS_ZipliningState*>(&From);
const FGMS_ZipliningState* ToState = static_cast<const FGMS_ZipliningState*>(&To);
ZiplineActor = ToState->ZiplineActor;
bIsMovingAtoB = ToState->bIsMovingAtoB;
}
// UGMS_ZipliningMode //////////////////////////////
UGMS_ZipliningMode::UGMS_ZipliningMode(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Transitions.Add(CreateDefaultSubobject<UGMS_ZiplineEndTransition>(TEXT("ZiplineEndTransition")));
}
#if ENGINE_MINOR_VERSION >=6
void UGMS_ZipliningMode::GenerateMove_Implementation(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, FProposedMove& OutProposedMove) const
#else
void UGMS_ZipliningMode::OnGenerateMove(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, FProposedMove& OutProposedMove) const
#endif
{
UMoverComponent* MoverComp = GetMoverComponent();
// Ziplining is just following a path from A to B, so all movement is handled in OnSimulationTick
OutProposedMove = FProposedMove();
}
#if ENGINE_MINOR_VERSION >=6
void UGMS_ZipliningMode::SimulationTick_Implementation(const FSimulationTickParams& Params, FMoverTickEndData& OutputState)
#else
void UGMS_ZipliningMode::OnSimulationTick(const FSimulationTickParams& Params, FMoverTickEndData& OutputState)
#endif
{
// Are we continuing a move or starting fresh?
const FGMS_ZipliningState* StartingZipState = Params.StartState.SyncState.SyncStateCollection.FindDataByType<FGMS_ZipliningState>();
FMoverDefaultSyncState& OutputSyncState = OutputState.SyncState.SyncStateCollection.FindOrAddMutableDataByType<FMoverDefaultSyncState>();
FGMS_ZipliningState& OutZipState = OutputState.SyncState.SyncStateCollection.FindOrAddMutableDataByType<FGMS_ZipliningState>();
USceneComponent* UpdatedComponent = Params.MovingComps.UpdatedComponent.Get();
UMoverComponent* MoverComp = Params.MovingComps.MoverComponent.Get();
AActor* MoverActor = MoverComp->GetOwner();
USceneComponent* StartPoint = nullptr;
USceneComponent* EndPoint = nullptr;
FVector ZipDirection;
FVector FlatFacingDir;
const float DeltaSeconds = Params.TimeStep.StepMs * 0.001f;
FVector ActorOrigin;
FVector BoxExtent;
MoverActor->GetActorBounds(true, OUT ActorOrigin, OUT BoxExtent);
const FVector ActorToZiplineOffset = MoverComp->GetUpDirection() * BoxExtent.Z;
if (!StartingZipState)
{
// There is no existing zipline state... so let's find the target
// A) teleport to the closest starting point, set the zip direction
// B) choose the appropriate facing direction
// C) choose the appropriate initial velocity
TArray<AActor*> OverlappingActors;
MoverComp->GetOwner()->GetOverlappingActors(OUT OverlappingActors);
for (AActor* CandidateActor : OverlappingActors)
{
bool bIsZipline = UKismetSystemLibrary::DoesImplementInterface(CandidateActor, UGMS_ZiplineInterface::StaticClass());
if (bIsZipline)
{
const FVector MoverLoc = UpdatedComponent->GetComponentLocation();
USceneComponent* ZipPointA = IGMS_ZiplineInterface::Execute_GetStartComponent(CandidateActor);
USceneComponent* ZipPointB = IGMS_ZiplineInterface::Execute_GetEndComponent(CandidateActor);
if (FVector::DistSquared(ZipPointA->GetComponentLocation(), MoverLoc) < FVector::DistSquared(ZipPointB->GetComponentLocation(), MoverLoc))
{
OutZipState.bIsMovingAtoB = true;
StartPoint = ZipPointA;
EndPoint = ZipPointB;
}
else
{
OutZipState.bIsMovingAtoB = false;
StartPoint = ZipPointB;
EndPoint = ZipPointA;
}
ZipDirection = (EndPoint->GetComponentLocation() - StartPoint->GetComponentLocation()).GetSafeNormal();
const FVector WarpLocation = StartPoint->GetComponentLocation() - ActorToZiplineOffset;
FlatFacingDir = FVector::VectorPlaneProject(ZipDirection, MoverComp->GetUpDirection()).GetSafeNormal();
OutZipState.ZiplineActor = CandidateActor;
UpdatedComponent->GetOwner()->TeleportTo(WarpLocation, FlatFacingDir.ToOrientationRotator());
break;
}
}
// If we were unable to find a valid target zipline, refund all the time and let the actor fall
if (!StartPoint || !EndPoint)
{
FName DefaultAirMode = DefaultModeNames::Falling;
if (UCommonLegacyMovementSettings* LegacySettings = MoverComp->FindSharedSettings_Mutable<UCommonLegacyMovementSettings>())
{
DefaultAirMode = LegacySettings->AirMovementModeName;
}
OutputState.MovementEndState.NextModeName = DefaultModeNames::Falling;
OutputState.MovementEndState.RemainingMs = Params.TimeStep.StepMs;
return;
}
}
else
{
check(StartingZipState->ZiplineActor);
OutZipState = *StartingZipState;
USceneComponent* ZipPointA = IGMS_ZiplineInterface::Execute_GetStartComponent(StartingZipState->ZiplineActor);
USceneComponent* ZipPointB = IGMS_ZiplineInterface::Execute_GetEndComponent(StartingZipState->ZiplineActor);
if (StartingZipState->bIsMovingAtoB)
{
StartPoint = ZipPointA;
EndPoint = ZipPointB;
}
else
{
StartPoint = ZipPointB;
EndPoint = ZipPointA;
}
ZipDirection = (EndPoint->GetComponentLocation() - StartPoint->GetComponentLocation()).GetSafeNormal();
FlatFacingDir = FVector::VectorPlaneProject(ZipDirection, MoverComp->GetUpDirection()).GetSafeNormal();
}
// Now let's slide along the zipline
const FVector StepStartPos = UpdatedComponent->GetComponentLocation() + ActorToZiplineOffset;
const FVector DesiredEndPos = StepStartPos + (ZipDirection * MaxSpeed * DeltaSeconds); // TODO: Make speed more dynamic
FVector ActualEndPos = FMath::ClosestPointOnSegment(DesiredEndPos,
StartPoint->GetComponentLocation(),
EndPoint->GetComponentLocation());
bool bWillReachEndPosition = (ActualEndPos - EndPoint->GetComponentLocation()).IsNearlyZero();
FVector MoveDelta = ActualEndPos - StepStartPos;
FMovementRecord MoveRecord;
MoveRecord.SetDeltaSeconds(DeltaSeconds);
if (!MoveDelta.IsNearlyZero())
{
FHitResult Hit(1.f);
UMovementUtils::TrySafeMoveUpdatedComponent(Params.MovingComps, MoveDelta, FlatFacingDir.ToOrientationQuat(), true, Hit, ETeleportType::None, MoveRecord);
}
const FVector FinalLocation = UpdatedComponent->GetComponentLocation();
const FVector FinalVelocity = MoveRecord.GetRelevantVelocity();
OutputSyncState.SetTransforms_WorldSpace(FinalLocation,
UpdatedComponent->GetComponentRotation(),
FinalVelocity,
nullptr); // no movement base
UpdatedComponent->ComponentVelocity = FinalVelocity;
if (bWillReachEndPosition)
{
FName DefaultAirMode = DefaultModeNames::Falling;
if (UCommonLegacyMovementSettings* LegacySettings = MoverComp->FindSharedSettings_Mutable<UCommonLegacyMovementSettings>())
{
DefaultAirMode = LegacySettings->AirMovementModeName;
}
OutputState.MovementEndState.NextModeName = DefaultAirMode;
// TODO: If we reach the end position early, we should refund the remaining time
}
}

View File

@@ -0,0 +1,118 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Nodes/GMS_AnimNode_CurvesBlend.h"
#include "Animation/AnimTrace.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimNode_CurvesBlend)
void FGMS_AnimNode_CurvesBlend::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC()
Super::Initialize_AnyThread(Context);
SourcePose.Initialize(Context);
CurvesPose.Initialize(Context);
}
void FGMS_AnimNode_CurvesBlend::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC()
Super::CacheBones_AnyThread(Context);
SourcePose.CacheBones(Context);
CurvesPose.CacheBones(Context);
}
void FGMS_AnimNode_CurvesBlend::Update_AnyThread(const FAnimationUpdateContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC()
Super::Update_AnyThread(Context);
GetEvaluateGraphExposedInputs().Execute(Context);
SourcePose.Update(Context);
const auto CurrentBlendAmount{GetBlendAmount()};
if (FAnimWeight::IsRelevant(CurrentBlendAmount))
{
CurvesPose.Update(Context);
}
TRACE_ANIM_NODE_VALUE(Context, TEXT("Blend Amount"), CurrentBlendAmount);
TRACE_ANIM_NODE_VALUE(Context, TEXT("Blend Mode"), *StaticEnum<EGMS_CurvesBlendMode>()->GetNameStringByValue(static_cast<int64>(GetBlendMode())));
}
void FGMS_AnimNode_CurvesBlend::Evaluate_AnyThread(FPoseContext& Output)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC()
ANIM_MT_SCOPE_CYCLE_COUNTER_VERBOSE(CurvesBlend, !IsInGameThread());
Super::Evaluate_AnyThread(Output);
SourcePose.Evaluate(Output);
const auto CurrentBlendAmount{GetBlendAmount()};
if (!FAnimWeight::IsRelevant(CurrentBlendAmount))
{
return;
}
auto CurvesPoseContext{Output};
CurvesPose.Evaluate(CurvesPoseContext);
switch (GetBlendMode())
{
case EGMS_CurvesBlendMode::BlendByAmount:
Output.Curve.Accumulate(CurvesPoseContext.Curve, CurrentBlendAmount);
break;
case EGMS_CurvesBlendMode::Combine:
Output.Curve.Combine(CurvesPoseContext.Curve);
break;
case EGMS_CurvesBlendMode::CombinePreserved:
Output.Curve.CombinePreserved(CurvesPoseContext.Curve);
break;
case EGMS_CurvesBlendMode::UseMaxValue:
Output.Curve.UseMaxValue(CurvesPoseContext.Curve);
break;
case EGMS_CurvesBlendMode::UseMinValue:
Output.Curve.UseMinValue(CurvesPoseContext.Curve);
break;
case EGMS_CurvesBlendMode::Override:
Output.Curve.Override(CurvesPoseContext.Curve);
break;
}
}
void FGMS_AnimNode_CurvesBlend::GatherDebugData(FNodeDebugData& DebugData)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
TStringBuilder<256> DebugItemBuilder{InPlace, DebugData.GetNodeName(this), TEXTVIEW(": Blend Amount: ")};
DebugItemBuilder.Appendf(TEXT("%.2f"), GetBlendAmount());
DebugData.AddDebugItem(FString{DebugItemBuilder});
SourcePose.GatherDebugData(DebugData.BranchFlow(1.0f));
CurvesPose.GatherDebugData(DebugData.BranchFlow(GetBlendAmount()));
}
float FGMS_AnimNode_CurvesBlend::GetBlendAmount() const
{
return GET_ANIM_NODE_DATA(float, BlendAmount);
}
EGMS_CurvesBlendMode FGMS_AnimNode_CurvesBlend::GetBlendMode() const
{
return GET_ANIM_NODE_DATA(EGMS_CurvesBlendMode, BlendMode);
}

View File

@@ -0,0 +1,51 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Nodes/GMS_AnimNode_GameplayTagsBlend.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimNode_GameplayTagsBlend)
int32 FGMS_AnimNode_GameplayTagsBlend::GetActiveChildIndex()
{
const auto& CurrentActiveTag{GetActiveTag()};
return CurrentActiveTag.IsValid()
? GetTags().Find(CurrentActiveTag) + 1
: 0;
}
const FGameplayTag& FGMS_AnimNode_GameplayTagsBlend::GetActiveTag() const
{
return GET_ANIM_NODE_DATA(FGameplayTag, ActiveTag);
}
const TArray<FGameplayTag>& FGMS_AnimNode_GameplayTagsBlend::GetTags() const
{
return GET_ANIM_NODE_DATA(TArray<FGameplayTag>, Tags);
}
#if WITH_EDITOR
void FGMS_AnimNode_GameplayTagsBlend::RefreshPoses()
{
const auto Difference{BlendPose.Num() - GetTags().Num() - 1};
if (Difference == 0)
{
return;
}
if (Difference > 0)
{
for (auto i{Difference}; i > 0; i--)
{
RemovePose(BlendPose.Num() - 1);
}
}
else
{
for (auto i{Difference}; i < 0; i++)
{
AddPose();
}
}
}
#endif

View File

@@ -0,0 +1,296 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Nodes/GMS_AnimNode_LayeredBoneBlend.h"
#include "AnimationRuntime.h"
#include "Animation/AnimInstanceProxy.h"
#include "Animation/AnimTrace.h"
#include "Animation/AnimCurveTypes.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimNode_LayeredBoneBlend)
/////////////////////////////////////////////////////
// FGMS_AnimNode_LayeredBoneBlend
void FGMS_AnimNode_LayeredBoneBlend::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread)
FAnimNode_Base::Initialize_AnyThread(Context);
const int NumPoses = BlendPoses.Num();
checkSlow(BlendWeights.Num() == NumPoses);
// initialize children
BasePose.Initialize(Context);
if (NumPoses > 0)
{
for (int32 ChildIndex = 0; ChildIndex < NumPoses; ++ChildIndex)
{
BlendPoses[ChildIndex].Initialize(Context);
}
}
}
void FGMS_AnimNode_LayeredBoneBlend::RebuildPerBoneBlendWeights(const USkeleton* InSkeleton)
{
if (InSkeleton)
{
if (ExternalLayerSetup.BranchFilters.IsEmpty())
{
FAnimationRuntime::CreateMaskWeights(PerBoneBlendWeights, LayerSetup, InSkeleton);
}
else
{
for (const FBranchFilter& BranchFilter : ExternalLayerSetup.BranchFilters)
{
LayerSetup[0].BranchFilters.Add(BranchFilter);
}
FAnimationRuntime::CreateMaskWeights(PerBoneBlendWeights, LayerSetup, InSkeleton);
}
SkeletonGuid = InSkeleton->GetGuid();
VirtualBoneGuid = InSkeleton->GetVirtualBoneGuid();
}
}
bool FGMS_AnimNode_LayeredBoneBlend::ArePerBoneBlendWeightsValid(const USkeleton* InSkeleton) const
{
return (InSkeleton != nullptr && InSkeleton->GetGuid() == SkeletonGuid && InSkeleton->GetVirtualBoneGuid() == VirtualBoneGuid);
}
void FGMS_AnimNode_LayeredBoneBlend::UpdateCachedBoneData(const FBoneContainer& RequiredBones, const USkeleton* Skeleton)
{
if (LayerSetup.IsValidIndex(0) && LayerSetup[0].BranchFilters.IsEmpty())
{
RebuildPerBoneBlendWeights(Skeleton);
}
// if(RequiredBones.GetSerialNumber() == RequiredBonesSerialNumber)
// {
// return;
// }
if (!ArePerBoneBlendWeightsValid(Skeleton))
{
RebuildPerBoneBlendWeights(Skeleton);
}
// build desired bone weights
const TArray<FBoneIndexType>& RequiredBoneIndices = RequiredBones.GetBoneIndicesArray();
const int32 NumRequiredBones = RequiredBoneIndices.Num();
DesiredBoneBlendWeights.SetNumZeroed(NumRequiredBones);
for (int32 RequiredBoneIndex=0; RequiredBoneIndex<NumRequiredBones; RequiredBoneIndex++)
{
const int32 SkeletonBoneIndex = RequiredBones.GetSkeletonIndex(FCompactPoseBoneIndex(RequiredBoneIndex));
if (ensure(SkeletonBoneIndex != INDEX_NONE))
{
DesiredBoneBlendWeights[RequiredBoneIndex] = PerBoneBlendWeights[SkeletonBoneIndex];
}
}
CurrentBoneBlendWeights.Reset(DesiredBoneBlendWeights.Num());
CurrentBoneBlendWeights.AddZeroed(DesiredBoneBlendWeights.Num());
//Reinitialize bone blend weights now that we have cleared them
FAnimationRuntime::UpdateDesiredBoneWeight(DesiredBoneBlendWeights, CurrentBoneBlendWeights, BlendWeights);
// Build curve source indices
{
// Get the original Reserve value
int32 OriginalReserve = CurvePoseSourceIndices.Max();
CurvePoseSourceIndices.Empty();
Skeleton->ForEachCurveMetaData([this, &RequiredBones](const FName& InCurveName, const FCurveMetaData& InMetaData)
{
for (const FBoneReference& LinkedBone : InMetaData.LinkedBones)
{
FCompactPoseBoneIndex CompactPoseIndex = LinkedBone.GetCompactPoseIndex(RequiredBones);
if (CompactPoseIndex != INDEX_NONE)
{
if (DesiredBoneBlendWeights[CompactPoseIndex.GetInt()].BlendWeight > 0.f)
{
CurvePoseSourceIndices.Add(InCurveName, DesiredBoneBlendWeights[CompactPoseIndex.GetInt()].SourceIndex);
break;
}
}
}
});
// Shrink afterwards to exactly what was used if the Reserve increased, to save memory. Eventually the reserve will
// stabilize at the maximum number of nodes actually used in practice for this specific anim node.
if (CurvePoseSourceIndices.Num() > OriginalReserve)
{
CurvePoseSourceIndices.Shrink();
}
}
RequiredBonesSerialNumber = RequiredBones.GetSerialNumber();
LayerSetup[0].BranchFilters.Reset();
}
void FGMS_AnimNode_LayeredBoneBlend::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(CacheBones_AnyThread)
BasePose.CacheBones(Context);
int32 NumPoses = BlendPoses.Num();
for(int32 ChildIndex=0; ChildIndex<NumPoses; ChildIndex++)
{
BlendPoses[ChildIndex].CacheBones(Context);
}
UpdateCachedBoneData(Context.AnimInstanceProxy->GetRequiredBones(), Context.AnimInstanceProxy->GetSkeleton());
}
void FGMS_AnimNode_LayeredBoneBlend::Update_AnyThread(const FAnimationUpdateContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Update_AnyThread)
bHasRelevantPoses = false;
int32 RootMotionBlendPose = -1;
float RootMotionWeight = 0.f;
const float RootMotionClearWeight = bBlendRootMotionBasedOnRootBone ? 0.f : 1.f;
if (IsLODEnabled(Context.AnimInstanceProxy))
{
GetEvaluateGraphExposedInputs().Execute(Context);
for (int32 ChildIndex = 0; ChildIndex < BlendPoses.Num(); ++ChildIndex)
{
const float ChildWeight = BlendWeights[ChildIndex];
if (FAnimWeight::IsRelevant(ChildWeight))
{
if (bHasRelevantPoses == false)
{
// Update cached data now we know we might be valid
UpdateCachedBoneData(Context.AnimInstanceProxy->GetRequiredBones(), Context.AnimInstanceProxy->GetSkeleton());
// Update weights
FAnimationRuntime::UpdateDesiredBoneWeight(DesiredBoneBlendWeights, CurrentBoneBlendWeights, BlendWeights);
bHasRelevantPoses = true;
if(bBlendRootMotionBasedOnRootBone && !CurrentBoneBlendWeights.IsEmpty())
{
const float NewRootMotionWeight = CurrentBoneBlendWeights[0].BlendWeight;
if(NewRootMotionWeight > ZERO_ANIMWEIGHT_THRESH)
{
RootMotionWeight = NewRootMotionWeight;
RootMotionBlendPose = CurrentBoneBlendWeights[0].SourceIndex;
}
}
}
const float ThisPoseRootMotionWeight = (ChildIndex == RootMotionBlendPose) ? RootMotionWeight : RootMotionClearWeight;
BlendPoses[ChildIndex].Update(Context.FractionalWeightAndRootMotion(ChildWeight, ThisPoseRootMotionWeight));
}
}
}
// initialize children
const float BaseRootMotionWeight = 1.f - RootMotionWeight;
if (BaseRootMotionWeight < ZERO_ANIMWEIGHT_THRESH)
{
BasePose.Update(Context.FractionalWeightAndRootMotion(1.f, BaseRootMotionWeight));
}
else
{
BasePose.Update(Context);
}
TRACE_ANIM_NODE_VALUE(Context, TEXT("Num Poses"), BlendPoses.Num());
}
void FGMS_AnimNode_LayeredBoneBlend::Evaluate_AnyThread(FPoseContext& Output)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Evaluate_AnyThread)
ANIM_MT_SCOPE_CYCLE_COUNTER(BlendPosesInGraph, !IsInGameThread());
const int NumPoses = BlendPoses.Num();
if ((NumPoses == 0) || !bHasRelevantPoses)
{
BasePose.Evaluate(Output);
}
else
{
FPoseContext BasePoseContext(Output);
// evaluate children
BasePose.Evaluate(BasePoseContext);
TArray<FCompactPose> TargetBlendPoses;
TargetBlendPoses.SetNum(NumPoses);
TArray<FBlendedCurve> TargetBlendCurves;
TargetBlendCurves.SetNum(NumPoses);
TArray<UE::Anim::FStackAttributeContainer> TargetBlendAttributes;
TargetBlendAttributes.SetNum(NumPoses);
for (int32 ChildIndex = 0; ChildIndex < NumPoses; ++ChildIndex)
{
if (FAnimWeight::IsRelevant(BlendWeights[ChildIndex]))
{
FPoseContext CurrentPoseContext(Output);
BlendPoses[ChildIndex].Evaluate(CurrentPoseContext);
TargetBlendPoses[ChildIndex].MoveBonesFrom(CurrentPoseContext.Pose);
TargetBlendCurves[ChildIndex].MoveFrom(CurrentPoseContext.Curve);
TargetBlendAttributes[ChildIndex].MoveFrom(CurrentPoseContext.CustomAttributes);
}
else
{
TargetBlendPoses[ChildIndex].ResetToRefPose(BasePoseContext.Pose.GetBoneContainer());
TargetBlendCurves[ChildIndex].InitFrom(Output.Curve);
}
}
// filter to make sure it only includes curves that are linked to the correct bone filter
UE::Anim::FNamedValueArrayUtils::RemoveByPredicate(BasePoseContext.Curve, CurvePoseSourceIndices,
[](const UE::Anim::FCurveElement& InOutBasePoseElement, const UE::Anim::FCurveElementIndexed& InSourceIndexElement)
{
// if source index is set, remove base pose curve value
return (InSourceIndexElement.Index != INDEX_NONE);
});
// Filter child pose curves
for (int32 ChildIndex = 0; ChildIndex < NumPoses; ++ChildIndex)
{
UE::Anim::FNamedValueArrayUtils::RemoveByPredicate(TargetBlendCurves[ChildIndex], CurvePoseSourceIndices,
[ChildIndex](const UE::Anim::FCurveElement& InOutBasePoseElement, const UE::Anim::FCurveElementIndexed& InSourceIndexElement)
{
// if not source, remove it
return (InSourceIndexElement.Index != INDEX_NONE) && (InSourceIndexElement.Index != ChildIndex);
});
}
FAnimationRuntime::EBlendPosesPerBoneFilterFlags BlendFlags = FAnimationRuntime::EBlendPosesPerBoneFilterFlags::None;
if (bMeshSpaceRotationBlend)
{
BlendFlags |= FAnimationRuntime::EBlendPosesPerBoneFilterFlags::MeshSpaceRotation;
}
if (bMeshSpaceScaleBlend)
{
BlendFlags |= FAnimationRuntime::EBlendPosesPerBoneFilterFlags::MeshSpaceScale;
}
FAnimationPoseData AnimationPoseData(Output);
FAnimationRuntime::BlendPosesPerBoneFilter(BasePoseContext.Pose, TargetBlendPoses, BasePoseContext.Curve, TargetBlendCurves, BasePoseContext.CustomAttributes, TargetBlendAttributes, AnimationPoseData, CurrentBoneBlendWeights, BlendFlags, CurveBlendOption);
}
}
void FGMS_AnimNode_LayeredBoneBlend::GatherDebugData(FNodeDebugData& DebugData)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
const int NumPoses = BlendPoses.Num();
FString DebugLine = DebugData.GetNodeName(this);
DebugLine += FString::Printf(TEXT("(Num Poses: %i)"), NumPoses);
DebugData.AddDebugItem(DebugLine);
BasePose.GatherDebugData(DebugData.BranchFlow(1.f));
for (int32 ChildIndex = 0; ChildIndex < NumPoses; ++ChildIndex)
{
BlendPoses[ChildIndex].GatherDebugData(DebugData.BranchFlow(BlendWeights[ChildIndex]));
}
}

View File

@@ -0,0 +1,680 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Nodes/GMS_AnimNode_OrientationWarping.h"
#include "Animation/AnimInstanceProxy.h"
#include "Animation/AnimNodeFunctionRef.h"
#include "Animation/AnimRootMotionProvider.h"
#include "BoneControllers/AnimNode_OffsetRootBone.h"
#include "HAL/IConsoleManager.h"
#include "Animation/AnimTrace.h"
#include "Logging/LogVerbosity.h"
#include "VisualLogger/VisualLogger.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_AnimNode_OrientationWarping)
DECLARE_CYCLE_STAT(TEXT("OrientationWarping Eval"), STAT_OrientationWarping_Eval, STATGROUP_Anim);
#if ENABLE_ANIM_DEBUG
static TAutoConsoleVariable<int32> CVarAnimNodeOrientationWarpingDebug(TEXT("a.AnimNode.GenericOrientationWarping.Debug"), 0, TEXT("Turn on visualization debugging for Orientation Warping."));
static TAutoConsoleVariable<int32> CVarAnimNodeOrientationWarpingVerbose(TEXT("a.AnimNode.GenericOrientationWarping.Verbose"), 0, TEXT("Turn on verbose graph debugging for Orientation Warping"));
static TAutoConsoleVariable<int32> CVarAnimNodeOrientationWarpingEnable(TEXT("a.AnimNode.GenericOrientationWarping.Enable"), 1, TEXT("Toggle Orientation Warping"));
#endif
namespace UE::Anim
{
static inline FVector GetAxisVector(const EAxis::Type& InAxis)
{
switch (InAxis)
{
case EAxis::X:
return FVector::ForwardVector;
case EAxis::Y:
return FVector::RightVector;
default:
return FVector::UpVector;
};
}
static inline bool IsInvalidWarpingAngleDegrees(float Angle, float Tolerance)
{
Angle = FRotator::NormalizeAxis(Angle);
return FMath::IsNearlyZero(Angle, Tolerance) || FMath::IsNearlyEqual(FMath::Abs(Angle), 180.f, Tolerance);
}
static float SignedAngleRadBetweenNormals(const FVector& From, const FVector& To, const FVector& Axis)
{
const float FromDotTo = FVector::DotProduct(From, To);
const float Angle = FMath::Acos(FromDotTo);
const FVector Cross = FVector::CrossProduct(From, To);
const float Dot = FVector::DotProduct(Cross, Axis);
return Dot >= 0 ? Angle : -Angle;
}
}
void FGMS_AnimNode_OrientationWarping::GatherDebugData(FNodeDebugData& DebugData)
{
FString DebugLine = DebugData.GetNodeName(this);
#if ENABLE_ANIM_DEBUG
if (CVarAnimNodeOrientationWarpingVerbose.GetValueOnAnyThread() == 1)
{
if (Mode == EWarpingEvaluationMode::Manual)
{
DebugLine += TEXT("\n - Evaluation Mode: (Manual)");
DebugLine += FString::Printf(TEXT("\n - Orientation Angle: (%.3fd)"), FMath::RadiansToDegrees(ActualOrientationAngleRad));
}
else
{
DebugLine += TEXT("\n - Evaluation Mode: (Graph)");
DebugLine += FString::Printf(TEXT("\n - Orientation Angle: (%.3fd)"), FMath::RadiansToDegrees(ActualOrientationAngleRad));
// Locomotion angle is already in degrees.
DebugLine += FString::Printf(TEXT("\n - Locomotion Angle: (%.3fd)"), LocomotionAngle);
DebugLine += FString::Printf(TEXT("\n - Locomotion Delta Angle Threshold: (%.3fd)"), LocomotionAngleDeltaThreshold);
#if WITH_EDITORONLY_DATA
DebugLine += FString::Printf(TEXT("\n - Root Motion Delta Attribute Found: %s)"), (bFoundRootMotionAttribute) ? TEXT("true") : TEXT("false"));
#endif
}
if (const UEnum* TypeEnum = FindObject<UEnum>(nullptr, TEXT("/Script/CoreUObject.EAxis")))
{
DebugLine += FString::Printf(TEXT("\n - Rotation Axis: (%s)"), *(TypeEnum->GetNameStringByIndex(static_cast<int32>(RotationAxis))));
}
DebugLine += FString::Printf(TEXT("\n - Rotation Interpolation Speed: (%.3fd)"), RotationInterpSpeed);
}
else
#endif
{
const float ActualOrientationAngleDegrees = FMath::RadiansToDegrees(ActualOrientationAngleRad);
DebugLine += FString::Printf(TEXT("(Orientation Angle: %.3fd)"), ActualOrientationAngleDegrees);
}
DebugData.AddDebugItem(DebugLine);
ComponentPose.GatherDebugData(DebugData);
}
void FGMS_AnimNode_OrientationWarping::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
FAnimNode_SkeletalControlBase::Initialize_AnyThread(Context);
Reset(Context);
//早一点拿到ExternalBoneReference.
if (IsLODEnabled(Context.AnimInstanceProxy))
{
GetEvaluateGraphExposedInputs().Execute(Context);
}
}
void FGMS_AnimNode_OrientationWarping::UpdateInternal(const FAnimationUpdateContext& Context)
{
FAnimNode_SkeletalControlBase::UpdateInternal(Context);
// If we just became relevant and haven't been initialized yet, then reset.
if (!bIsFirstUpdate && UpdateCounter.HasEverBeenUpdated() && !UpdateCounter.WasSynchronizedCounter(Context.AnimInstanceProxy->GetUpdateCounter()))
{
Reset(Context);
}
UpdateCounter.SynchronizeWith(Context.AnimInstanceProxy->GetUpdateCounter());
BlendWeight = Context.GetFinalBlendWeight();
// if (WarpingSpace == EOrientationWarpingSpace::RootBoneTransform)
// {
// if (UE::AnimationWarping::FRootOffsetProvider* RootOffsetProvider = Context.GetMessage<UE::AnimationWarping::FRootOffsetProvider>())
// {
// WarpingSpaceTransform = RootOffsetProvider->GetRootTransform();
// }
// else
// {
// WarpingSpaceTransform = Context.AnimInstanceProxy->GetComponentTransform();
// }
// }
if (WarpingSpace == EOrientationWarpingSpace::ComponentTransform)
{
WarpingSpaceTransform = Context.AnimInstanceProxy->GetComponentTransform();
}
}
void FGMS_AnimNode_OrientationWarping::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
{
SCOPE_CYCLE_COUNTER(STAT_OrientationWarping_Eval);
check(OutBoneTransforms.Num() == 0);
float TargetOrientationAngleRad;
const float DeltaSeconds = Output.AnimInstanceProxy->GetDeltaSeconds();
const float MaxAngleCorrectionRad = FMath::DegreesToRadians(MaxCorrectionDegrees);
const FVector RotationAxisVector = UE::Anim::GetAxisVector(RotationAxis);
FVector LocomotionForward = FVector::ZeroVector;
bool bGraphDrivenWarping = false;
const UE::Anim::IAnimRootMotionProvider* RootMotionProvider = UE::Anim::IAnimRootMotionProvider::Get();
if (Mode == EWarpingEvaluationMode::Graph)
{
bGraphDrivenWarping = !!RootMotionProvider;
ensureMsgf(bGraphDrivenWarping, TEXT("Graph driven Orientation Warping expected a valid root motion delta provider interface."));
}
#if WITH_EDITORONLY_DATA
bFoundRootMotionAttribute = false;
#endif
#if ENABLE_ANIM_DEBUG
FTransform RootMotionTransformDelta = FTransform::Identity;
float RootMotionDeltaAngleRad = 0.0;
const float PreviousOrientationAngleRad = ActualOrientationAngleRad;
#endif
// We will likely need to revisit LocomotionAngle participating as an input to orientation warping.
// Without velocity information from the motion model (such as the capsule), LocomotionAngle isn't enough
// information in isolation for all cases when deciding to warp.
//
// For example imagine that the motion model has stopped moving with zero velocity due to a
// transition into a strafing stop. During that transition we may play an animation with non-zero
// velocity for an arbitrary number of frames. In this scenario the concept of direction is meaningless
// since we cannot orient the animation to match a zero velocity and consequently a zero direction,
// since that would break the pose. For those frames, we would incorrectly over-orient the strafe.
//
// The solution may be instead to pass velocity with the actor base rotation, allowing us to retain
// speed information about the motion. It may also allow us to do more complex orienting behavior
// when multiple degrees of freedom can be considered.
if (WarpingSpace == EOrientationWarpingSpace::ComponentTransform)
{
WarpingSpaceTransform = Output.AnimInstanceProxy->GetComponentTransform();
}
if (bGraphDrivenWarping)
{
#if !ENABLE_ANIM_DEBUG
FTransform RootMotionTransformDelta = FTransform::Identity;
#endif
bGraphDrivenWarping = RootMotionProvider->ExtractRootMotion(Output.CustomAttributes, RootMotionTransformDelta);
// Graph driven orientation warping will modify the incoming root motion to orient towards the intended locomotion angle
if (bGraphDrivenWarping)
{
#if WITH_EDITORONLY_DATA
// Graph driven Orientation Warping expects a root motion delta to be present in the attribute stream.
bFoundRootMotionAttribute = true;
#endif
// In UE, forward is defined as +x; consequently this is also true when sampling an actor's velocity. Historically the skeletal
// mesh component forward will not match the actor, requiring us to correct the rotation before sampling the LocomotionForward.
// In order to make orientation warping 'pure' in the future we will need to provide more context about the intent of
// the actor vs the intent of the animation in their respective spaces. Specifically, we will need some form the following information:
//
// 1. Actor Forward
// 2. Actor Velocity
// 3. Skeletal Mesh Relative Rotation
if (LocomotionDirection.SquaredLength() > UE_SMALL_NUMBER)
{
// if we have a LocomotionDirection vector, transform into root bone local space
LocomotionForward = WarpingSpaceTransform.InverseTransformVector(LocomotionDirection);
LocomotionForward.Normalize();
}
else
{
LocomotionAngle = FRotator::NormalizeAxis(LocomotionAngle);
// UE-184297 Avoid storing LocomotionAngle in radians in case haven't updated the pinned input, to avoid a DegToRad(RadianValue)
const float LocomotionAngleRadians = FMath::DegreesToRadians(LocomotionAngle);
const FQuat LocomotionRotation = FQuat(RotationAxisVector, LocomotionAngleRadians);
const FTransform SkeletalMeshRelativeTransform = Output.AnimInstanceProxy->GetComponentRelativeTransform();
const FQuat SkeletalMeshRelativeRotation = SkeletalMeshRelativeTransform.GetRotation();
LocomotionForward = SkeletalMeshRelativeRotation.UnrotateVector(LocomotionRotation.GetForwardVector()).GetSafeNormal();
}
// Flatten locomotion direction, along the rotation axis.
LocomotionForward = (LocomotionForward - RotationAxisVector.Dot(LocomotionForward) * RotationAxisVector).GetSafeNormal();
// @todo: Graph mode using a "manual value" makes no sense. Restructure logic to address this in the future.
if (bUseManualRootMotionVelocity)
{
RootMotionTransformDelta.SetTranslation(ManualRootMotionVelocity * DeltaSeconds);
}
FVector RootMotionDeltaTranslation = RootMotionTransformDelta.GetTranslation();
// Flatten root motion translation, along the rotation axis.
RootMotionDeltaTranslation = RootMotionDeltaTranslation - RotationAxisVector.Dot(RootMotionDeltaTranslation) * RotationAxisVector;
const float RootMotionDeltaSpeed = RootMotionDeltaTranslation.Size() / DeltaSeconds;
if (RootMotionDeltaSpeed < MinRootMotionSpeedThreshold)
{
// If we're under the threshold, snap orientation angle to 0, and let interpolation handle the delta
TargetOrientationAngleRad = 0.0f;
}
else
{
const FVector PreviousRootMotionDeltaDirection = RootMotionDeltaDirection;
// Hold previous direction if we can't calculate it from current move delta, because the root is no longer moving
RootMotionDeltaDirection = RootMotionDeltaTranslation.GetSafeNormal(UE_SMALL_NUMBER, PreviousRootMotionDeltaDirection);
TargetOrientationAngleRad = UE::Anim::SignedAngleRadBetweenNormals(RootMotionDeltaDirection, LocomotionForward, RotationAxisVector);
// Motion Matching may return an animation that deviates a lot from the movement direction (e.g movement direction going bwd and motion matching could return the fwd animation for a few frames)
// When that happens, since we use the delta between root motion and movement direction, we would be over-rotating the lower body and breaking the pose during those frames
// So, when that happens we use the inverse of the root motion direction to calculate our target rotation.
// This feels a bit 'hacky' but its the only option I've found so far to mitigate the problem
if (LocomotionAngleDeltaThreshold > 0.f)
{
if (FMath::Abs(FMath::RadiansToDegrees(TargetOrientationAngleRad)) > LocomotionAngleDeltaThreshold)
{
TargetOrientationAngleRad = FMath::UnwindRadians(TargetOrientationAngleRad + FMath::DegreesToRadians(180.0f));
RootMotionDeltaDirection = -RootMotionDeltaDirection;
}
}
// Don't compensate interpolation by the root motion angle delta if the previous angle isn't valid.
if (bCounterCompenstateInterpolationByRootMotion && !PreviousRootMotionDeltaDirection.IsNearlyZero(UE_SMALL_NUMBER))
{
#if !ENABLE_ANIM_DEBUG
float RootMotionDeltaAngleRad;
#endif
// Counter the interpolated orientation angle by the root motion direction angle delta.
// This prevents our interpolation from fighting the natural root motion that's flowing through the graph.
RootMotionDeltaAngleRad = UE::Anim::SignedAngleRadBetweenNormals(RootMotionDeltaDirection, PreviousRootMotionDeltaDirection, RotationAxisVector);
// Root motion may have large deltas i.e. bad blends or sudden direction changes like pivots.
// If there's an instantaneous pop in root motion direction, this is likely a pivot.
const float MaxRootMotionDeltaToCompensateRad = FMath::DegreesToRadians(MaxRootMotionDeltaToCompensateDegrees);
if (FMath::Abs(RootMotionDeltaAngleRad) < MaxRootMotionDeltaToCompensateRad)
{
ActualOrientationAngleRad = FMath::UnwindRadians(ActualOrientationAngleRad + RootMotionDeltaAngleRad);
}
}
// Rotate the root motion delta fully by the warped angle
const FVector WarpedRootMotionTranslationDelta = FQuat(RotationAxisVector, TargetOrientationAngleRad).RotateVector(RootMotionDeltaTranslation);
RootMotionTransformDelta.SetTranslation(WarpedRootMotionTranslationDelta);
}
// Forward the side effects of orientation warping on the root motion contribution for this sub-graph
const bool bRootMotionOverridden = RootMotionProvider->OverrideRootMotion(RootMotionTransformDelta, Output.CustomAttributes);
ensureMsgf(bRootMotionOverridden, TEXT("Graph driven Orientation Warping expected a root motion delta to be present in the attribute stream prior to warping/overriding it."));
}
else
{
// Early exit on missing root motion delta attribute
return;
}
}
else
{
// Manual orientation warping will take the angle directly
TargetOrientationAngleRad = FRotator::NormalizeAxis(OrientationAngle);
TargetOrientationAngleRad = FMath::DegreesToRadians(TargetOrientationAngleRad);
}
// Optionally interpolate the effective orientation towards the target orientation angle
// When the orientation warping node becomes relevant, the input pose orientation may not be aligned with the desired orientation.
// Instead of interpolating this difference, snap to the desired orientation if it's our first update to minimize corrections over-time.
if ((RotationInterpSpeed > 0.f) && !bIsFirstUpdate)
{
const float SmoothOrientationAngleRad = FMath::FInterpTo(ActualOrientationAngleRad, TargetOrientationAngleRad, DeltaSeconds, RotationInterpSpeed);
// Limit our interpolation rate to prevent pops.
// @TODO: Use better, more physically accurate interpolation here.
ActualOrientationAngleRad = FMath::Clamp(SmoothOrientationAngleRad, ActualOrientationAngleRad - MaxAngleCorrectionRad, ActualOrientationAngleRad + MaxAngleCorrectionRad);
}
else
{
ActualOrientationAngleRad = TargetOrientationAngleRad;
}
ActualOrientationAngleRad = FMath::Clamp(ActualOrientationAngleRad, -MaxAngleCorrectionRad, MaxAngleCorrectionRad);
// Allow the alpha value of the node to affect the final rotation
ActualOrientationAngleRad *= ActualAlpha;
if (bScaleByGlobalBlendWeight)
{
ActualOrientationAngleRad *= BlendWeight;
}
#if ENABLE_ANIM_DEBUG
bool bDebugging = false;
#if WITH_EDITORONLY_DATA
bDebugging = bDebugging || bEnableDebugDraw;
#else
constexpr float DebugDrawScale = 1.f;
#endif
const int32 DebugIndex = CVarAnimNodeOrientationWarpingDebug.GetValueOnAnyThread();
bDebugging = bDebugging || (DebugIndex > 0);
if (bDebugging)
{
const FTransform ComponentTransform = Output.AnimInstanceProxy->GetComponentTransform();
const FVector ActorForwardDirection = Output.AnimInstanceProxy->GetActorTransform().GetRotation().GetForwardVector();
FVector DebugArrowOffset = FVector::ZAxisVector * DebugDrawScale;
// Draw debug shapes
{
const FVector ForwardDirection = bGraphDrivenWarping
? ComponentTransform.GetRotation().RotateVector(LocomotionForward)
: ActorForwardDirection;
Output.AnimInstanceProxy->AnimDrawDebugDirectionalArrow(
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + ForwardDirection * 100.f * DebugDrawScale,
40.f * DebugDrawScale, FColor::Red, false, 0.f, 2.f * DebugDrawScale);
const FVector RotationDirection = bGraphDrivenWarping
? ComponentTransform.GetRotation().RotateVector(RootMotionDeltaDirection)
: ActorForwardDirection.RotateAngleAxis(OrientationAngle, RotationAxisVector);
DebugArrowOffset += FVector::ZAxisVector * DebugDrawScale;
Output.AnimInstanceProxy->AnimDrawDebugDirectionalArrow(
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + RotationDirection * 100.f * DebugDrawScale,
40.f * DebugDrawScale, FColor::Blue, false, 0.f, 2.f * DebugDrawScale);
const float ActualOrientationAngleDegrees = FMath::RadiansToDegrees(ActualOrientationAngleRad);
const FVector WarpedRotationDirection = bGraphDrivenWarping
? RotationDirection.RotateAngleAxis(ActualOrientationAngleDegrees, RotationAxisVector)
: ActorForwardDirection.RotateAngleAxis(ActualOrientationAngleDegrees, RotationAxisVector);
DebugArrowOffset += FVector::ZAxisVector * DebugDrawScale;
Output.AnimInstanceProxy->AnimDrawDebugDirectionalArrow(
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + WarpedRotationDirection * 100.f * DebugDrawScale,
40.f * DebugDrawScale, FColor::Green, false, 0.f, 2.f * DebugDrawScale);
}
// Draw text on mesh in world space
{
TStringBuilder<1024> DebugLine;
const float PreviousOrientationAngleDegrees = FMath::RadiansToDegrees(PreviousOrientationAngleRad);
const float ActualOrientationAngleDegrees = FMath::RadiansToDegrees(ActualOrientationAngleRad);
const float TargetOrientationAngleDegrees = FMath::RadiansToDegrees(TargetOrientationAngleRad);
if (Mode == EWarpingEvaluationMode::Manual)
{
DebugLine.Appendf(TEXT("\n - Previous Orientation Angle: (%.3fd)"), PreviousOrientationAngleDegrees);
DebugLine.Appendf(TEXT("\n - Orientation Angle: (%.3fd)"), ActualOrientationAngleDegrees);
DebugLine.Appendf(TEXT("\n - Target Orientation Angle: (%.3fd)"), TargetOrientationAngleRad);
}
else
{
if (RotationInterpSpeed > 0.0f)
{
DebugLine.Appendf(TEXT("\n - Previous Orientation Angle: (%.3fd)"), FMath::RadiansToDegrees(PreviousOrientationAngleRad));
DebugLine.Appendf(TEXT("\n - Root Motion Frame Delta Angle: (%.3fd)"), FMath::RadiansToDegrees(RootMotionDeltaAngleRad));
}
DebugLine.Appendf(TEXT("\n - Actual Orientation Angle: (%.3fd)"), FMath::RadiansToDegrees(ActualOrientationAngleRad));
DebugLine.Appendf(TEXT("\n - Target Orientation Angle: (%.3fd)"), FMath::RadiansToDegrees(TargetOrientationAngleRad));
// Locomotion angle is already in degrees.
DebugLine.Appendf(TEXT("\n - Locomotion Angle: (%.3fd)"), LocomotionAngle);
DebugLine.Appendf(TEXT("\n - Root Motion Delta: %s)"), *RootMotionTransformDelta.GetTranslation().ToString());
DebugLine.Appendf(TEXT("\n - Root Motion Speed: %.3fd)"), RootMotionTransformDelta.GetTranslation().Size() / DeltaSeconds);
}
Output.AnimInstanceProxy->AnimDrawDebugInWorldMessage(DebugLine.ToString(), FVector::UpVector * 50.0f, FColor::Yellow, 1.f /*TextScale*/);
}
}
#endif
#if ANIM_TRACE_ENABLED
{
const float PreviousOrientationAngleDegrees = FMath::RadiansToDegrees(PreviousOrientationAngleRad);
const float ActualOrientationAngleDegrees = FMath::RadiansToDegrees(ActualOrientationAngleRad);
const float TargetOrientationAngleDegrees = FMath::RadiansToDegrees(TargetOrientationAngleRad);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Previous OrientationAngle Degrees"), PreviousOrientationAngleDegrees);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Actual Orientation Angle Degrees"), ActualOrientationAngleDegrees);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Target Orientation Angle Degrees"), TargetOrientationAngleDegrees);
if (Mode == EWarpingEvaluationMode::Graph)
{
const float RootMotionDeltaAngleDegrees = FMath::RadiansToDegrees(RootMotionDeltaAngleRad);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Root Motion Delta Angle Degrees"), RootMotionDeltaAngleDegrees);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Locomotion Angle"), LocomotionAngle);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Root Motion Translation Delta"), RootMotionTransformDelta.GetTranslation());
const float RootMotionSpeed = RootMotionTransformDelta.GetTranslation().Size() / DeltaSeconds;
TRACE_ANIM_NODE_VALUE(Output, TEXT("Root Motion Speed"), RootMotionSpeed);
}
}
#endif
#if ENABLE_VISUAL_LOG
if (FVisualLogger::IsRecording())
{
const FTransform ComponentTransform = Output.AnimInstanceProxy->GetComponentTransform();
const FVector ActorForwardDirection = Output.AnimInstanceProxy->GetActorTransform().GetRotation().GetForwardVector();
FVector DebugArrowOffset = FVector::ZAxisVector * DebugDrawScale;
// Draw debug shapes
{
const FVector ForwardDirection = bGraphDrivenWarping
? ComponentTransform.GetRotation().RotateVector(LocomotionForward)
: ActorForwardDirection;
UE_VLOG_ARROW(Output.AnimInstanceProxy->GetAnimInstanceObject(), "OrientationWarping", Display,
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + ForwardDirection * 100.f * DebugDrawScale,
FColor::Red, TEXT(""));
const FVector RotationDirection = bGraphDrivenWarping
? ComponentTransform.GetRotation().RotateVector(RootMotionDeltaDirection)
: ActorForwardDirection.RotateAngleAxis(OrientationAngle, RotationAxisVector);
DebugArrowOffset += FVector::ZAxisVector * DebugDrawScale;
UE_VLOG_ARROW(Output.AnimInstanceProxy->GetAnimInstanceObject(), "OrientationWarping", Display,
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + RotationDirection * 100.f * DebugDrawScale,
FColor::Blue, TEXT(""));
const float ActualOrientationAngleDegrees = FMath::RadiansToDegrees(ActualOrientationAngleRad);
const FVector WarpedRotationDirection = bGraphDrivenWarping
? RotationDirection.RotateAngleAxis(ActualOrientationAngleDegrees, RotationAxisVector)
: ActorForwardDirection.RotateAngleAxis(ActualOrientationAngleDegrees, RotationAxisVector);
DebugArrowOffset += FVector::ZAxisVector * DebugDrawScale;
UE_VLOG_ARROW(Output.AnimInstanceProxy->GetAnimInstanceObject(), "OrientationWarping", Display,
ComponentTransform.GetLocation() + DebugArrowOffset,
ComponentTransform.GetLocation() + DebugArrowOffset + WarpedRotationDirection * 100.f * DebugDrawScale,
FColor::Green, TEXT(""));
}
}
#endif
const float RootOffset = FMath::UnwindRadians(ActualOrientationAngleRad * DistributedBoneOrientationAlpha);
// Rotate Root Bone first, as that cheaply rotates the whole pose with one transformation.
if (!FMath::IsNearlyZero(RootOffset, KINDA_SMALL_NUMBER))
{
const FQuat RootRotation = FQuat(RotationAxisVector, RootOffset);
const FCompactPoseBoneIndex RootBoneIndex(0);
FTransform RootBoneTransform(Output.Pose.GetComponentSpaceTransform(RootBoneIndex));
RootBoneTransform.SetRotation(RootRotation * RootBoneTransform.GetRotation());
RootBoneTransform.NormalizeRotation();
Output.Pose.SetComponentSpaceTransform(RootBoneIndex, RootBoneTransform);
}
const int32 NumSpineBones = SpineBoneDataArray.Num();
const bool bSpineOrientationAlpha = !FMath::IsNearlyZero(DistributedBoneOrientationAlpha, KINDA_SMALL_NUMBER);
const bool bUpdateSpineBones = (NumSpineBones > 0) && bSpineOrientationAlpha;
if (bUpdateSpineBones)
{
// Spine bones counter rotate body orientation evenly across all bones.
for (int32 ArrayIndex = 0; ArrayIndex < NumSpineBones; ArrayIndex++)
{
const FOrientationWarpingSpineBoneData& BoneData = SpineBoneDataArray[ArrayIndex];
const FQuat SpineBoneCounterRotation = FQuat(RotationAxisVector, -ActualOrientationAngleRad * DistributedBoneOrientationAlpha * BoneData.Weight);
check(BoneData.Weight > 0.f);
FTransform SpineBoneTransform(Output.Pose.GetComponentSpaceTransform(BoneData.BoneIndex));
SpineBoneTransform.SetRotation((SpineBoneCounterRotation * SpineBoneTransform.GetRotation()));
SpineBoneTransform.NormalizeRotation();
Output.Pose.SetComponentSpaceTransform(BoneData.BoneIndex, SpineBoneTransform);
}
}
const float IKFootRootOrientationAlpha = 1.f - DistributedBoneOrientationAlpha;
const bool bUpdateIKFootRoot = (IKFootData.IKFootRootBoneIndex != FCompactPoseBoneIndex(INDEX_NONE)) && !FMath::IsNearlyZero(IKFootRootOrientationAlpha, KINDA_SMALL_NUMBER);
// Rotate IK Foot Root
if (bUpdateIKFootRoot)
{
const FQuat BoneRotation = FQuat(RotationAxisVector, ActualOrientationAngleRad * IKFootRootOrientationAlpha);
FTransform IKFootRootTransform(Output.Pose.GetComponentSpaceTransform(IKFootData.IKFootRootBoneIndex));
IKFootRootTransform.SetRotation(BoneRotation * IKFootRootTransform.GetRotation());
IKFootRootTransform.NormalizeRotation();
Output.Pose.SetComponentSpaceTransform(IKFootData.IKFootRootBoneIndex, IKFootRootTransform);
// IK Feet
// These match the root orientation, so don't rotate them. Just preserve root rotation.
// We need to update their translation though, since we rotated their parent (the IK Foot Root bone).
const int32 NumIKFootBones = IKFootData.IKFootBoneIndexArray.Num();
const bool bUpdateIKFootBones = bUpdateIKFootRoot && (NumIKFootBones > 0);
if (bUpdateIKFootBones)
{
const FQuat IKFootRotation = FQuat(RotationAxisVector, -ActualOrientationAngleRad * IKFootRootOrientationAlpha);
for (int32 ArrayIndex = 0; ArrayIndex < NumIKFootBones; ArrayIndex++)
{
const FCompactPoseBoneIndex& IKFootBoneIndex = IKFootData.IKFootBoneIndexArray[ArrayIndex];
FTransform IKFootBoneTransform(Output.Pose.GetComponentSpaceTransform(IKFootBoneIndex));
IKFootBoneTransform.SetRotation(IKFootRotation * IKFootBoneTransform.GetRotation());
IKFootBoneTransform.NormalizeRotation();
Output.Pose.SetComponentSpaceTransform(IKFootBoneIndex, IKFootBoneTransform);
}
}
}
OutBoneTransforms.Sort(FCompareBoneTransformIndex());
bIsFirstUpdate = false;
}
bool FGMS_AnimNode_OrientationWarping::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones)
{
#if ENABLE_ANIM_DEBUG
if (CVarAnimNodeOrientationWarpingEnable.GetValueOnAnyThread() == 0)
{
return false;
}
#endif
if (RotationAxis == EAxis::None)
{
return false;
}
if (Mode == EWarpingEvaluationMode::Manual && UE::Anim::IsInvalidWarpingAngleDegrees(OrientationAngle, KINDA_SMALL_NUMBER))
{
return false;
}
if (SpineBoneDataArray.IsEmpty())
{
return false;
}
else
{
for (const auto& Spine : SpineBoneDataArray)
{
if (Spine.BoneIndex == INDEX_NONE)
{
return false;
}
}
}
if (IKFootData.IKFootRootBoneIndex == INDEX_NONE)
{
return false;
}
if (IKFootData.IKFootBoneIndexArray.IsEmpty())
{
return false;
}
else
{
for (const auto& IKFootBoneIndex : IKFootData.IKFootBoneIndexArray)
{
if (IKFootBoneIndex == INDEX_NONE)
{
return false;
}
}
}
return true;
}
void FGMS_AnimNode_OrientationWarping::InitializeBoneReferences(const FBoneContainer& RequiredBones)
{
ExternalBoneReference.IKFootRootBone.Initialize(RequiredBones);
IKFootData.IKFootRootBoneIndex = ExternalBoneReference.IKFootRootBone.GetCompactPoseIndex(RequiredBones);
IKFootData.IKFootBoneIndexArray.Reset();
for (auto& Bone : ExternalBoneReference.IKFootBones)
{
Bone.Initialize(RequiredBones);
IKFootData.IKFootBoneIndexArray.Add(Bone.GetCompactPoseIndex(RequiredBones));
}
SpineBoneDataArray.Reset();
for (auto& Bone : ExternalBoneReference.SpineBones)
{
Bone.Initialize(RequiredBones);
SpineBoneDataArray.Add(FOrientationWarpingSpineBoneData(Bone.GetCompactPoseIndex(RequiredBones)));
}
if (SpineBoneDataArray.Num() > 0)
{
// Sort bones indices so we can transform parent before child
SpineBoneDataArray.Sort(FOrientationWarpingSpineBoneData::FCompareBoneIndex());
// Assign Weights.
TArray<int32, TInlineAllocator<20>> IndicesToUpdate;
for (int32 Index = SpineBoneDataArray.Num() - 1; Index >= 0; Index--)
{
// If this bone's weight hasn't been updated, scan its parents.
// If parents have weight, we add it to 'ExistingWeight'.
// split (1.f - 'ExistingWeight') between all members of the chain that have no weight yet.
if (SpineBoneDataArray[Index].Weight == 0.f)
{
IndicesToUpdate.Reset(SpineBoneDataArray.Num());
float ExistingWeight = 0.f;
IndicesToUpdate.Add(Index);
const FCompactPoseBoneIndex CompactBoneIndex = SpineBoneDataArray[Index].BoneIndex;
for (int32 ParentIndex = Index - 1; ParentIndex >= 0; ParentIndex--)
{
if (RequiredBones.BoneIsChildOf(CompactBoneIndex, SpineBoneDataArray[ParentIndex].BoneIndex))
{
if (SpineBoneDataArray[ParentIndex].Weight > 0.f)
{
ExistingWeight += SpineBoneDataArray[ParentIndex].Weight;
}
else
{
IndicesToUpdate.Add(ParentIndex);
}
}
}
check(IndicesToUpdate.Num() > 0);
const float WeightToShare = 1.f - ExistingWeight;
const float IndividualWeight = WeightToShare / float(IndicesToUpdate.Num());
for (int32 UpdateListIndex = 0; UpdateListIndex < IndicesToUpdate.Num(); UpdateListIndex++)
{
SpineBoneDataArray[IndicesToUpdate[UpdateListIndex]].Weight = IndividualWeight;
}
}
}
}
}
void FGMS_AnimNode_OrientationWarping::Reset(const FAnimationBaseContext& Context)
{
bIsFirstUpdate = true;
RootMotionDeltaDirection = FVector::ZeroVector;
ManualRootMotionVelocity = FVector::ZeroVector;
ActualOrientationAngleRad = 0.f;
}

View File

@@ -0,0 +1,6 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Settings/GMS_SettingEnumLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_SettingEnumLibrary)

View File

@@ -0,0 +1,277 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Settings/GMS_SettingObjectLibrary.h"
#include "Locomotions/GMS_AnimLayer.h"
#include "Animation/BlendSpace.h"
#include "Locomotions/GMS_AnimLayer_Additive.h"
#include "Locomotions/GMS_AnimLayer_Overlay.h"
#include "Locomotions/GMS_AnimLayer_SkeletalControls.h"
#include "Locomotions/GMS_AnimLayer_States.h"
#include "Locomotions/GMS_AnimLayer_View_Default.h"
#include "Misc/DataValidation.h"
#include "Utility/GMS_Utility.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_SettingObjectLibrary)
#pragma region CommonSettings
#if WITH_EDITOR
#include "UObject/ObjectSaveContext.h"
void UGMS_MovementDefinition::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
}
EDataValidationResult UGMS_MovementDefinition::IsDataValid(class FDataValidationContext& Context) const
{
for (const TTuple<FGameplayTag, FGMS_MovementSetSetting>& Pair : MovementSets)
{
if (Pair.Value.ControlSetting == nullptr)
{
Context.AddError(FText::FromString(FString::Format(TEXT("ControlSetting is required on {0}!!!"), {Pair.Key.GetTagName().ToString()})));
}
if (Pair.Value.bUseInstancedStatesSetting && Pair.Value.AnimLayerSetting_States && Pair.Value.AnimLayerSetting_States->IsDataValid(Context) == EDataValidationResult::Invalid)
{
return EDataValidationResult::Invalid;
}
if (Pair.Value.bUseInstancedOverlaySetting && Pair.Value.AnimLayerSetting_Overlay && Pair.Value.AnimLayerSetting_Overlay->IsDataValid(Context) == EDataValidationResult::Invalid)
{
return EDataValidationResult::Invalid;
}
if (Pair.Value.AnimLayerSetting_Additive && Pair.Value.AnimLayerSetting_Additive->IsDataValid(Context) == EDataValidationResult::Invalid)
{
return EDataValidationResult::Invalid;
}
if (Pair.Value.AnimLayerSetting_View && Pair.Value.AnimLayerSetting_View->IsDataValid(Context) == EDataValidationResult::Invalid)
{
return EDataValidationResult::Invalid;
}
if (Pair.Value.AnimLayerSetting_SkeletalControls && Pair.Value.AnimLayerSetting_SkeletalControls->IsDataValid(Context) == EDataValidationResult::Invalid)
{
return EDataValidationResult::Invalid;
}
}
return Super::IsDataValid(Context);
}
#endif
FGameplayTag UGMS_MovementControlSetting_Default::MatchStateTagBySpeed(float Speed, float Threshold) const
{
for (const FGMS_MovementStateSetting& MovementState : MovementStates)
{
if (MovementState.Speed > 0.0f && MovementState.Speed < Speed + Threshold)
{
return MovementState.Tag;
}
}
return FGameplayTag::EmptyTag;
}
bool UGMS_MovementControlSetting_Default::GetStateByIndex(const int32& Index, FGMS_MovementStateSetting& OutSetting) const
{
if (MovementStates.IsValidIndex(Index))
{
OutSetting = MovementStates[Index];
return true;
}
return false;
}
bool UGMS_MovementControlSetting_Default::GetStateBySpeedLevel(const int32& Level, FGMS_MovementStateSetting& OutSetting) const
{
if (SpeedLevelToArrayIndex.Contains(Level))
{
OutSetting = MovementStates[SpeedLevelToArrayIndex[Level]];
return true;
}
return false;
}
bool UGMS_MovementControlSetting_Default::GetStateByTag(const FGameplayTag& Tag, FGMS_MovementStateSetting& OutSetting) const
{
if (auto Setting = GetMovementStateSetting(Tag))
{
OutSetting = *Setting;
return true;
}
return false;
}
const FGMS_MovementStateSetting* UGMS_MovementControlSetting_Default::GetMovementStateSetting(const FGameplayTag& Tag) const
{
if (!Tag.IsValid())
{
return nullptr;
}
return MovementStates.FindByPredicate([Tag](const FGMS_MovementStateSetting& Setting)
{
return Setting.Tag.IsValid() && Setting.Tag == Tag;
});
}
const FGMS_MovementStateSetting* UGMS_MovementControlSetting_Default::GetMovementStateSetting(const FGameplayTag& Tag, bool bHasFallback) const
{
if (auto Setting = GetMovementStateSetting(Tag))
{
return Setting;
}
if (bHasFallback)
{
checkf(!MovementStates.IsEmpty(), TEXT("%s: MovementStates can't be empty!"), *GetNameSafe(this))
return &MovementStates.Last();
}
return nullptr;
}
#pragma endregion
#pragma region ControlSettings
#if WITH_EDITOR
float UGMS_MovementControlSetting_Default::MigrateRotationInterpolationSpeed(float Old)
{
if (Old <= 0.0f)
{
return 0.0f;
}
// larger old value, smaller new value.
return FMath::GetMappedRangeValueClamped(FVector2f{0.0f, 20.0f}, FVector2f{0.3f, 0.1f}, Old);
}
void UGMS_MovementControlSetting_Default::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
SpeedLevelToArrayIndex.Empty();
MovementStates.Sort([](const FGMS_MovementStateSetting& A, const FGMS_MovementStateSetting& B)
{
return A.SpeedLevel < B.SpeedLevel;
});
for (int i = 0; i < MovementStates.Num(); ++i)
{
FGMS_MovementStateSetting& Setting = MovementStates[i];
Setting.EditorFriendlyName = FString::Format(TEXT("State({0}) SpeedLevel({1}) Speed({2})"), {UGMS_Utility::GetSimpleTagName(Setting.Tag).ToString(), Setting.SpeedLevel, Setting.Speed});
SpeedLevelToArrayIndex.Emplace(Setting.SpeedLevel, i);
}
// Migration code for GMS1.5, TODO remove in 1.6
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (MovementStates.Num() > 0)
{
const FGMS_MovementStateSetting& LastMovementState = MovementStates[MovementStates.Num() - 1];
// migrate velocity direction setting.
if (!VelocityDirectionSetting.IsValid())
{
if (LastMovementState.VelocityDirectionSetting.DirectionMode != EGMS_VelocityDirectionMode_DEPRECATED::TurningCircle)
{
FGMS_VelocityDirectionSetting_Default Temp;
Temp.bEnableRotationWhenNotMoving = LastMovementState.VelocityDirectionSetting.bEnableRotationWhenNotMoving;
Temp.TargetYawAngleRotationSpeed = LastMovementState.TargetYawAngleRotationSpeed;
Temp.RotationInterpolationSpeed = MigrateRotationInterpolationSpeed(LastMovementState.RotationInterpolationSpeed);
Temp.bOrientateToMoveInputIntent = LastMovementState.VelocityDirectionSetting.DirectionMode == EGMS_VelocityDirectionMode_DEPRECATED::OrientToInputDirection;
VelocityDirectionSetting.InitializeAs<FGMS_VelocityDirectionSetting_Default>(Temp);
}
else if (LastMovementState.VelocityDirectionSetting.DirectionMode == EGMS_VelocityDirectionMode_DEPRECATED::TurningCircle)
{
FGMS_VelocityDirectionSetting_RateBased Temp;
Temp.bEnableRotationWhenNotMoving = LastMovementState.VelocityDirectionSetting.bEnableRotationWhenNotMoving;
Temp.TurnRate = LastMovementState.VelocityDirectionSetting.TurningRate;
VelocityDirectionSetting.InitializeAs<FGMS_VelocityDirectionSetting_RateBased>(Temp);
}
}
// migrate view direction setting.
if (!ViewDirectionSetting.IsValid())
{
if (LastMovementState.ViewDirectionSetting.DirectionMode == EGMS_ViewDirectionMode_DEPRECATED::Aiming)
{
FGMS_ViewDirectionSetting_Aiming Temp;
Temp.bEnableRotationWhenNotMoving = LastMovementState.ViewDirectionSetting.bRotateToViewDirectionWhileNotMoving;
Temp.TargetYawAngleRotationSpeed = LastMovementState.TargetYawAngleRotationSpeed;
Temp.RotationInterpolationSpeed = MigrateRotationInterpolationSpeed(LastMovementState.RotationInterpolationSpeed);
Temp.MinAimingYawAngleLimit = LastMovementState.ViewDirectionSetting.MinAimingYawAngleLimit;
ViewDirectionSetting.InitializeAs<FGMS_ViewDirectionSetting_Aiming>(Temp);
}
else if (LastMovementState.ViewDirectionSetting.DirectionMode == EGMS_ViewDirectionMode_DEPRECATED::Default)
{
FGMS_ViewDirectionSetting_Default Temp;
Temp.bEnableRotationWhenNotMoving = LastMovementState.ViewDirectionSetting.bRotateToViewDirectionWhileNotMoving;
Temp.TargetYawAngleRotationSpeed = LastMovementState.TargetYawAngleRotationSpeed;
Temp.RotationInterpolationSpeed = MigrateRotationInterpolationSpeed(LastMovementState.RotationInterpolationSpeed);
ViewDirectionSetting.InitializeAs<FGMS_ViewDirectionSetting_Default>(Temp);
}
}
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// Safety guard if still invalid.
if (!VelocityDirectionSetting.IsValid())
{
VelocityDirectionSetting.InitializeAs(FGMS_VelocityDirectionSetting_Default());
}
if (!ViewDirectionSetting.IsValid())
{
ViewDirectionSetting.InitializeAs(FGMS_ViewDirectionSetting_Default());
}
}
EDataValidationResult UGMS_MovementControlSetting_Default::IsDataValid(class FDataValidationContext& Context) const
{
for (int32 i = 0; i < MovementStates.Num(); i++)
{
const FGMS_MovementStateSetting& MRSetting = MovementStates[i];
if (!MRSetting.Tag.IsValid())
{
Context.AddError(FText::FromString(FString::Format(TEXT("Invalid tag at index({0}) of MovementStates"), {i})));
return EDataValidationResult::Invalid;
}
if (MRSetting.AllowedRotationModes.IsEmpty())
{
Context.AddError(FText::FromString(
FString::Format(TEXT("AllowedRotationModes at index({0}) of MovementStates can't be empty!"), {i})));
return EDataValidationResult::Invalid;
}
}
if (!ViewDirectionSetting.IsValid())
{
Context.AddError(FText::FromString(TEXT("Invalid view direction setting")));
return EDataValidationResult::Invalid;
}
if (ViewDirectionSetting.IsValid() && ViewDirectionSetting.GetScriptStruct() == FGMS_ViewDirectionSetting::StaticStruct())
{
Context.AddError(FText::FromString(FString::Format(TEXT("View direction setting({0}) was deprecated!"), {FGMS_ViewDirectionSetting::StaticStruct()->GetName()})));
return EDataValidationResult::Invalid;
}
if (!VelocityDirectionSetting.IsValid())
{
Context.AddError(FText::FromString(TEXT("Invalid velocity direction setting")));
return EDataValidationResult::Invalid;
}
if (VelocityDirectionSetting.IsValid() && VelocityDirectionSetting.GetScriptStruct() == FGMS_VelocityDirectionSetting::StaticStruct())
{
Context.AddError(FText::FromString(FString::Format(TEXT("Velocity direction setting({0}) was deprecated!"), {FGMS_VelocityDirectionSetting::StaticStruct()->GetName()})));
return EDataValidationResult::Invalid;
}
return Super::IsDataValid(Context);
}
#endif
#pragma endregion

View File

@@ -0,0 +1,27 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Settings/GMS_SettingStructLibrary.h"
#include "Settings/GMS_SettingObjectLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_SettingStructLibrary)
#if WITH_EDITOR
void FGMS_AnimDataSetting_General::PostEditChangeProperty(const FPropertyChangedEvent& PropertyChangedEvent)
{
if (PropertyChangedEvent.GetPropertyName() != GET_MEMBER_NAME_CHECKED(FGMS_AnimDataSetting_General, GroundPredictionResponseChannels))
{
return;
}
GroundPredictionSweepResponses.SetAllChannels(ECR_Ignore);
for (const auto CollisionChannel : GroundPredictionResponseChannels)
{
GroundPredictionSweepResponses.SetResponse(CollisionChannel, ECR_Block);
}
}
#endif

View File

@@ -0,0 +1,48 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utility/GMS_Log.h"
#include "GameFramework/Actor.h"
#include "Components/ActorComponent.h"
#include "Animation/AnimInstance.h"
const FName GMSLog::MessageLogName{TEXTVIEW("GMS")};
DEFINE_LOG_CATEGORY(LogGMS)
DEFINE_LOG_CATEGORY(LogGMS_Animation)
FString GetGMSLogContextString(const UObject* ContextObject)
{
ENetRole Role = ROLE_None;
FString RoleName = TEXT("None");
FString Name = "None";
if (const AActor* Actor = Cast<AActor>(ContextObject))
{
Role = Actor->GetLocalRole();
Name = Actor->GetName();
}
else if (const UActorComponent* Component = Cast<UActorComponent>(ContextObject))
{
Role = Component->GetOwnerRole();
Name = Component->GetOwner()->GetName();
}
else if (const UAnimInstance* AnimInstance = Cast<UAnimInstance>(ContextObject))
{
if (AnimInstance->GetOwningActor())
{
Role = AnimInstance->GetOwningActor()->GetLocalRole();
Name = AnimInstance->GetOwningActor()->GetName();
}
}
else if (IsValid(ContextObject))
{
Name = ContextObject->GetName();
}
if (Role != ROLE_None)
{
RoleName = (Role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
}
return FString::Printf(TEXT("[%s] (%s)"), *RoleName, *Name);
}

View File

@@ -0,0 +1,8 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utility/GMS_Math.h"
#include "Locomotions/GMS_LocomotionEnumLibrary.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_Math)

View File

@@ -0,0 +1,47 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utility/GMS_Tags.h"
namespace GMS_MovementModeTags
{
UE_DEFINE_GAMEPLAY_TAG(None, FName{TEXTVIEW("GMS.LocomotionMode.None")})
UE_DEFINE_GAMEPLAY_TAG(Grounded, FName{TEXTVIEW("GMS.LocomotionMode.Grounded")})
UE_DEFINE_GAMEPLAY_TAG(InAir, FName{TEXTVIEW("GMS.LocomotionMode.InAir")})
UE_DEFINE_GAMEPLAY_TAG(Flying, FName{TEXTVIEW("GMS.LocomotionMode.Flying")})
UE_DEFINE_GAMEPLAY_TAG(Swimming, FName{TEXTVIEW("GMS.LocomotionMode.Swimming")})
UE_DEFINE_GAMEPLAY_TAG(Zipline, FName{TEXTVIEW("GMS.LocomotionMode.Zipline")})
}
namespace GMS_RotationModeTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(VelocityDirection, FName{TEXTVIEW("GMS.RotationMode.VelocityDirection")}, "Rotate(orientate) to given velocity direction.(转向指定的的速率方向)")
UE_DEFINE_GAMEPLAY_TAG_COMMENT(ViewDirection, FName{TEXTVIEW("GMS.RotationMode.ViewDirection")}, "Rotate(orientate) to view direction(转向看的方向)")
}
namespace GMS_MovementStateTags
{
UE_DEFINE_GAMEPLAY_TAG(Walk, FName{TEXTVIEW("GMS.MovementState.Walk")})
UE_DEFINE_GAMEPLAY_TAG(Jog, FName{TEXTVIEW("GMS.MovementState.Jog")})
UE_DEFINE_GAMEPLAY_TAG(Sprint, FName{TEXTVIEW("GMS.MovementState.Sprint")})
}
namespace GMS_OverlayModeTags
{
UE_DEFINE_GAMEPLAY_TAG(None, FName{TEXTVIEW("GMS.OverlayMode.None")})
UE_DEFINE_GAMEPLAY_TAG(Default, FName{TEXTVIEW("GMS.OverlayMode.Default")})
}
namespace GMS_SMTags
{
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Root, FName{TEXTVIEW("GMS.SM")}, "State Machine Root Tag");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(InAir, FName{TEXTVIEW("GMS.SM.InAir")}, "InAir States")
UE_DEFINE_GAMEPLAY_TAG(InAir_Jump, FName{TEXTVIEW("GMS.SM.InAir.Jump")})
UE_DEFINE_GAMEPLAY_TAG(InAir_Fall, FName{TEXTVIEW("GMS.SM.InAir.Fall")})
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Grounded, FName{TEXTVIEW("GMS.SM.Grounded")}, "Grounded States")
UE_DEFINE_GAMEPLAY_TAG(Grounded_Idle, FName{TEXTVIEW("GMS.SM.Grounded.Idle")})
UE_DEFINE_GAMEPLAY_TAG(Grounded_Start, FName{TEXTVIEW("GMS.SM.Grounded.Start")})
UE_DEFINE_GAMEPLAY_TAG(Grounded_Cycle, FName{TEXTVIEW("GMS.SM.Grounded.Cycle")})
UE_DEFINE_GAMEPLAY_TAG(Grounded_Stop, FName{TEXTVIEW("GMS.SM.Grounded.Stop")})
UE_DEFINE_GAMEPLAY_TAG(Grounded_Pivot, FName{TEXTVIEW("GMS.SM.Grounded.Pivot")})
UE_DEFINE_GAMEPLAY_TAG(Grounded_Land, FName{TEXTVIEW("GMS.SM.Grounded.Land")})
}

View File

@@ -0,0 +1,237 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utility/GMS_Utility.h"
#include "Chooser.h"
#include "ChooserPropertyAccess.h"
#include "PoseSearch/PoseSearchDatabase.h"
#include "GameplayTagsManager.h"
#include "IObjectChooser.h"
#include "Animation/AnimInstance.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/World.h"
#include "GameFramework/Character.h"
#include "GameFramework/HUD.h"
#include "Animation/AnimSequenceBase.h"
#include "Locomotions/GMS_AnimLayer.h"
#include "Locomotions/GMS_MainAnimInstance.h"
#include "Settings/GMS_SettingObjectLibrary.h"
#include "Utility/GMS_Log.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_Utility)
FString UGMS_Utility::NameToDisplayString(const FName& Name, const bool bNameIsBool)
{
return FName::NameToDisplayString(Name.ToString(), bNameIsBool);
}
float UGMS_Utility::GetAnimationCurveValueFromCharacter(const ACharacter* Character, const FName& CurveName)
{
const auto* Mesh{IsValid(Character) ? Character->GetMesh() : nullptr};
const auto* AnimationInstance{IsValid(Mesh) ? Mesh->GetAnimInstance() : nullptr};
return IsValid(AnimationInstance) ? AnimationInstance->GetCurveValue(CurveName) : 0.0f;
}
FGameplayTagContainer UGMS_Utility::GetChildTags(const FGameplayTag& Tag)
{
return UGameplayTagsManager::Get().RequestGameplayTagChildren(Tag);
}
FName UGMS_Utility::GetSimpleTagName(const FGameplayTag& Tag)
{
const auto TagNode{UGameplayTagsManager::Get().FindTagNode(Tag)};
return TagNode.IsValid() ? TagNode->GetSimpleTagName() : NAME_None;
}
bool UGMS_Utility::ShouldDisplayDebugForActor(const AActor* Actor, const FName& DisplayName)
{
const auto* World{IsValid(Actor) ? Actor->GetWorld() : nullptr};
const auto* PlayerController{IsValid(World) ? World->GetFirstPlayerController() : nullptr};
auto* Hud{IsValid(PlayerController) ? PlayerController->GetHUD() : nullptr};
return IsValid(Hud) && Hud->ShouldDisplayDebug(DisplayName) && Hud->GetCurrentDebugTargetActor() == Actor;
}
float UGMS_Utility::CalculateAnimatedSpeed(const UAnimSequenceBase* AnimSequence)
{
if (AnimSequence == nullptr)
{
UE_LOG(LogGMS, Warning, TEXT("Passed invalid anim sequence"));
return 0.0f;
}
const float AnimLength = AnimSequence->GetPlayLength();
// Calculate the speed as: (distance traveled by the animation) / (length of the animation)
const FVector RootMotionTranslation = AnimSequence->ExtractRootMotionFromRange(0.0f, AnimLength).GetTranslation();
const float RootMotionDistance = RootMotionTranslation.Size2D();
if (!FMath::IsNearlyZero(RootMotionDistance))
{
const float AnimationSpeed = RootMotionDistance / AnimLength;
return AnimationSpeed;
}
UE_LOG(LogGMS, Warning, TEXT("Unable to Calculate animation speed for animation with no root motion delta (%s)."), *GetNameSafe(AnimSequence));
return 0.0f;
}
UAnimSequence* UGMS_Utility::SelectAnimationWithFloat(const TArray<FGMS_AnimationWithDistance>& Animations, const float& ReferenceValue)
{
if (Animations.IsEmpty())
{
return nullptr;
}
float Delta = FLT_MAX; // 使用FLT_MAX来初始化Delta
int32 Found = INDEX_NONE;
for (int32 i = 0; i < Animations.Num(); i++)
{
// 计算与传入Float的绝对差值
float TempDelta = FMath::Abs(ReferenceValue - Animations[i].Distance);
// 如果当前的差值更小则更新Delta和Found
if (TempDelta < Delta)
{
Delta = TempDelta;
Found = i;
}
}
// 返回找到的动画或最后一个动画作为备选
return (Found != INDEX_NONE) ? Animations[Found].Animation : Animations.Last().Animation;
}
bool UGMS_Utility::ValidatePoseSearchDatabasesChooser(const UChooserTable* ChooserTable, FText& OutMessage)
{
if (!IsValid(ChooserTable))
{
OutMessage = FText::FromName("Invalid ChooserTable");
return false;
}
if (ChooserTable->GetContextData().Num() != 2)
{
OutMessage = FText::FromString(FString::Format(TEXT("ChooserTable({0}):Context is empty, and only allow 2 element!"), {*ChooserTable->GetName()}));
return false;
}
if (ChooserTable->ResultType != EObjectChooserResultType::ObjectResult || ChooserTable->OutputObjectType != UPoseSearchDatabase::StaticClass())
{
OutMessage = FText::FromString(FString::Format(TEXT("ChooserTable({0}):Result type must be ObjectResult and the OutputObjectType must be PoseSearchDatabase."), {*ChooserTable->GetName()}));
return false;
}
const FContextObjectTypeClass* Ctx1 = ChooserTable->GetContextData()[0].GetPtr<FContextObjectTypeClass>();
bool bValidCtx1 = Ctx1 != nullptr && Ctx1->Class != nullptr && Ctx1->Class->IsChildOf(UGMS_MainAnimInstance::StaticClass());
if (!bValidCtx1)
{
OutMessage = FText::FromString(FString::Format(
TEXT("ChooserTable({0}): First context must be ContextObjectTypeClass and the class must be Subclass of UGMS_MainAnimInstance."), {*ChooserTable->GetName()}));
return false;
}
const FContextObjectTypeClass* Ctx2 = ChooserTable->GetContextData()[1].GetPtr<FContextObjectTypeClass>();
bool bValidCtx2 = Ctx2 != nullptr && Ctx2->Class != nullptr && Ctx2->Class->IsChildOf(UGMS_AnimLayer::StaticClass());
if (!bValidCtx2)
{
OutMessage = FText::FromString(
FString::Format(TEXT("ChooserTable({0}): Secondary context must be ContextObjectTypeClass and the class must be Subclass of UGMS_AnimLayer."), {*ChooserTable->GetName()}));
return false;
}
return true;
}
bool UGMS_Utility::IsValidPoseSearchDatabasesChooser(const UChooserTable* ChooserTable)
{
if (!IsValid(ChooserTable))
{
return false;
}
if (ChooserTable->GetContextData().Num() != 2)
{
UE_LOG(LogGMS, Warning, TEXT("ChooserTable(%s):Context is empty, and only allow 2 element!"), *ChooserTable->GetName());
return false;
}
if (ChooserTable->ResultType != EObjectChooserResultType::ObjectResult || ChooserTable->OutputObjectType != UPoseSearchDatabase::StaticClass())
{
UE_LOG(LogGMS, Warning, TEXT("ChooserTable(%s):Result type must be ObjectResult and the OutputObjectType must be PoseSearchDatabase."), *ChooserTable->GetName());
return false;
}
const FContextObjectTypeClass* Ctx1 = ChooserTable->GetContextData()[0].GetPtr<FContextObjectTypeClass>();
bool bValidCtx1 = Ctx1 != nullptr && Ctx1->Class != nullptr && Ctx1->Class->IsChildOf(UGMS_MainAnimInstance::StaticClass());
if (!bValidCtx1)
{
UE_LOG(LogGMS, Warning, TEXT("ChooserTable(%s): First context must be ContextObjectTypeClass and the class must be Subclass of UGMS_MainAnimInstance."),
*ChooserTable->GetName());
return false;
}
const FContextObjectTypeClass* Ctx2 = ChooserTable->GetContextData()[1].GetPtr<FContextObjectTypeClass>();
bool bValidCtx2 = Ctx2 != nullptr && Ctx2->Class != nullptr && Ctx2->Class->IsChildOf(UGMS_AnimLayer::StaticClass());
if (!bValidCtx2)
{
GMS_LOG(Warning, "ChooserTable(%s): Secondary context must be ContextObjectTypeClass and the class must be Subclass of UGMS_AnimLayer.",
*ChooserTable->GetName())
return false;
}
return true;
}
TArray<UPoseSearchDatabase*> UGMS_Utility::EvaluatePoseSearchDatabasesChooser(const UGMS_MainAnimInstance* MainAnimInstance, const UGMS_AnimLayer* AnimLayerInstance,
UChooserTable* ChooserTable)
{
TArray<UPoseSearchDatabase*> Ret;
if (!IsValid(ChooserTable))
{
return Ret;
}
// Fallback single context object version
FChooserEvaluationContext Context;
Context.AddObjectParam(const_cast<UGMS_MainAnimInstance*>(MainAnimInstance));
Context.AddObjectParam(const_cast<UGMS_AnimLayer*>(AnimLayerInstance));
auto Callback = FObjectChooserBase::FObjectChooserIteratorCallback::CreateLambda([&Ret](UObject* InResult)
{
if (InResult && InResult->IsA(UPoseSearchDatabase::StaticClass()))
{
Ret.Add(Cast<UPoseSearchDatabase>(InResult));
}
return FObjectChooserBase::EIteratorStatus::Continue;
});
UChooserTable::EvaluateChooser(Context, ChooserTable, Callback);
return Ret;
}
const UGMS_MovementSetUserSetting* UGMS_Utility::GetMovementSetUserSetting(const FGMS_MovementSetSetting& MovementSetSetting, TSubclassOf<UGMS_MovementSetUserSetting> DesiredClass)
{
if (!IsValid(DesiredClass))
{
return nullptr;
}
for (TObjectPtr<UGMS_MovementSetUserSetting> UserSetting : MovementSetSetting.UserSettings)
{
if (UserSetting->GetClass() == DesiredClass)
{
return UserSetting;
}
}
return nullptr;
}

View File

@@ -0,0 +1,31 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Utility/GMS_Vector.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_Vector)
FVector UGMS_Vector::SlerpSkipNormalization(const FVector& From, const FVector& To, const float Ratio)
{
// http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/
auto Dot{From | To};
if (Dot > 0.9995f)
{
return FMath::Lerp(From, To, Ratio).GetSafeNormal();
}
Dot = FMath::Max(-1.0f, Dot);
const auto Theta{UE_REAL_TO_FLOAT(FMath::Acos(Dot)) * Ratio};
float Sin, Cos;
FMath::SinCos(&Sin, &Cos, Theta);
auto FromPerpendicular{To - From * Dot};
FromPerpendicular.Normalize();
return From * Cos + FromPerpendicular * Sin;
}