第一次提交

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,43 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
using UnrealBuildTool;
public class GenericMovementSystem : ModuleRules
{
public GenericMovementSystem(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
CppCompileWarningSettings.NonInlinedGenCppWarningLevel = WarningLevel.Warning;
PublicDependencyModuleNames.AddRange(
new[]
{
"GameplayTags",
"AnimationWarpingRuntime",
"Chooser", "PoseSearch", "Mover"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new[]
{
"Core",
"CoreUObject",
"NetCore",
"ModularGameplay",
"EngineSettings",
"Engine",
"AnimGraphRuntime",
"BlendStack",
"AnimationLocomotionLibraryRuntime",
"Niagara",
"DeveloperSettings"
// ... add private dependencies that you statically link with here ...
}
);
if (Target.Type == TargetRules.TargetType.Editor) PrivateDependencyModuleNames.AddRange(new[] { "MessageLog" });
}
}

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;
}

View File

@@ -0,0 +1,616 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "GMS_MovementSystemComponent.h"
#include "GameFramework/Character.h"
#include "GMS_CharacterMovementSystemComponent.generated.h"
class UGMS_CharacterMovementSetting_Default;
class UGMS_CharacterRotationSetting_Default;
/**
* Movement system component for characters.
* 角色的运动系统组件。
*/
UCLASS(ClassGroup=GMS, BlueprintType, Blueprintable, meta=(BlueprintSpawnableComponent), DisplayName="GMS Movement System Component(Character)")
class GENERICMOVEMENTSYSTEM_API UGMS_CharacterMovementSystemComponent : public UGMS_MovementSystemComponent
{
GENERATED_BODY()
public:
/**
* Constructor with object initializer.
* 使用对象初始化器构造函数。
*/
explicit UGMS_CharacterMovementSystemComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
/**
* Gets lifetime replicated properties.
* 获取生命周期复制属性。
* @param OutLifetimeProps The lifetime properties. 生命周期属性。
*/
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/**
* Gets the character movement component.
* 获取角色运动组件。
* @return The character movement component. 角色运动组件。
*/
UCharacterMovementComponent* GetCharacterMovement() const { return CharacterMovement; };
protected:
/**
* The character movement component.
* 角色运动组件。
*/
UPROPERTY()
TObjectPtr<UCharacterMovementComponent> CharacterMovement;
/**
* The owning character.
* 拥有该组件的角色。
*/
UPROPERTY()
TObjectPtr<ACharacter> OwnerCharacter{nullptr};
/**
* Maps movement modes to gameplay tags.
* 将运动模式映射到游戏标签。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Settings|GameplayTags", meta=(Categories="GMS.LocomotionMode"))
TMap<TEnumAsByte<EMovementMode>, FGameplayTag> MovementModeToTagMapping;
/**
* Maps custom movement modes to gameplay tags.
* 将自定义运动模式映射到游戏标签。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Settings|GameplayTags", meta=(Categories="GMS.LocomotionMode"))
TMap<uint8, FGameplayTag> CustomMovementModeToTagMapping;
/**
* Whether to apply movement settings to the character movement component.
* 是否将运动设置应用于角色运动组件。
* @note Can be disabled for external control (e.g., AI). 可禁用以进行外部控制例如AI
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Settings|DynamicMovementState")
bool bAllowRefreshCharacterMovementSettings{true};
/**
* Curve mapping ground speed to movement state index (e.g., walk, jog, sprint).
* 将地面速度映射到运动状态索引的曲线(例如走、慢跑、冲刺)。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Settings|DynamicMovementState", meta=(EditCondition="!bAllowRefreshCharacterMovementSettings"))
TObjectPtr<UCurveFloat> SpeedToMovementStateCurve;
/**
* Initializes the component.
* 初始化组件。
*/
virtual void InitializeComponent() override;
/**
* Called when the game starts.
* 游戏开始时调用。
*/
virtual void BeginPlay() override;
/**
* Called when the game ends.
* 游戏结束时调用。
* @param EndPlayReason The reason for ending. 结束原因。
*/
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
/**
* Called every frame.
* 每帧调用。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
* @param TickType The type of tick. tick类型。
* @param ThisTickFunction The tick function. tick函数。
*/
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
protected:
/**
* Called when the character's movement mode changes.
* 角色运动模式更改时调用。
* @param InCharacter The character. 角色。
* @param PrevMovementMode The previous movement mode. 之前的运动模式。
* @param PreviousCustomMode The previous custom mode. 之前的自定义模式。
*/
UFUNCTION()
virtual void OnCharacterMovementModeChanged(ACharacter* InCharacter, EMovementMode PrevMovementMode, uint8 PreviousCustomMode = 0);
/**
* Called when the locomotion mode is replicated.
* 运动模式复制时调用。
* @param PreviousLocomotionMode The previous locomotion mode. 之前的运动模式。
*/
virtual void OnReplicated_LocomotionMode(const FGameplayTag& PreviousLocomotionMode) override;
/**
* Calculates the actual movement state.
* 计算实际的运动状态。
* @return The actual movement state. 实际的运动状态。
*/
virtual FGameplayTag CalculateActualMovementState();
/**
* Applies the desired movement state settings to the character movement component.
* 将期望的运动状态设置应用于角色运动组件。
*/
virtual void ApplyMovementSetting() override;
/**
* Called when the rotation mode changes.
* 旋转模式更改时调用。
* @param PreviousRotationMode The previous rotation mode. 之前的旋转模式。
*/
virtual void OnRotationModeChanged_Implementation(const FGameplayTag& PreviousRotationMode) override;
virtual void RefreshMovementBase() override;
#pragma region MovementState
public:
/**
* Gets the desired movement state.
* 获取期望的运动状态。
* @return The desired movement state. 期望的运动状态。
*/
virtual const FGameplayTag& GetDesiredMovementState() const override;
/**
* Sets the desired movement state.
* 设置期望的运动状态。
* @param NewDesiredMovement The new desired movement state. 新的期望运动状态。
*/
virtual void SetDesiredMovement(const FGameplayTag& NewDesiredMovement) override;
/**
* Gets the current movement state.
* 获取当前的运动状态。
* @return The current movement state. 当前的运动状态。
*/
virtual const FGameplayTag& GetMovementState() const override;
protected:
/**
* Sets the movement state.
* 设置运动状态。
* @param NewMovementState The new movement state. 新运动状态。
*/
virtual void SetMovementState(const FGameplayTag& NewMovementState);
/**
* Refreshes the movement state.
* 刷新运动状态。
*/
virtual void RefreshMovementState();
private:
/**
* Sets the desired movement state with optional RPC.
* 设置期望的运动状态可选择是否发送RPC。
* @param NewDesiredMovement The new desired movement state. 新的期望运动状态。
* @param bSendRpc Whether to send RPC. 是否发送RPC。
*/
void SetDesiredMovement(const FGameplayTag& NewDesiredMovement, bool bSendRpc);
/**
* Client RPC to set the desired movement state.
* 客户端RPC设置期望的运动状态。
* @param NewDesiredMovement The new desired movement state. 新的期望运动状态。
*/
UFUNCTION(Client, Reliable)
void ClientSetDesiredMovement(const FGameplayTag& NewDesiredMovement);
virtual void ClientSetDesiredMovement_Implementation(const FGameplayTag& NewDesiredMovement);
/**
* Server RPC to set the desired movement state.
* 服务器RPC设置期望的运动状态。
* @param NewDesiredMovement The new desired movement state. 新的期望运动状态。
*/
UFUNCTION(Server, Reliable)
void ServerSetDesiredMovement(const FGameplayTag& NewDesiredMovement);
virtual void ServerSetDesiredMovement_Implementation(const FGameplayTag& NewDesiredMovement);
#pragma endregion
#pragma region RotationMode
public:
/**
* Gets the desired rotation mode.
* 获取期望的旋转模式。
* @return The desired rotation mode. 期望的旋转模式。
*/
virtual const FGameplayTag& GetDesiredRotationMode() const override;
/**
* Sets the desired rotation mode.
* 设置期望的旋转模式。
* @param NewDesiredRotationMode The new desired rotation mode. 新的期望旋转模式。
*/
virtual void SetDesiredRotationMode(const FGameplayTag& NewDesiredRotationMode) override;
/**
* Gets the current rotation mode.
* 获取当前的旋转模式。
* @return The current rotation mode. 当前的旋转模式。
*/
virtual const FGameplayTag& GetRotationMode() const override;
protected:
/**
* Sets the rotation mode.
* 设置旋转模式。
* @param NewRotationMode The new rotation mode. 新旋转模式。
*/
virtual void SetRotationMode(const FGameplayTag& NewRotationMode);
/**
* Refreshes the rotation mode.
* 刷新旋转模式。
*/
virtual void RefreshRotationMode();
private:
/**
* Sets the desired rotation mode with optional RPC.
* 设置期望的旋转模式可选择是否发送RPC。
* @param NewDesiredRotationMode The new desired rotation mode. 新的期望旋转模式。
* @param bSendRpc Whether to send RPC. 是否发送RPC。
*/
void SetDesiredRotationMode(const FGameplayTag& NewDesiredRotationMode, bool bSendRpc);
/**
* Client RPC to set the desired rotation mode.
* 客户端RPC设置期望的旋转模式。
* @param NewDesiredRotationMode The new desired rotation mode. 新的期望旋转模式。
*/
UFUNCTION(Client, Reliable)
void ClientSetDesiredRotationMode(const FGameplayTag& NewDesiredRotationMode);
virtual void ClientSetDesiredRotationMode_Implementation(const FGameplayTag& NewDesiredRotationMode);
/**
* Server RPC to set the desired rotation mode.
* 服务器RPC设置期望的旋转模式。
* @param NewDesiredRotationMode The new desired rotation mode. 新的期望旋转模式。
*/
UFUNCTION(Server, Reliable)
void ServerSetDesiredRotationMode(const FGameplayTag& NewDesiredRotationMode);
virtual void ServerSetDesiredRotationMode_Implementation(const FGameplayTag& NewDesiredRotationMode);
#pragma endregion
#pragma region Input
public:
/**
* Gets the movement intent.
* 获取移动意图。
* @return The movement intent vector. 移动意图向量。
*/
virtual FVector GetMovementIntent() const override;
/**
* Sets the movement intent.
* 设置移动意图。
* @param NewMovementIntent The new movement intent vector. 新移动意图向量。
*/
void SetMovementIntent(FVector NewMovementIntent);
protected:
/**
* Refreshes input handling.
* 刷新输入处理。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
virtual void RefreshInput(float DeltaTime) override;
/**
* Replicated movement intent.
* 复制的移动意图。
*/
UPROPERTY(VisibleAnywhere, Category="State|Input", Transient, Replicated)
FVector_NetQuantizeNormal MovementIntent;
/**
* Desired movement state.
* 期望的运动状态。
*/
UPROPERTY(EditAnywhere, Category="Settings", Replicated, meta=(Categories="GMS.MovementState"))
FGameplayTag DesiredMovementState{GMS_MovementStateTags::Jog};
/**
* Desired rotation mode.
* 期望的旋转模式。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Settings", Replicated, meta=(Categories="GMS.RotationMode"))
FGameplayTag DesiredRotationMode{GMS_RotationModeTags::ViewDirection};
/**
* Current movement state.
* 当前的运动状态。
*/
UPROPERTY(VisibleAnywhere, Category="State", Transient, meta=(Categories="GMS.MovementState"))
FGameplayTag MovementState{GMS_MovementStateTags::Jog};
/**
* Current rotation mode.
* 当前的旋转模式。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="State", Transient, meta=(Categories="GMS.RotationMode"))
FGameplayTag RotationMode{GMS_RotationModeTags::ViewDirection};
#pragma endregion
#pragma region ViewSystem
/**
* Refreshes the view system.
* 刷新视图系统。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
virtual void RefreshView(float DeltaTime);
#pragma endregion
#pragma region Abstraction
public:
/**
* Checks if the character is crouching.
* 检查角色是否在蹲伏。
* @return True if crouching. 如果在蹲伏返回true。
*/
virtual bool IsCrouching() const override;
/**
* Gets the maximum speed.
* 获取最大速度。
* @return The maximum speed. 最大速度。
*/
virtual float GetMaxSpeed() const override;
/**
* Gets the scaled capsule radius.
* 获取缩放的胶囊体半径。
* @return The capsule radius. 胶囊体半径。
*/
virtual float GetScaledCapsuleRadius() const override;
/**
* Gets the scaled capsule half height.
* 获取缩放的胶囊体半高。
* @return The capsule half height. 胶囊体半高。
*/
virtual float GetScaledCapsuleHalfHeight() const override;
/**
* Gets the maximum acceleration.
* 获取最大加速度。
* @return The maximum acceleration. 最大加速度。
*/
virtual float GetMaxAcceleration() const override;
/**
* Gets the maximum braking deceleration.
* 获取最大制动减速度。
* @return The maximum braking deceleration. 最大制动减速度。
*/
virtual float GetMaxBrakingDeceleration() const override;
/**
* Gets the walkable floor Z value.
* 获取可行走地面Z值。
* @return The walkable floor Z. 可行走地面Z值。
*/
virtual float GetWalkableFloorZ() const override;
/**
* Gets the gravity Z value.
* 获取重力Z值。
* @return The gravity Z. 重力Z值。
*/
virtual float GetGravityZ() const override;
/**
* Gets the skeletal mesh component.
* 获取骨骼网格组件。
* @return The skeletal mesh component. 骨骼网格组件。
*/
virtual USkeletalMeshComponent* GetMesh() const override;
virtual bool IsMovingOnGround() const override;
#pragma endregion
#pragma region Locomotion
protected:
/**
* Early refresh for locomotion.
* 运动的早期刷新。
*/
virtual void RefreshLocomotionEarly();
/**
* Refreshes locomotion state.
* 刷新运动状态。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
virtual void RefreshLocomotion(float DeltaTime);
/**
* Late refresh for locomotion.
* 运动的后期刷新。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
virtual void RefreshLocomotionLate(float DeltaTime);
/**
* Refreshes dynamic movement state.
* 刷新动态运动状态。
*/
virtual void RefreshDynamicMovementState();
#pragma endregion
#pragma region Rotation System
public:
/**
* Sets rotation instantly.
* 立即设置旋转。
* @param TargetYawAngle The target yaw angle. 目标偏航角。
* @param Teleport The teleport type. 传送类型。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|MovementSystem")
void SetRotationInstant(const float TargetYawAngle, const ETeleportType Teleport);
/**
* Sets rotation smoothly.
* 平滑设置旋转。
* @param TargetYawAngle The target yaw angle. 目标偏航角。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
* @param InterpolationHalfLife The rotation interpolation speed. 旋转插值速度。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|MovementSystem")
void SetRotationSmooth(float TargetYawAngle, float DeltaTime, float InterpolationHalfLife);
/**
* Sets rotation with extra smoothness.
* 额外平滑设置旋转。
* @param TargetYawAngle The target yaw angle. 目标偏航角。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
* @param InterpolationHalfLife The rotation interpolation speed. 旋转插值速度。
* @param TargetYawAngleRotationSpeed The target yaw rotation speed. 目标偏航旋转速度。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|MovementSystem")
void SetRotationExtraSmooth(float TargetYawAngle, float DeltaTime, float InterpolationHalfLife, float TargetYawAngleRotationSpeed);
protected:
/**
* Refreshes the character's rotation.
* 刷新角色的旋转。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
UFUNCTION(BlueprintNativeEvent, Category="GMS|MovementSystem")
void RefreshRotation(float DeltaTime);
virtual void RefreshRotation_Implementation(float DeltaTime);
/**
* Refreshes grounded rotation.
* 刷新地面旋转。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
virtual void RefreshGroundedRotation(float DeltaTime);
/**
* Refreshes rotation when grounded and not moving.
* 地面上且不移动时刷新旋转。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
virtual void RefreshGroundedNotMovingRotation(float DeltaTime);
virtual float CalculateGroundedMovingRotationInterpolationSpeed(TObjectPtr<UCurveFloat> SpeedCurve, float Default = 12.0f) const;
/**
* Refreshes rotation when grounded and moving.
* 地面上且移动时刷新旋转。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
virtual void RefreshGroundedMovingRotation(float DeltaTime);
/**
* Constrains aiming rotation.
* 约束瞄准旋转。
* @param ActorRotation The actor's rotation. Actor的旋转。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
* @param bApplySecondaryConstraint Whether to apply secondary constraints. 是否应用次级约束。
* @return True if rotation was constrained. 如果旋转被约束返回true。
*/
virtual bool ConstrainAimingRotation(FRotator& ActorRotation, float DeltaTime, bool bApplySecondaryConstraint = false);
/**
* Applies rotation yaw speed animation curve.
* 应用旋转偏航速度动画曲线。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
* @return True if the curve was applied. 如果应用了曲线返回true。
*/
bool ApplyRotationYawSpeedAnimationCurve(float DeltaTime);
/**
* Custom rotation logic for grounded moving state.
* 地面移动状态的自定义旋转逻辑。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
* @return True if default rotation logic should be skipped. 如果应跳过默认旋转逻辑返回true。
*/
UFUNCTION(BlueprintNativeEvent, Category="GMS|MovementSystem")
bool RefreshCustomGroundedMovingRotation(float DeltaTime);
/**
* Custom rotation logic for grounded not moving state.
* 地面不移动状态的自定义旋转逻辑。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
* @return True if default rotation logic should be skipped. 如果应跳过默认旋转逻辑返回true。
*/
UFUNCTION(BlueprintNativeEvent, Category="GMS|MovementSystem")
bool RefreshCustomGroundedNotMovingRotation(float DeltaTime);
/**
* Refreshes in-air rotation.
* 刷新空中旋转。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
virtual void RefreshInAirRotation(float DeltaTime);
/**
* Refreshes the target yaw angle using actor's rotation.
* 使用Actor的旋转刷新目标偏航角。
*/
void RefreshTargetYawAngleUsingActorRotation();
/**
* Sets the target yaw angle.
* 设置目标偏航角。
* @param TargetYawAngle The target yaw angle. 目标偏航角。
*/
void SetTargetYawAngle(float TargetYawAngle);
/**
* Sets the target yaw angle smoothly.
* 平滑设置目标偏航角。
* @param TargetYawAngle The target yaw angle. 目标偏航角。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
* @param RotationSpeed The rotation speed. 旋转速度。
*/
void SetTargetYawAngleSmooth(float TargetYawAngle, float DeltaTime, float RotationSpeed);
/**
* Refreshes the view-relative target yaw angle.
* 刷新相对于视图的目标偏航角。
*/
void RefreshViewRelativeTargetYawAngle();
virtual void SetActorRotation(FRotator DesiredRotation);
#pragma endregion
#pragma region DistanceMatching
public:
/**
* Gets parameters for predicting ground movement pivot location.
* 获取预测地面运动枢轴位置的参数。
* @return The pivot location parameters. 枢轴位置参数。
*/
virtual FGMS_PredictGroundMovementPivotLocationParams GetPredictGroundMovementPivotLocationParams() const override;
/**
* Gets parameters for predicting ground movement stop location.
* 获取预测地面运动停止位置的参数。
* @return The stop location parameters. 停止位置参数。
*/
virtual FGMS_PredictGroundMovementStopLocationParams GetPredictGroundMovementStopLocationParams() const override;
#pragma endregion
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,546 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_MovementSystemComponent.h"
#include "MoverSimulationTypes.h"
#include "GMS_MoverMovementSystemComponent.generated.h"
class UMoverTrajectoryPredictor;
class UNavMoverComponent;
class UGMS_InputProducer;
/**
* Movement system component for mover-based movement (work in progress).
* 基于Mover的运动系统组件开发中
* @note Not recommended for use until production ready. 在标记为生产就绪之前不建议使用。
*/
UCLASS(ClassGroup=GMS, BlueprintType, Blueprintable, meta=(BlueprintSpawnableComponent), DisplayName="GMS Movement System Component(Mover)")
class GENERICMOVEMENTSYSTEM_API UGMS_MoverMovementSystemComponent : public UGMS_MovementSystemComponent, public IMoverInputProducerInterface
{
GENERATED_BODY()
public:
/**
* Constructor with object initializer.
* 使用对象初始化器构造函数。
*/
UGMS_MoverMovementSystemComponent(const FObjectInitializer& ObjectInitializer);
/**
* Gets lifetime replicated properties.
* 获取生命周期复制属性。
* @param OutLifetimeProps The lifetime properties. 生命周期属性。
*/
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/**
* Produces input for the mover simulation.
* 为Mover模拟生成输入。
* @param SimTimeMs Simulation time in milliseconds. 模拟时间(毫秒)。
* @param InputCmdResult The input command context (output). 输入命令上下文(输出)。
*/
virtual void ProduceInput_Implementation(int32 SimTimeMs, FMoverInputCmdContext& InputCmdResult) override;
/**
* Called every frame.
* 每帧调用。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
* @param TickType The type of tick. tick类型。
* @param ThisTickFunction The tick function. tick函数。
*/
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
/**
* Called before the mover simulation tick.
* Mover模拟tick前调用。
* @param TimeStep The mover time step. Mover时间步。
* @param InputCmd The input command context. 输入命令上下文。
*/
UFUNCTION()
virtual void OnMoverPreSimulationTick(const FMoverTimeStep& TimeStep, const FMoverInputCmdContext& InputCmd);
protected:
/**
* The mover component.
* Mover组件。
*/
UPROPERTY()
TObjectPtr<UMoverComponent> MoverComponent{nullptr};
UPROPERTY()
TObjectPtr<UMoverTrajectoryPredictor> TrajectoryPredictor{nullptr};
/**
* The skeletal mesh component.
* 骨骼网格组件。
*/
UPROPERTY()
TObjectPtr<USkeletalMeshComponent> MeshComponent{nullptr};
/**
* Handles navigation movement data and functions.
* 处理导航运动数据和函数。
*/
UPROPERTY()
TObjectPtr<UNavMoverComponent> NavMoverComponent{nullptr};
/**
* Maps movement modes to gameplay tags.
* 将运动模式映射到游戏标签。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Settings|GameplayTags", meta=(Categories="GMS.LocomotionMode"))
TMap<FName, FGameplayTag> MovementModeToTagMapping;
/**
* Initializes the component.
* 初始化组件。
*/
virtual void InitializeComponent() override;
/**
* Called when the game starts.
* 游戏开始时调用。
*/
virtual void BeginPlay() override;
/**
* Called when the game ends.
* 游戏结束时调用。
* @param EndPlayReason The reason for ending. 结束原因。
*/
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
/**
* Handle for the movement state modifier (e.g., crouching).
* 运动状态修饰符的句柄(例如蹲伏)。
*/
FMovementModifierHandle MovementStateModiferHandle;
/**
* Called when the mover movement mode changes.
* Mover运动模式更改时调用。
* @param PreviousMovementModeName The previous movement mode name. 之前的运动模式名称。
* @param NewMovementModeName The new movement mode name. 新运动模式名称。
*/
UFUNCTION()
virtual void OnMoverMovementModeChanged(const FName& PreviousMovementModeName, const FName& NewMovementModeName);
/**
* Applies movement settings.
* 应用运动设置。
*/
virtual void ApplyMovementSetting() override;
#pragma region ViewSystem
/**
* Refreshes the view system.
* 刷新视图系统。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
virtual void RefreshView(float DeltaTime);
/**
* Server RPC to set replicated view rotation.
* 服务器RPC设置复制的视图旋转。
* @param NewViewRotation The new view rotation. 新视图旋转。
*/
virtual void ServerSetReplicatedViewRotation_Implementation(const FRotator& NewViewRotation) override;
#pragma endregion
#pragma region Locomotion
public:
virtual TScriptInterface<IPoseSearchTrajectoryPredictorInterface> GetTrajectoryPredictor() const override;
/**
* Checks if the character is crouching.
* 检查角色是否在蹲伏。
* @return True if crouching. 如果在蹲伏返回true。
*/
virtual bool IsCrouching() const override;
/**
* Gets the maximum speed.
* 获取最大速度。
* @return The maximum speed. 最大速度。
*/
virtual float GetMaxSpeed() const override;
/**
* Gets the scaled capsule radius.
* 获取缩放的胶囊体半径。
* @return The capsule radius. 胶囊体半径。
*/
virtual float GetScaledCapsuleRadius() const override;
/**
* Gets the scaled capsule half height.
* 获取缩放的胶囊体半高。
* @return The capsule half height. 胶囊体半高。
*/
virtual float GetScaledCapsuleHalfHeight() const override;
/**
* Gets the maximum acceleration.
* 获取最大加速度。
* @return The maximum acceleration. 最大加速度。
*/
virtual float GetMaxAcceleration() const override;
/**
* Gets the maximum braking deceleration.
* 获取最大制动减速度。
* @return The maximum braking deceleration. 最大制动减速度。
*/
virtual float GetMaxBrakingDeceleration() const override;
/**
* Gets the walkable floor Z value.
* 获取可行走地面Z值。
* @return The walkable floor Z. 可行走地面Z值。
*/
virtual float GetWalkableFloorZ() const override;
/**
* Gets the gravity Z value.
* 获取重力Z值。
* @return The gravity Z. 重力Z值。
*/
virtual float GetGravityZ() const override;
/**
* Gets the skeletal mesh component.
* 获取骨骼网格组件。
* @return The skeletal mesh component. 骨骼网格组件。
*/
virtual USkeletalMeshComponent* GetMesh() const override;
virtual bool IsMovingOnGround() const override;
protected:
/**
* Early refresh for locomotion.
* 运动的早期刷新。
*/
virtual void RefreshLocomotionEarly();
/**
* Refreshes locomotion state.
* 刷新运动状态。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
virtual void RefreshLocomotion(float DeltaTime);
/**
* Refreshes dynamic movement state.
* 刷新动态运动状态。
*/
virtual void RefreshDynamicMovementState();
/**
* Late refresh for locomotion.
* 运动的后期刷新。
* @param DeltaTime Time since last frame. 上一帧以来的时间。
*/
virtual void RefreshLocomotionLate(float DeltaTime);
#pragma endregion
#pragma region Rotation System
public:
/**
* Refreshes the target yaw angle using locomotion rotation.
* 使用运动旋转刷新目标偏航角。
*/
void RefreshTargetYawAngleUsingActorRotation();
/**
* Sets the target yaw angle.
* 设置目标偏航角。
* @param TargetYawAngle The target yaw angle. 目标偏航角。
*/
void SetTargetYawAngle(float TargetYawAngle);
/**
* Refreshes the view-relative target yaw angle.
* 刷新相对于视图的目标偏航角。
*/
void RefreshViewRelativeTargetYawAngle();
#pragma endregion
#pragma region DistanceMatching
/**
* Gets parameters for predicting ground movement pivot location.
* 获取预测地面运动枢轴位置的参数。
* @return The pivot location parameters. 枢轴位置参数。
*/
virtual FGMS_PredictGroundMovementPivotLocationParams GetPredictGroundMovementPivotLocationParams() const override;
/**
* Gets parameters for predicting ground movement stop location.
* 获取预测地面运动停止位置的参数。
* @return The stop location parameters. 停止位置参数。
*/
virtual FGMS_PredictGroundMovementStopLocationParams GetPredictGroundMovementStopLocationParams() const override;
#pragma endregion
#pragma region MovementState
virtual void RefreshMovementBase() override;
/**
* Gets the desired movement state.
* 获取期望的运动状态。
* @return The desired movement state. 期望的运动状态。
*/
virtual const FGameplayTag& GetDesiredMovementState() const override;
/**
* Sets the desired movement state.
* 设置期望的运动状态。
* @param NewDesiredMovement The new desired movement state. 新的期望运动状态。
*/
virtual void SetDesiredMovement(const FGameplayTag& NewDesiredMovement) override;
/**
* Gets the current movement state.
* 获取当前的运动状态。
* @return The current movement state. 当前的运动状态。
*/
virtual const FGameplayTag& GetMovementState() const override;
protected:
/**
* Applies the movement state and related settings.
* 应用运动状态及相关设置。
* @param NewMovementState The new movement state. 新运动状态。
*/
virtual void ApplyMovementState(const FGameplayTag& NewMovementState);
#pragma endregion
#pragma region RotationMode
public:
/**
* Gets the desired rotation mode.
* 获取期望的旋转模式。
* @return The desired rotation mode. 期望的旋转模式。
*/
virtual const FGameplayTag& GetDesiredRotationMode() const override;
/**
* Sets the desired rotation mode.
* 设置期望的旋转模式。
* @param NewDesiredRotationMode The new desired rotation mode. 新的期望旋转模式。
*/
virtual void SetDesiredRotationMode(const FGameplayTag& NewDesiredRotationMode) override;
/**
* Gets the current rotation mode.
* 获取当前的旋转模式。
* @return The current rotation mode. 当前的旋转模式。
*/
virtual const FGameplayTag& GetRotationMode() const override;
protected:
/**
* Applies the rotation mode and related settings.
* 应用旋转模式及相关设置。
* @param NewRotationMode The new rotation mode. 新旋转模式。
*/
virtual void ApplyRotationMode(const FGameplayTag& NewRotationMode);
#pragma endregion
#pragma region Input
/**
* Requests movement with an intended directional magnitude.
* 请求以指定方向强度移动。
* @param DesiredIntent The desired movement intent. 期望的移动意图。
*/
UFUNCTION(BlueprintCallable, Category="GMS|MovementSystem")
virtual void RequestMoveByIntent(const FVector& DesiredIntent) { CachedMoveInputIntent = DesiredIntent; }
/**
* Requests movement with a desired velocity.
* 请求以指定速度移动。
* @param DesiredVelocity The desired velocity. 期望的速度。
*/
UFUNCTION(BlueprintCallable, Category="GMS|MovementSystem")
virtual void RequestMoveByVelocity(const FVector& DesiredVelocity) { CachedMoveInputVelocity = DesiredVelocity; }
/**
* Clears movement requests.
* 清除移动请求。
*/
UFUNCTION(BlueprintCallable, Category="GMS|MovementSystem")
virtual void ClearMoveRequest()
{
CachedMoveInputIntent = FVector::ZeroVector;
CachedMoveInputVelocity = FVector::ZeroVector;
}
/**
* Gets the movement intent.
* 获取移动意图。
* @return The movement intent vector. 移动意图向量。
*/
virtual FVector GetMovementIntent() const override;
/**
* Produces input for the simulation frame.
* 为模拟帧生成输入。
* @param DeltaMs Time delta in milliseconds. 时间增量(毫秒)。
* @param InputCmd The input command context. 输入命令上下文。
* @return The produced input command context. 生成的输入命令上下文。
*/
UFUNCTION(BlueprintNativeEvent, Category="GMS|MovementSystem")
FMoverInputCmdContext OnProduceInput(float DeltaMs, FMoverInputCmdContext InputCmd);
/**
* Adjusts orientation intent based on context.
* 根据上下文调整方向意图。
* @param DeltaSeconds Time since last frame. 上一帧以来的时间。
* @param OrientationIntent The orientation intent. 方向意图。
* @return The adjusted orientation intent. 调整后的方向意图。
*/
virtual FVector AdjustOrientationIntent(float DeltaSeconds, const FVector& OrientationIntent) const;
/**
* Whether to use base-relative movement.
* 是否使用基于基础的移动。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings|Input")
bool bUseBaseRelativeMovement = true;
/**
* Whether to maintain the last input orientation after input ceases.
* 输入停止后是否保持最后输入方向。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings|Input")
bool bMaintainLastInputOrientation = false;
/**
* Last non-zero movement input.
* 最后非零移动输入。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State|Input")
FVector LastAffirmativeMoveInput = FVector::ZeroVector;
/**
* If true, the actor will remain vertical despite any rotation applied to the actor
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=MoverExamples)
bool bShouldRemainVertical = true;
/**
* Cached movement input intent.
* 缓存的移动输入意图。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="State|Input")
FVector CachedMoveInputIntent = FVector::ZeroVector;
/**
* Cached movement input velocity.
* 缓存的移动输入速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="State|Input")
FVector CachedMoveInputVelocity = FVector::ZeroVector;
/**
* Cached turn input.
* 缓存的转向输入。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State|Input")
FRotator CachedTurnInput = FRotator::ZeroRotator;
/**
* Cached look input.
* 缓存的观察输入。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State|Input")
FRotator CachedLookInput = FRotator::ZeroRotator;
/**
* Current desired movement state.
* 当前期望的运动状态。
*/
UPROPERTY(EditAnywhere, Category="State|Input", meta=(Categories="GMS.MovementState"))
FGameplayTag DesiredMovementState{GMS_MovementStateTags::Jog};
/**
* Current desired rotation mode.
* 当前期望的旋转模式。
*/
UPROPERTY(EditAnywhere, Category="Settings", meta=(Categories="GMS.RotationMode"))
FGameplayTag DesiredRotationMode{GMS_RotationModeTags::ViewDirection};
/**
* Current movement state.
* 当前的运动状态。
*/
UPROPERTY(VisibleAnywhere, Category="State", ReplicatedUsing=OnMovementStateChanged, meta=(Categories="GMS.MovementState"))
FGameplayTag MovementState{GMS_MovementStateTags::Jog};
/**
* Current rotation mode.
* 当前的旋转模式。
*/
UPROPERTY(VisibleAnywhere, Category="State", ReplicatedUsing=OnRotationModeChanged, meta=(Categories="GMS.RotationMode"))
FGameplayTag RotationMode{GMS_RotationModeTags::ViewDirection};
public:
/**
* Whether the jump input was just pressed.
* 跳跃输入是否刚被按下。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State|Input")
bool bIsJumpJustPressed = false;
/**
* Whether the jump input is held.
* 跳跃输入是否被持续按住。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State|Input")
bool bIsJumpPressed = false;
/**
* Input tags for the component.
* 组件的输入标签。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient, Category="State|Input")
FGameplayTagContainer InputTags;
/**
* Whether flying is active.
* 飞行是否激活。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State|Input")
bool bIsFlyingActive = false;
/**
* Whether to toggle flying.
* 是否切换飞行状态。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State|Input")
bool bShouldToggleFlying = false;
private:
/**
* Flag indicating if input is produced in Blueprint.
* 指示是否在蓝图中生成输入的标志。
*/
uint8 bHasProduceInputInBpFunc : 1;
#pragma endregion
#if WITH_EDITOR
/**
* Validates data in the editor.
* 在编辑器中验证数据。
* @param Context The validation context. 验证上下文。
* @return The validation result. 验证结果。
*/
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
#endif
};

View File

@@ -0,0 +1,21 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
struct FAutoCompleteCommand;
class FGenericMovementSystemModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
#if ALLOW_CONSOLE
void Console_OnRegisterAutoCompleteEntries(TArray<FAutoCompleteCommand>& AutoCompleteCommands);
#endif
};

View File

@@ -0,0 +1,160 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_LocomotionStructLibrary.h"
#include "Engine/DataAsset.h"
#include "Animation/AnimInstance.h"
#include "GMS_AnimLayer.generated.h"
class UGMS_MovementDefinition;
class UGMS_AnimLayer;
class UGMS_MovementSystemComponent;
class UGMS_MainAnimInstance;
class APawn;
/**
* Base class for animation layer settings.
* 动画层设置的基类。
*/
UCLASS(Abstract, BlueprintType, EditInlineNew, Const, CollapseCategories)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayerSetting : public UDataAsset
{
GENERATED_BODY()
public:
/**
* Gets the override animation layer class.
* 获取覆盖的动画层类。
* @param OutLayerClass The output animation layer class. 输出的动画层类。
* @return True if an override class is provided. 如果提供了覆盖类则返回true。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|AnimationLayer")
bool GetOverrideAnimLayerClass(TSubclassOf<UGMS_AnimLayer>& OutLayerClass) const;
/**
* Validates the data for this animation layer setting.
* 验证此动画层设置的数据。
* @param ErrorText The error message if validation fails. 如果验证失败的错误信息。
* @return True if data is valid. 如果数据有效则返回true。
*/
UFUNCTION(BlueprintNativeEvent, Category="GMS|AnimationLayer", meta=(DisplayName="Is Data Valid"))
bool K2_IsDataValid(FText& ErrorText) const;
#if WITH_EDITOR
public:
/**
* Validates data in the editor.
* 在编辑器中验证数据。
* @param Context The validation context. 验证上下文。
* @return The validation result. 验证结果。
*/
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
#endif
};
/**
* Base class for all animation layers.
* 所有动画层的基类。
* @note Classes inheriting from this must only be linked to GMS_MainAnimInstance (the main animation instance). 从该类继承的类只能链接到GMS_MainAnimInstance主动画实例
*/
UCLASS(BlueprintType, Abstract)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayer : public UAnimInstance
{
GENERATED_BODY()
protected:
/**
* The owning pawn of this animation layer.
* 此动画层的拥有Pawn。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="State", Transient)
TObjectPtr<APawn> PawnOwner;
/**
* Reference to the movement system component.
* 运动系统组件的引用。
*/
UPROPERTY(Transient)
TObjectPtr<UGMS_MovementSystemComponent> MovementSystem;
private:
/**
* Weak reference to the parent main animation instance.
* 对父主动画实例的弱引用。
*/
UPROPERTY(VisibleAnywhere, Category="State", Transient)
TWeakObjectPtr<UGMS_MainAnimInstance> Parent;
public:
/**
* Constructor.
* 构造函数。
*/
UGMS_AnimLayer();
/**
* Gets the parent main animation instance.
* 获取父主动画实例。
* @return The parent main animation instance. 父主动画实例。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|AnimationLayer", Meta = (BlueprintThreadSafe, ReturnDisplayName = "Parent"))
UGMS_MainAnimInstance* GetParent() const;
/**
* Called when the animation layer is linked to the main animation instance.
* 当动画层链接到主动画实例时调用。
* @note Suitable for initialization tasks similar to BeginPlay. 适合执行类似BeginPlay的初始化任务。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|AnimationLayer")
void OnLinked();
virtual void OnLinked_Implementation();
/**
* Called when the animation layer is unlinked from the main animation instance.
* 当动画层从主动画实例取消链接时调用。
* @note Suitable for cleanup tasks similar to EndPlay. 适合执行类似EndPlay的清理任务。
*/
UFUNCTION(BlueprintNativeEvent, Category="GMS|AnimationLayer")
void OnUnlinked();
virtual void OnUnlinked_Implementation();
/**
* Applies settings to the animation layer.
* 向动画层应用设置。
* @param Setting The setting object to apply, cast to the desired type. 要应用的设置对象,可转换为所需类型。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|AnimationLayer")
void ApplySetting(const UGMS_AnimLayerSetting* Setting);
virtual void ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting);
/**
* Resets the settings of the animation layer.
* 重置动画层的设置。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|AnimationLayer")
void ResetSetting();
virtual void ResetSetting_Implementation();
/**
* Initializes the animation.
* 初始化动画。
*/
virtual void NativeInitializeAnimation() override;
/**
* Called when the game starts.
* 游戏开始时调用。
*/
virtual void NativeBeginPlay() override;
/**
* Maps animation state names to gameplay tags for checking node relevance.
* 将动画状态名称映射到游戏标签以检查节点相关性。
* @note Used to determine if an animation state node is active via NodeRelevantTags in the main animation instance. 用于通过主动画实例中的NodeRelevantTags确定动画状态节点是否活跃。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Settings", meta=(TitleProperty="Tag"))
TArray<FGMS_AnimStateNameToTag> AnimStateNameToTagMapping;
};

View File

@@ -0,0 +1,17 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_AnimLayer.h"
#include "GMS_AnimLayer_Additive.generated.h"
/**
* Base class for additive animation layer settings.
* 附加动画层设置的基类。
*/
UCLASS(Abstract, Blueprintable)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayerSetting_Additive : public UGMS_AnimLayerSetting
{
GENERATED_BODY()
};

View File

@@ -0,0 +1,81 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_AnimLayer.h"
#include "Settings/GMS_SettingObjectLibrary.h"
#include "GMS_AnimLayer_Overlay.generated.h"
#pragma region Blend Data Structures
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_PoseBlendSetting
{
GENERATED_BODY()
virtual ~FGMS_AnimData_PoseBlendSetting() = default;
/**
* The overall adoption of the pose(0~1).
* 姿势的整体采用度0~11是开启0是关闭。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(ClampMin=0, ClampMax=1, DisplayPriority=0))
float BlendAmount{1.0f};
};
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_PoseBlendSetting_TwoParams : public FGMS_AnimData_PoseBlendSetting
{
GENERATED_BODY()
/**
* Overall adoption of Montage playing on this slot (0~1)
* 在此槽上播放的Montage的整体采用度0~1
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(ClampMin=0, ClampMax=1, DisplayPriority=2))
float SlotBlendAmount{1.0f};
};
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_PoseBlendSetting_ThreeParams : public FGMS_AnimData_PoseBlendSetting_TwoParams
{
GENERATED_BODY()
/**
* How much to blend the overlay pose with the underlying motion.
* 叠加姿势与底层运动的混合程度。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(ClampMin=0, ClampMax=1, DisplayPriority=1))
float AdditiveBlendAmount{0.0f};
};
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_PoseBlendSetting_FourParams : public FGMS_AnimData_PoseBlendSetting_ThreeParams
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category="GMS", meta=(ClampMin=0, ClampMax=1, DisplayPriority=3))
bool bMeshSpace{true};
};
#pragma endregion
/**
* Base class for overlay animation layer settings.
* 叠加动画层设置的基类。
*/
UCLASS(Abstract, Blueprintable)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayerSetting_Overlay : public UGMS_AnimLayerSetting
{
GENERATED_BODY()
public:
/**
* Checks if the overlay mode is valid.
* 检查叠加模式是否有效。
* @param NewOverlayMode The overlay mode to check. 要检查的叠加模式。
* @return True if the overlay mode is valid, false otherwise. 如果叠加模式有效则返回true否则返回false。
*/
virtual bool IsValidForOverlayMode(const FGameplayTag& NewOverlayMode) const { return true; };
};

View File

@@ -0,0 +1,351 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_AnimLayer.h"
#include "Animation/AnimExecutionContext.h"
#include "Animation/AnimNodeReference.h"
#include "GameplayTagContainer.h"
#include "GMS_AnimLayer_Overlay.h"
#include "GMS_AnimState.h"
#include "InstancedStruct.h"
#include "StructUtils/InstancedStruct.h"
#include "GMS_AnimLayer_Overlay_ParallelPoseStack.generated.h"
#pragma region Settings
/**
* The body mask type for human body.
* 针对人的身体遮罩。
* @attention The enum order also controls the override priority.枚举顺序同时控制了姿势覆盖的优先级。
*/
UENUM(BlueprintType)
enum class EGMS_BodyMask : uint8
{
Head,
ArmLeft,
ArmRight,
UpperBody,
LowerBody,
FullBody,
MAX UMETA(Hidden)
};
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_BodyPose
{
GENERATED_BODY()
virtual ~FGMS_AnimData_BodyPose() = default;
/**
* Gameplay tag query against to main anim instance's relevance tags(the tags representing the relevant/active state of the anim state machine nodes).
* 针对主动画实例的相关性标签的查询,相关性标签指:用于标识动画状态机节点是否激活的标签。
* @details This pose will be considered if the tags matches this query.此姿势会在标签匹配此查询时被考虑。
* @note Left empty will bypass this filter. 留空则不应用此过滤。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(Categories="GMS.SM"))
FGameplayTagQuery RelevanceQuery;
/**
* Gameplay tag query against to the movement system's owned tags.
* 针对运动系统组件所拥有标签的查询。
* @details This pose will be considered if the tags matches this query.此姿势会在标签匹配此查询时被考虑。
* @note Left empty will bypass this filter. 留空则不应用此过滤。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FGameplayTagQuery TagQuery;
/**
* The pose sequence.
* 姿势。
*/
UPROPERTY(EditAnywhere, Category="GMS")
TObjectPtr<UAnimSequence> Pose{nullptr};
/**
* The pose time.
* 姿势的帧位置。
*/
UPROPERTY(EditAnywhere, Category="GMS")
float PoseExplicitTime{0.0f};
UPROPERTY()
int32 Priority = 999;
virtual EGMS_BodyMask GetBodyPart() const { return EGMS_BodyMask::FullBody; }
virtual void Reset()
{
Pose = nullptr;
Priority = 999;
};
virtual void UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const
{
};
bool IsValid() const { return Pose != nullptr; }
#if WITH_EDITORONLY_DATA
UPROPERTY(EditAnywhere, Category="GES", meta=(EditCondition=false, EditConditionHides))
FString EditorFriendlyName;
virtual void PreSave();
#endif
};
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_BodyPose_Full : public FGMS_AnimData_BodyPose
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_ThreeParams HeadBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_FourParams ArmLeftBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_FourParams ArmRightBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting HandLeftBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting HandRightBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_ThreeParams SpineBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_TwoParams PelvisBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_TwoParams LegsBlend;
virtual EGMS_BodyMask GetBodyPart() const override { return EGMS_BodyMask::FullBody; }
virtual void UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const override;
};
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_BodyPose_Upper : public FGMS_AnimData_BodyPose
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_ThreeParams HeadBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_FourParams ArmLeftBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_FourParams ArmRightBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting HandLeftBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting HandRightBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_ThreeParams SpineBlend;
virtual EGMS_BodyMask GetBodyPart() const override { return EGMS_BodyMask::UpperBody; }
virtual void UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const override;
};
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_BodyPose_Head : public FGMS_AnimData_BodyPose
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_ThreeParams Blend;
virtual EGMS_BodyMask GetBodyPart() const override { return EGMS_BodyMask::Head; }
virtual void UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const override;
};
USTRUCT(meta=(Hidden))
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_BodyPose_Arms : public FGMS_AnimData_BodyPose
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_FourParams Blend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting HandBlend;
};
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_BodyPose_ArmLeft : public FGMS_AnimData_BodyPose_Arms
{
GENERATED_BODY()
virtual EGMS_BodyMask GetBodyPart() const override { return EGMS_BodyMask::ArmLeft; }
virtual void UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const override;
};
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_BodyPose_ArmRight : public FGMS_AnimData_BodyPose_Arms
{
GENERATED_BODY()
virtual EGMS_BodyMask GetBodyPart() const override { return EGMS_BodyMask::ArmRight; }
virtual void UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const override;
};
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData_BodyPose_Lower : public FGMS_AnimData_BodyPose
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_TwoParams PelvisBlend;
UPROPERTY(EditAnywhere, Category="GMS", meta=(DisplayAfter="PoseExplicitTime"))
FGMS_AnimData_PoseBlendSetting_TwoParams LegsBlend;
virtual EGMS_BodyMask GetBodyPart() const override { return EGMS_BodyMask::LowerBody; }
virtual void UpdateLayeringState(FGMS_AnimState_Layering& LayeringState) const override;
};
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_OverlayModeSetting_ParallelPoseStack
{
GENERATED_BODY()
virtual ~FGMS_OverlayModeSetting_ParallelPoseStack() = default;
/**
* Unique tag for this overlay mode.
* 此叠加模式的唯一标签。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(Categories="GMS.OverlayMode"))
FGameplayTag Tag;
UPROPERTY(EditAnywhere, Category="GMS")
TObjectPtr<UAnimSequence> BasePose = nullptr;
UPROPERTY(EditAnywhere, Category="GMS")
TArray<TInstancedStruct<FGMS_AnimData_BodyPose_Full>> FullBodyPoses;
UPROPERTY(EditAnywhere, Category="GMS")
TArray<TInstancedStruct<FGMS_AnimData_BodyPose_Upper>> UpperBodyPoses;
UPROPERTY(EditAnywhere, Category="GMS")
TArray<TInstancedStruct<FGMS_AnimData_BodyPose_Lower>> LowerBodyPoses;
UPROPERTY(EditAnywhere, Category="GMS")
TArray<TInstancedStruct<FGMS_AnimData_BodyPose_ArmLeft>> ArmLeftPoses;
UPROPERTY(EditAnywhere, Category="GMS")
TArray<TInstancedStruct<FGMS_AnimData_BodyPose_ArmRight>> ArmRightPoses;
UPROPERTY(EditAnywhere, Category="GMS")
TArray<TInstancedStruct<FGMS_AnimData_BodyPose_Head>> HeadPoses;
#if WITH_EDITORONLY_DATA
virtual void PreSave();
#endif
};
struct GENERICMOVEMENTSYSTEM_API FGMS_BodyPartOverridePolicy
{
FGMS_BodyPartOverridePolicy();
bool CanOverride(EGMS_BodyMask NewPart, EGMS_BodyMask ExistingPart, int32 NewPriority, int32 ExistingPriority) const;
void ApplyCoverage(EGMS_BodyMask BodyPart, TArray<TInstancedStruct<FGMS_AnimData_BodyPose>>& SelectedPoses, const TInstancedStruct<FGMS_AnimData_BodyPose>& NewPose, int32 NewPriority) const;
TMap<EGMS_BodyMask, TArray<EGMS_BodyMask>> FallbackChain;
};
/**
* Anim layer setting for ParallelPoseStack overlay system.
* 针对"并行姿势栈"的动画叠加系统设置。
* @details Similar to PoseStack, but allowing multiple body part has different overlay at the same time.与姿势栈相似,但允许同时让身体的不同部位有不同的姿势。
*/
UCLASS(NotBlueprintable)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayerSetting_Overlay_ParallelPoseStack final : public UGMS_AnimLayerSetting_Overlay
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category="Settings", meta=(EditCondition=false, EditConditionHides))
TMap<FGameplayTag, FGMS_OverlayModeSetting_ParallelPoseStack> AcceleratedOverlayModes;
protected:
UPROPERTY(EditAnywhere, Category="Settings", meta=(TitleProperty="Tag"))
TArray<FGMS_OverlayModeSetting_ParallelPoseStack> OverlayModes;
#if WITH_EDITORONLY_DATA
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
#endif
};
#pragma endregion
/**
* Anim layer implementation for ParallelPoseStack overlay system.
* 针对"并行姿势栈"的动画叠加系统实现。
*/
UCLASS(Abstract)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayer_Overlay_ParallelPoseStack : public UGMS_AnimLayer
{
GENERATED_BODY()
protected:
virtual void ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting) override;
virtual void ResetSetting_Implementation() override;
virtual void NativeInitializeAnimation() override;
virtual void NativeThreadSafeUpdateAnimation(float DeltaSeconds) override;
const FGMS_OverlayModeSetting_ParallelPoseStack& GetOverlayModeSetting() const;
void UpdateAnim(const FAnimUpdateContext& Context, const FAnimNodeReference& Node, const EGMS_BodyMask& BodyMask, const FGMS_AnimData_BodyPose& BodyPose);
void SelectPoses(const FGameplayTagContainer& Tags, const FGameplayTagContainer& Nodes);
void UpdateLayeringState(float DeltaSeconds);
void UpdateLayeringSmoothState(float DeltaSeconds);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|Animation", meta=(BlueprintThreadSafe))
void BasePose_AnimUpdate(UPARAM(ref)
FAnimUpdateContext& Context, UPARAM(ref)
FAnimNodeReference& Node);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|Animation", meta=(BlueprintThreadSafe))
void BodyPart_AnimUpdate(UPARAM(ref)
FAnimUpdateContext& Context, UPARAM(ref)
FAnimNodeReference& Node, EGMS_BodyMask BodyMask);
UPROPERTY()
TArray<TInstancedStruct<FGMS_AnimData_BodyPose>> SelectedBodyPoses; // Indexed by EGMS_BodyPart
UPROPERTY()
TObjectPtr<const UGMS_AnimLayerSetting_Overlay_ParallelPoseStack> CurrentSetting = nullptr;
UPROPERTY(Transient)
FGameplayTag CurrentOverlayMode;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
TObjectPtr<UAnimSequence> BasePose = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
bool bHasValidSetting = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
FGMS_AnimState_LayeringSmooth LayeringSmoothState;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
bool bArmLeftMeshSpace{false};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
bool bArmRightMeshSpace{false};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
FGMS_AnimState_Layering LayeringState;
FGMS_BodyPartOverridePolicy OverridePolicy;
};

View File

@@ -0,0 +1,381 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_AnimLayer.h"
#include "GMS_AnimLayer_Overlay.h"
#include "Settings/GMS_SettingObjectLibrary.h"
#include "GMS_AnimLayer_Overlay_ParallelSequenceStack.generated.h"
struct FCachedAnimStateData;
/**
* Single entry within "GMS_ParallelSequenceStack".
* "GMS_ParallelSequenceStack"中的单个条目
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_ParallelSequenceStackEntry
{
GENERATED_BODY()
/**
* Gameplay tag query against to the movement system's owned tags.
* 针对运动系统组件所拥有标签的查询。
* @details This sequence will be considered if the tags matches this query.此序列会在标签匹配此查询时被考虑。
* @note Left empty will bypass this filter. 留空则不应用此过滤。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
FGameplayTagQuery TagQuery;
/**
* Animation sequence for the overlay.
* 叠加的动画序列。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
TObjectPtr<UAnimSequenceBase> Sequence = nullptr;
/**
* Blend weight for the overlay.
* 叠加的混合权重。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(ClampMax=1, ClampMin=0))
float BlendWeight = 1.0f;
/**
* Whether to blend in mesh space.
* 是否在网格空间中混合。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
bool MeshSpaceBlend = true;
/**
* Play mode for the overlay animation.
* 叠加动画的播放模式。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
EGMS_OverlayPlayMode PlayMode{EGMS_OverlayPlayMode::SequenceEvaluator};
/**
* Start position for sequence player mode.
* 序列播放器模式的起始位置。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(ClampMin=0, EditCondition="PlayMode == EGMS_OverlayPlayMode::SequencePlayer", EditConditionHides))
float StartPosition = 0.0f;
/**
* Explicit time for sequence evaluator mode.
* 序列评估器模式的明确时间。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(ClampMin=0, EditCondition="PlayMode == EGMS_OverlayPlayMode::SequenceEvaluator", EditConditionHides))
float ExplicitTime = 0.0f;
/**
* Blend mode for the overlay animation.
* 叠加动画的混合模式。
* @attention Use branch filters for multiple skeletons to improve reusability. 如果项目中使用多个骨架,建议使用分支过滤器以提高复用性。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
EGMS_LayeredBoneBlendMode BlendMode{EGMS_LayeredBoneBlendMode::BlendMask};
/**
* Blend mask name for the overlay.
* 叠加的混合遮罩名称。
* @attention Must be defined in the skeleton first. 必须先在骨架中定义。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(EditCondition="BlendMode == EGMS_LayeredBoneBlendMode::BlendMask", EditConditionHides))
FName BlendMaskName;
/**
* Branch filters for the overlay.
* 叠加的分支过滤器。
* @attention Refer to documentation for usage details. 请参阅文档了解使用详情。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(EditCondition="BlendMode == EGMS_LayeredBoneBlendMode::BranchFilter", EditConditionHides))
FGMS_InputBlendPose BranchFilters;
/**
* Speed to blend to the specified blend weight.
* 混合到指定混合权重的速度。
* @attention Zero means instant blending. 为零表示立即混合。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(ClampMin=0))
float BlendInSpeed = 10.f;
/**
* Speed to blend back to zero weight.
* 混合回零权重的速度。
* @attention Zero means instant return to zero. 为零表示立即归零。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(ClampMin=0))
float BlendOutSpeed = 10.f;
/**
* Indicates if the overlay data is valid.
* 指示叠加数据是否有效。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
bool bValid{false};
/**
* Equality operator for overlay data.
* 叠加数据的相等比较运算符。
*/
friend bool operator==(const FGMS_ParallelSequenceStackEntry& Lhs, const FGMS_ParallelSequenceStackEntry& RHS)
{
return Lhs.Sequence == RHS.Sequence
&& Lhs.BlendMode == RHS.BlendMode
&& Lhs.MeshSpaceBlend == RHS.MeshSpaceBlend
&& Lhs.bValid == RHS.bValid;
}
/**
* Inequality operator for overlay data.
* 叠加数据的不相等比较运算符。
*/
friend bool operator!=(const FGMS_ParallelSequenceStackEntry& Lhs, const FGMS_ParallelSequenceStackEntry& RHS)
{
return !(Lhs == RHS);
}
#if WITH_EDITORONLY_DATA
/**
* Validates the overlay animation data.
* 验证叠加动画数据。
*/
void Validate();
/**
* Friendly message for displaying in the editor.
* 在编辑器中显示的友好消息。
*/
UPROPERTY(VisibleAnywhere, Category = "GMS", Meta = (EditCondition = False, EditConditionHides))
FString EditorMessage;
#endif
};
/**
* A single parallel sequence stack
* 堆叠叠加动画数据的结构体。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_ParallelSequenceStack
{
GENERATED_BODY()
/**
* Animation nodes that determine overlay relevance.
* 确定叠加相关性的动画节点。
* @attention Configure state-to-tag mapping in the main AnimInstance or derived GMS_AnimLayer. 在主AnimInstance或派生的GMS_AnimLayer中配置状态到标签的映射。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(Categories="GMS.SM"))
FGameplayTagContainer TargetAnimNodes;
/**
* List of potential overlays, with the first matching one selected.
* 潜在叠加列表,第一个匹配的将被选用。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(TitleProperty="EditorMessage"))
TArray<FGMS_ParallelSequenceStackEntry> Overlays;
#if WITH_EDITORONLY_DATA
/**
* Friendly name for displaying in the editor.
* 在编辑器中显示的友好名称。
*/
UPROPERTY(VisibleAnywhere, Category = "GMS", Meta = (EditCondition = False, EditConditionHides))
FString EditorFriendlyName;
#endif
};
/**
* Parallel Sequence Stacks for certain overlay mode.
* 针对特定叠加模式的并行Sequence栈
*/
USTRUCT(BlueprintType)
struct FGMS_OverlayModeSetting_ParallelSequenceStack
{
GENERATED_BODY()
/**
* Unique tag for this overlay mode.
* 此叠加模式的唯一标签。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(Categories="GMS.OverlayMode"))
FGameplayTag Tag;
/**
* List of parallel sequence stack
* 用于并行混合的动画序列栈列表。
* @attention Later stacks have higher priority; each stack selects one entry from multiple candidates. 越靠后的栈优先级越高;每个栈从多个候选动画中选择一个。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(TitleProperty="EditorFriendlyName"))
TArray<FGMS_ParallelSequenceStack> Stacks;
};
/**
* Struct for the state of a stacked overlay.
* 栈式叠加状态的结构体。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_ParallelSequenceStackState
{
GENERATED_BODY()
/**
* Indicates if the target animation nodes are relevant.
* 指示目标动画节点是否相关。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bRelevant{false};
/**
* Current blend weight of the overlay.
* 叠加的当前混合权重。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float BlendWeight{0.0f};
/**
* Speed to blend out the overlay.
* 叠加混合退出的速度。
*/
float BlendOutSpeed{0.0f};
/**
* Overlay animation data.
* 叠加动画数据。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FGMS_ParallelSequenceStackEntry Overlay;
};
/**
* Anim layer setting for ParallelSequenceStack overlay system.
* 针对"并行序列栈"的动画叠加系统设置。
* @attention Its more recommended to use SequenceStack instead of this one.通常你应该使用序列栈,而不是这个。
*/
UCLASS(NotBlueprintable)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayerSetting_Overlay_ParallelSequenceStack final : public UGMS_AnimLayerSetting_Overlay
{
GENERATED_BODY()
public:
/**
* Map of accelerated overlay modes.
* 加速叠加模式的映射。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(EditCondition=false, EditConditionHides))
TMap<FGameplayTag, FGMS_OverlayModeSetting_ParallelSequenceStack> AcceleratedOverlayModes;
protected:
/**
* List of overlay mode settings.
* 叠加模式设置列表。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings", meta=(TitleProperty="Tag"))
TArray<FGMS_OverlayModeSetting_ParallelSequenceStack> OverlayModes;
#if WITH_EDITORONLY_DATA
/**
* Called before saving the object.
* 在保存对象之前调用。
* @param SaveContext The save context. 保存上下文。
*/
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
#endif
};
/**
* Anim layer implementation for ParallelSequenceStack overlay system.
* 针对"并行序列栈"的动画叠加系统实现。
*/
UCLASS(Abstract)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayer_Overlay_ParallelSequenceStack : public UGMS_AnimLayer
{
GENERATED_BODY()
friend UGMS_AnimLayerSetting_Overlay_ParallelSequenceStack;
public:
/**
* Called when the component begins play.
* 组件开始播放时调用。
*/
virtual void NativeBeginPlay() override;
/**
* Updates the animation.
* 更新动画。
* @param DeltaSeconds Time since the last update. 自上次更新以来的时间。
*/
virtual void NativeUpdateAnimation(float DeltaSeconds) override;
/**
* Updates the animation in a thread-safe manner.
* 以线程安全的方式更新动画。
* @param DeltaSeconds Time since the last update. 自上次更新以来的时间。
*/
virtual void NativeThreadSafeUpdateAnimation(float DeltaSeconds) override;
/**
* Applies the specified animation layer setting.
* 应用指定的动画层设置。
* @param Setting The animation layer setting to apply. 要应用的动画层设置。
*/
virtual void ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting) override;
/**
* Resets the animation layer setting.
* 重置动画层设置。
*/
virtual void ResetSetting_Implementation() override;
protected:
/**
* Refreshes the relevance of the overlay stacks.
* 刷新叠加堆栈的相关性。
*/
virtual void RefreshRelevance();
/**
* Refreshes the blending of the overlay stacks.
* 刷新叠加堆栈的混合。
* @param DeltaSeconds Time since the last update. 自上次更新以来的时间。
*/
virtual void RefreshBlend(float DeltaSeconds);
/**
* Previous overlay setting.
* 前一个叠加设置。
*/
UPROPERTY()
TObjectPtr<const UGMS_AnimLayerSetting_Overlay_ParallelSequenceStack> PrevSetting{nullptr};
/**
* Previous overlay mode tag.
* 前一个叠加模式标签。
*/
UPROPERTY()
FGameplayTag PrevOverlayMode;
/**
* List of overlay stacks.
* 叠加堆栈列表。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings", meta=(TitleProperty="EditorFriendlyName"))
TArray<FGMS_ParallelSequenceStack> OverlayStacks;
/**
* List of overlay stack states.
* 叠加堆栈状态列表。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
TArray<FGMS_ParallelSequenceStackState> OverlayStackStates;
/**
* Maximum number of overlay layers.
* 最大叠加层数。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State", meta=(ClampMin=4))
int32 MaxLayers{10};
};

View File

@@ -0,0 +1,439 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_AnimLayer.h"
#include "GMS_AnimLayer_Overlay.h"
#include "GMS_AnimState.h"
#include "Settings/GMS_SettingObjectLibrary.h"
#include "GMS_AnimLayer_Overlay_PoseStack.generated.h"
#pragma region Deprecated
/**
* Enum for pose overlay setting types.
* 姿势叠加设置类型的枚举。
*/
UENUM(BlueprintType)
enum class EGMS_PoseOverlaySettingType: uint8
{
Simple,
Layered
};
/**
* Struct for simple pose overlay animation data.
* 简单姿势叠加动画数据的结构体。
*/
USTRUCT()
struct FGMS_AnimData_PoseOverlay_Simple
{
GENERATED_BODY()
/**
* Idle pose animation sequence.
* 空闲姿势动画序列。
*/
UPROPERTY(EditAnywhere, Category = "GMS")
TObjectPtr<UAnimSequence> IdlePose{nullptr};
/**
* Explicit time for the idle pose.
* 空闲姿势的明确时间。
*/
UPROPERTY(EditAnywhere, Category = "GMS")
float IdlePoseExplicitTime{0};
/**
* Moving pose animation sequence.
* 移动姿势动画序列。
*/
UPROPERTY(EditAnywhere, Category = "GMS")
TObjectPtr<UAnimSequence> MovingPose{nullptr};
/**
* Explicit time for the moving pose.
* 移动姿势的明确时间。
*/
UPROPERTY(EditAnywhere, Category = "GMS")
float MovingPoseExplicitTime{0};
/**
* Aiming sweep pose animation sequence.
* 瞄准扫动画序列。
*/
UPROPERTY(EditAnywhere, Category = "GMS")
TObjectPtr<UAnimSequence> AimingSweepPose{nullptr};
};
/**
* Struct for layered pose overlay anim data.
* 分层姿势叠加动画数据的结构体。
*/
USTRUCT()
struct FGMS_AnimData_PoseOverlay_Layered
{
GENERATED_BODY()
/**
* Gameplay tag query for layered pose.
* 分层姿势的游戏标签查询。
*/
UPROPERTY(EditAnywhere, Category = "GMS")
FGameplayTagQuery TagQuery;
/**
* Idle pose animation sequence for layered pose.
* 分层姿势的空闲姿势动画序列。
*/
UPROPERTY(EditAnywhere, Category = "GMS")
TObjectPtr<UAnimSequence> IdlePose{nullptr};
/**
* Explicit time for the idle pose in layered pose.
* 分层姿势中空闲姿势的明确时间。
*/
UPROPERTY(EditAnywhere, Category = "GMS")
float IdlePoseExplicitTime{0};
/**
* Moving pose animation sequence for layered pose.
* 分层姿势的移动姿势动画序列。
*/
UPROPERTY(EditAnywhere, Category = "GMS")
TObjectPtr<UAnimSequence> MovingPose{nullptr};
/**
* Explicit time for the moving pose in layered pose.
* 分层姿势中移动姿势的明确时间。
*/
UPROPERTY(EditAnywhere, Category = "GMS")
float MovingPoseExplicitTime{0};
/**
* Aiming sweep pose animation sequence for layered pose.
* 分层姿势的瞄准扫动画序列。
*/
UPROPERTY(EditAnywhere, Category = "GMS")
TObjectPtr<UAnimSequence> AimingSweepPose{nullptr};
};
#pragma endregion
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_PerBodyPoseBlendSetting
{
GENERATED_BODY()
virtual ~FGMS_PerBodyPoseBlendSetting() = default;
UPROPERTY(EditAnywhere, Category="GMS")
FGMS_AnimData_PoseBlendSetting_ThreeParams HeadBlend;
UPROPERTY(EditAnywhere, Category="GMS")
FGMS_AnimData_PoseBlendSetting_FourParams ArmLeftBlend;
UPROPERTY(EditAnywhere, Category="GMS")
FGMS_AnimData_PoseBlendSetting_FourParams ArmRightBlend;
UPROPERTY(EditAnywhere, Category="GMS")
FGMS_AnimData_PoseBlendSetting HandLeftBlend;
UPROPERTY(EditAnywhere, Category="GMS")
FGMS_AnimData_PoseBlendSetting HandRightBlend;
UPROPERTY(EditAnywhere, Category="GMS")
FGMS_AnimData_PoseBlendSetting_ThreeParams SpineBlend;
UPROPERTY(EditAnywhere, Category="GMS")
FGMS_AnimData_PoseBlendSetting_TwoParams PelvisBlend;
UPROPERTY(EditAnywhere, Category="GMS")
FGMS_AnimData_PoseBlendSetting_TwoParams LegsBlend;
//Read layering setting from sequence curves.
void ApplyFromSequence(const UAnimSequence* InSequence, float ExplicitTime);
//Set blend setting to layering state.
virtual void ApplyToLayeringState(FGMS_AnimState_Layering& InLayeringState) const;
//Read setting from layering state.
virtual void ApplyFromLayeringState(const FGMS_AnimState_Layering& InLayeringState);
};
/**
* Single entry within GMS_OverlayModeSetting_PoseStack
* GMS_OverlayModeSetting_PoseStack中的单个条目。
*
*/
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_PoseStackEntry
{
GENERATED_BODY()
/**
* Gameplay tag query against to main anim instance's relevance tags(the tags representing the relevant/active state of the anim state machine nodes).
* 针对主动画实例的相关性标签的查询,相关性标签指:用于标识动画状态机节点是否激活的标签。
* @details This pose will be considered if the tags matches this query.此姿势会在标签匹配此查询时被考虑。
* @note Left empty will bypass this filter. 留空则不应用此过滤。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(Categories="GMS.SM"))
FGameplayTagQuery RelevanceQuery;
/**
* Gameplay tag query against to the movement system's owned tags.
* 针对运动系统组件所拥有标签的查询。
* @details This pose will be considered if the tags matches this query.此姿势会在标签匹配此查询时被考虑。
* @note Left empty will bypass this filter. 留空则不应用此过滤。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FGameplayTagQuery TagQuery;
/**
* The sequence for this pose.
* 此姿势的动画序列。
*/
UPROPERTY(EditAnywhere, Category="GMS")
TObjectPtr<UAnimSequence> Pose{nullptr};
/**
* Controls the blend weight for each body parts.
* 控制每一个身体部位的混合权重。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FGMS_PerBodyPoseBlendSetting PoseBlend;
/**
* Explicit time for the pose.
* 姿势的明确播放时间。
*/
UPROPERTY(EditAnywhere, Category="GMS")
float ExplicitTime{0};
/**
* Aiming sweep pose animation sequence for layered pose.
* 分层姿势的瞄准扫动画序列。
*/
UPROPERTY(EditAnywhere, Category="GMS")
TObjectPtr<UAnimSequence> AimingSweepPose{nullptr};
#if WITH_EDITORONLY_DATA
UPROPERTY(EditAnywhere, Category = "GMS", meta = (EditCondition = false, EditConditionHides))
FString EditorFriendlyName;
#endif
};
/**
* Pose overlay setting for specific overlay mode.
* 针对特定动画叠加模式的姿势叠加设置。
*/
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_OverlayModeSetting_PoseStack
{
GENERATED_BODY()
/**
* Unique tag for this overlay mode.
* 此叠加模式的唯一标签。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(Categories="GMS.OverlayMode"))
FGameplayTag Tag;
/**
* Base pose animation sequence.
* 基础姿势动画序列。
*/
UPROPERTY(EditAnywhere, Category="GMS")
TObjectPtr<UAnimSequence> BasePose{nullptr};
/**
* Potential dynamic poses.
* 潜在的动态poses。
* @note To ensure smooth pose switching, Avoid using "multi frames sequence with different explicit time setup", favors "single frame sequence with 0 explicit time setup."
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(TitleProperty="EditorFriendlyName"))
TArray<FGMS_PoseStackEntry> Poses;
#if WITH_EDITORONLY_DATA
/**
* Type of pose overlay setting.
* 姿势叠加设置的类型。
*/
UE_DEPRECATED (1.5, "deprecated and will be removed in 1.6")
UPROPERTY(EditAnywhere, Category = "Deprecated", meta = (EditCondition = false, EditConditionHides))
EGMS_PoseOverlaySettingType PoseOverlaySettingType{EGMS_PoseOverlaySettingType::Simple};
/**
* Simple pose overlay settings.
* 简单姿势叠加设置。
*/
UE_DEPRECATED (1.5, "deprecated and will be removed in 1.6")
UPROPERTY(EditAnywhere, Category = "Deprecated", meta = (EditCondition = false, EditConditionHides))
FGMS_AnimData_PoseOverlay_Simple SimplePoseSetting;
/**
* Layered pose overlay settings.
* 分层姿势叠加设置。
*/
UE_DEPRECATED (1.5, "deprecated and will be removed in 1.6")
UPROPERTY(EditAnywhere, Category = "Deprecated", meta = (EditCondition = false, EditConditionHides))
TArray<FGMS_AnimData_PoseOverlay_Layered> LayeredPoseSetting;
#endif
};
/**
* Anim layer setting for PoseStack overlay system.
* 针对"姿势栈"的动画叠加系统设置。
* @details Similar to ALS's layering system, but more dynamic and easy to use. 类似于ALS的叠层系统但更动态且易于使用。
*/
UCLASS(NotBlueprintable)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayerSetting_Overlay_PoseStack : public UGMS_AnimLayerSetting_Overlay
{
GENERATED_BODY()
public:
/**
* Map of accelerated overlay modes.
* 加速叠加模式的映射。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(EditCondition=false, EditConditionHides))
TMap<FGameplayTag, FGMS_OverlayModeSetting_PoseStack> AcceleratedOverlayModes;
/**
* Checks if the overlay mode is valid.
* 检查叠加模式是否有效。
* @param NewOverlayMode The overlay mode to check. 要检查的叠加模式。
* @return True if the overlay mode is valid, false otherwise. 如果叠加模式有效则返回true否则返回false。
*/
virtual bool IsValidForOverlayMode(const FGameplayTag& NewOverlayMode) const override;
protected:
/**
* List of pose overlay settings.
* 姿势叠加设置列表。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(TitleProperty="Tag"))
TArray<FGMS_OverlayModeSetting_PoseStack> OverlayModes;
#if WITH_EDITOR
public:
UFUNCTION(BlueprintCallable, CallInEditor, Category = "GMS")
void RunDataMigration(bool bResetDeprecatedSettings = false);
UFUNCTION(BlueprintCallable, CallInEditor, Category = "GMS")
static void RunDataMigrationFromDefinition(UGMS_MovementDefinition* InDefinition, bool bResetDeprecatedSettings = false);
/**
* Called before saving the object.
* 在保存对象之前调用。
* @param SaveContext The save context. 保存上下文。
*/
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
#endif
};
/**
* Anim layer implementation for PoseStack overlay system.
* 针对"姿势栈"的动画叠加系统实现。
*/
UCLASS(Abstract)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayer_Overlay_PoseStack : public UGMS_AnimLayer
{
GENERATED_BODY()
public:
/**
* Applies the specified animation layer setting.
* 应用指定的动画层设置。
* @param Setting The animation layer setting to apply. 要应用的动画层设置。
*/
virtual void ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting) override;
/**
* Resets the animation layer setting.
* 重置动画层设置。
*/
virtual void ResetSetting_Implementation() override;
bool SelectPose();
/**
* Updates the animation in a thread-safe manner.
* 以线程安全的方式更新动画。
* @param DeltaSeconds Time since the last update. 自上次更新以来的时间。
*/
virtual void NativeThreadSafeUpdateAnimation(float DeltaSeconds) override;
const FGMS_OverlayModeSetting_PoseStack& GetOverlayModeSetting() const;
virtual void UpdateLayeringSmoothState(float DeltaSeconds);
protected:
/**
* Base pose animation sequence.
* 基础姿势动画序列。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
TObjectPtr<UAnimSequence> BasePose{nullptr};
/**
* Current Selected Pose.
* 当前选择的Pose.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
TObjectPtr<UAnimSequence> Pose{nullptr};
UPROPERTY()
TObjectPtr<UAnimSequence> PrevPose{nullptr};
/**
* Explicit time for the current pose
* 当前姿势的明确时间。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
float ExplicitTime{0};
/**
* Aiming sweep pose animation sequence.
* 瞄准扫动画序列。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
TObjectPtr<UAnimSequence> AimingSweepPose{nullptr};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
bool bValidPose{false};
/**
* Indicates if the aiming pose is valid.
* 指示瞄准姿势是否有效。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
bool bValidAimingPose{false};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
bool bHasValidSetting = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
float LayeringSmoothSpeed{2.0f};
/**
* Current layering state.
* 当前分层状态。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
FGMS_AnimState_Layering LayeringState;
/**
* Current smoothed layering state.
* 当前的平滑分层状态。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
FGMS_AnimState_LayeringSmooth LayeringSmoothState;
/**
* Reference to the pose-based overlay settings.
* 基于姿势的叠加设置的引用。
*/
UPROPERTY(Transient)
TObjectPtr<const UGMS_AnimLayerSetting_Overlay_PoseStack> CurrentSetting;
UPROPERTY(Transient)
FGameplayTag CurrentOverlayMode;
};

View File

@@ -0,0 +1,276 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_AnimLayer_Overlay.h"
#include "GMS_AnimLayer_Overlay_SequenceStack.generated.h"
class UGMS_AnimLayerSetting_Overlay_ParallelSequenceStack;
/**
* Single entry within "GMS_OverlayModeSetting_SequenceStack".
* "GMS_OverlayModeSetting_SequenceStack"中的单个条目
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_SequenceStackEntry
{
GENERATED_BODY()
/**
* Gameplay tag query against to main anim instance's relevance tags(the tags representing the relevant/active state of the anim state machine nodes).
* 针对主动画实例的相关性标签的查询,相关性标签指:用于标识动画状态机节点是否激活的标签。
* @details This pose will be considered if the tags matches this query.此姿势会在标签匹配此查询时被考虑。
* @note Left empty will bypass this filter. 留空则不应用此过滤。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(Categories="GMS.SM"))
FGameplayTagQuery RelevanceQuery;
/**
* Gameplay tag query against to the movement system's owned tags.
* 针对运动系统组件所拥有标签的查询。
* @details This pose will be considered if the tags matches this query.此姿势会在标签匹配此查询时被考虑。
* @note Left empty will bypass this filter. 留空则不应用此过滤。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
FGameplayTagQuery TagQuery;
/**
* Animation sequence for the overlay.
* 叠加的动画序列。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
TObjectPtr<UAnimSequence> Sequence = nullptr;
/**
* Play mode for the overlay animation.
* 叠加动画的播放模式。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
EGMS_OverlayPlayMode PlayMode{EGMS_OverlayPlayMode::SequencePlayer};
/**
* Blend weight for the overlay.
* 叠加的混合权重。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(ClampMax=1, ClampMin=0))
float BlendWeight = 1.0f;
/**
* Speed to blend to the specified blend weight.
* 混合到指定混合权重的速度。
* @attention Zero means instant blending. 为零表示立即混合。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(ClampMin=0))
float BlendInSpeed = 10.f;
/**
* Speed to blend back to zero weight.
* 混合回零权重的速度。
* @attention Zero means instant return to zero. 为零表示立即归零。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(ClampMin=0))
float BlendOutSpeed = 10.f;
/**
* Whether to blend in mesh space.
* 是否在网格空间中混合。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
bool MeshSpaceBlend = true;
/**
* The start time of animation.
* 动画开始播放的事件。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
float AnimationTime = 0.0f;
/**
* Blend mode for the overlay animation.
* 叠加动画的混合模式。
* @attention Use branch filters for multiple skeletons to improve reusability. 如果项目中使用多个骨架,建议使用分支过滤器以提高复用性。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS")
EGMS_LayeredBoneBlendMode BlendMode{EGMS_LayeredBoneBlendMode::BlendMask};
/**
* Blend mask name for the overlay.
* 叠加的混合遮罩名称。
* @attention Must be defined in the skeleton first. 必须先在骨架中定义。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(EditCondition="BlendMode == EGMS_LayeredBoneBlendMode::BlendMask", EditConditionHides))
FName BlendMaskName;
/**
* Branch filters for the overlay.
* 叠加的分支过滤器。
* @attention Refer to documentation for usage details. 请参阅文档了解使用详情。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(EditCondition="BlendMode == EGMS_LayeredBoneBlendMode::BranchFilter", EditConditionHides))
FGMS_InputBlendPose BranchFilters;
/**
* Speed to blend to the specified blend weight.
* 混合到指定混合权重的速度。
* @attention Zero means instant blending. 为零表示立即混合。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(ClampMin=0))
float BlendTime = 0.2f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(ClampMin=0))
FName BlendProfile = NAME_None;
friend bool operator==(const FGMS_SequenceStackEntry& Lhs, const FGMS_SequenceStackEntry& RHS)
{
return Lhs.Sequence == RHS.Sequence
&& Lhs.BlendMode == RHS.BlendMode
&& Lhs.MeshSpaceBlend == RHS.MeshSpaceBlend;
}
friend bool operator!=(const FGMS_SequenceStackEntry& Lhs, const FGMS_SequenceStackEntry& RHS)
{
return !(Lhs == RHS);
}
#if WITH_EDITORONLY_DATA
/**
* Friendly message for displaying in the editor.
* 在编辑器中显示的友好消息。
*/
UPROPERTY(VisibleAnywhere, Category = "GMS", Meta = (EditCondition = False, EditConditionHides))
FString EditorFriendlyName;
#endif
};
/**
* Pose overlay setting for specific overlay mode.
* 针对特定动画叠加模式的姿势叠加设置。
*/
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_OverlayModeSetting_SequenceStack
{
GENERATED_BODY()
/**
* Unique tag for this overlay mode.
* 此叠加模式的唯一标签。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(Categories="GMS.OverlayMode"))
FGameplayTag Tag;
/**
* Potential dynamic sequences.
* 潜在的动态sequences。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(TitleProperty="EditorFriendlyName"))
TArray<FGMS_SequenceStackEntry> Sequences;
};
/**
* Anim layer setting for SequenceStack overlay system.
* 针对序列栈的动画叠加系统设置。
* @details Dynamically stacking different anim sequence. 可动态叠加不同的动画序列。
*/
UCLASS(NotBlueprintable)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayerSetting_Overlay_SequenceStack : public UGMS_AnimLayerSetting_Overlay
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category="GMS", meta=(EditCondition=false, EditConditionHides))
TMap<FGameplayTag, FGMS_OverlayModeSetting_SequenceStack> AcceleratedOverlayModes;
protected:
/**
* List of sequence overlay settings.
* 序列叠加设置列表。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(TitleProperty="Tag"))
TArray<FGMS_OverlayModeSetting_SequenceStack> OverlayModes;
#if WITH_EDITOR
public:
UFUNCTION(BlueprintCallable, CallInEditor, Category = "GMS")
void ConvertToSequenceStack(const UGMS_AnimLayerSetting_Overlay_ParallelSequenceStack* Src);
UFUNCTION(BlueprintCallable, CallInEditor, Category = "GMS")
static void ConvertToSequenceStackFromDefinition(UGMS_MovementDefinition* InDefinition);
/**
* Called before saving the object.
* 在保存对象之前调用。
* @param SaveContext The save context. 保存上下文。
*/
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
#endif
};
/**
*
*/
UCLASS()
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayer_Overlay_SequenceStack : public UGMS_AnimLayer
{
GENERATED_BODY()
public:
/**
* Applies the specified animation layer setting.
* 应用指定的动画层设置。
* @param Setting The animation layer setting to apply. 要应用的动画层设置。
*/
virtual void ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting) override;
/**
* Resets the animation layer setting.
* 重置动画层设置。
*/
virtual void ResetSetting_Implementation() override;
bool SelectSequence();
/**
* Updates the animation in a thread-safe manner.
* 以线程安全的方式更新动画。
* @param DeltaSeconds Time since the last update. 自上次更新以来的时间。
*/
virtual void NativeThreadSafeUpdateAnimation(float DeltaSeconds) override;
const FGMS_OverlayModeSetting_SequenceStack& GetOverlayModeSetting() const;
protected:
/**
* Current Selected Pose.
* 当前选择的Pose.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
FGMS_SequenceStackEntry Definition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
bool bHasValidDefinition = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
float BlendWeight{0.0f};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
float BlendOutSpeed{0.0f};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
TObjectPtr<UBlendProfile> BlendProfile{nullptr};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings")
bool bHasValidSetting = false;
/**
* Reference to the pose-based overlay settings.
* 基于姿势的叠加设置的引用。
*/
UPROPERTY(Transient)
TObjectPtr<const UGMS_AnimLayerSetting_Overlay_SequenceStack> CurrentSetting;
UPROPERTY(Transient)
FGameplayTag CurrentOverlayMode;
};

View File

@@ -0,0 +1,17 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_AnimLayer.h"
#include "GMS_AnimLayer_SkeletalControls.generated.h"
/**
* Base class for skeletal control animation layer settings.
* 骨骼控制动画层设置的基类。
*/
UCLASS(Abstract, Blueprintable)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayerSetting_SkeletalControls : public UGMS_AnimLayerSetting
{
GENERATED_BODY()
};

View File

@@ -0,0 +1,50 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_AnimLayer.h"
#include "UObject/Object.h"
#include "Runtime/Launch/Resources/Version.h"
#if ENGINE_MINOR_VERSION < 5
#include "InstancedStruct.h"
#else
#include "StructUtils/InstancedStruct.h"
#endif
#include "Settings/GMS_SettingObjectLibrary.h"
#include "GMS_AnimLayer_States.generated.h"
/**
* Animation data for state-based animation layers.
* 状态动画层的数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimData
{
GENERATED_BODY()
virtual ~FGMS_AnimData() = default;
/**
* Validates the animation data.
* 验证动画数据。
*/
virtual void Validate();
/**
* Indicates if the animation data is valid.
* 指示动画数据是否有效。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GMS", meta=(EditCondition=false, EditConditionHides))
bool bValid{false};
};
/**
* Base class for state-based animation layer settings.
* 状态动画层设置的基类。
* @details Inherit this class to create custom state-based animation layer settings. 继承此类以创建自定义状态动画层设置。
*/
UCLASS(Abstract, Blueprintable)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayerSetting_States : public UGMS_AnimLayerSetting
{
GENERATED_BODY()
};

View File

@@ -0,0 +1,18 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_AnimLayer.h"
#include "UObject/Object.h"
#include "GMS_AnimLayer_View.generated.h"
/**
* Base class for view animation layer settings.
* 视图动画层设置的基类。
*/
UCLASS(Abstract, Blueprintable)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayerSetting_View : public UGMS_AnimLayerSetting
{
GENERATED_BODY()
};

View File

@@ -0,0 +1,109 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_AnimLayer.h"
#include "GMS_AnimLayer_View.h"
#include "GMS_AnimLayer_View_Default.generated.h"
class UAimOffsetBlendSpace;
/**
* Native implementation of default view animation layer settings.
* 默认视图动画层设置的原生实现。
*/
UCLASS(NotBlueprintable)
class UGMS_AnimLayerSetting_View_Default final : public UGMS_AnimLayerSetting_View
{
GENERATED_BODY()
public:
/**
* Blend space for the view animation.
* 视图动画的混合空间。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Anim View")
TObjectPtr<UBlendSpace> BlendSpace;
/**
* Yaw angle offset for the view.
* 视图的偏航角偏移。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Anim View")
float YawAngleOffset = 0.0f;
/**
* Additional smoothing speed for aim offset.
* 瞄准偏移的额外平滑速度。
* @attention Zero means no additional smoothing. 为零表示无额外平滑。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Anim View", meta=(ClampMin=0))
float SmoothInterpSpeed = 5.0f;
/**
* Yaw angle limits for the view.
* 视图的偏航角限制。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Anim View")
FVector2D YawAngleLimit{-90.0f, 90.0f};
};
/**
* Native implementation of default view animation layer.
* 默认视图动画层的原生实现。
*/
UCLASS(Abstract)
class GENERICMOVEMENTSYSTEM_API UGMS_AnimLayer_View_Default : public UGMS_AnimLayer
{
GENERATED_BODY()
public:
/**
* Applies the specified animation layer setting.
* 应用指定的动画层设置。
* @param Setting The animation layer setting to apply. 要应用的动画层设置。
*/
virtual void ApplySetting_Implementation(const UGMS_AnimLayerSetting* Setting) override;
/**
* Resets the animation layer setting.
* 重置动画层设置。
*/
virtual void ResetSetting_Implementation() override;
/**
* Blend space for the view animation.
* 视图动画的混合空间。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Anim View")
TObjectPtr<UBlendSpace> BlendSpace;
/**
* Yaw angle offset for the view.
* 视图的偏航角偏移。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Anim View")
float YawAngleOffset = 0.0f;
/**
* Yaw angle limits for the view.
* 视图的偏航角限制。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Anim View")
FVector2D YawAngleLimit{ -90.0f, 90.0f };
/**
* Smoothing speed for the view animation.
* 视图动画的平滑速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Anim View", meta=(ClampMin=0))
float SmoothInterpSpeed = 0.0f;
/**
* Indicates if the blend space is valid.
* 指示混合空间是否有效。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Anim View")
bool bValidBlendSpace;
};

View File

@@ -0,0 +1,976 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_LocomotionEnumLibrary.h"
#include "Animation/TrajectoryTypes.h"
#include "BoneControllers/AnimNode_OffsetRootBone.h"
#include "UObject/Object.h"
#include "GMS_AnimState.generated.h"
class UAnimSequenceBase;
class UAnimSequence;
/**
* Stores locomotion-related animation state data.
* 存储与运动相关的动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_Locomotion
{
GENERATED_BODY()
/**
* World-space location of the character.
* 角色的世界空间位置。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector Location{ForceInit};
/**
* Displacement from the previous frame.
* 上一帧的位移。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float PreviousDisplacement{0.0f};
/**
* Speed of displacement (cm/s).
* 位移速度(厘米/秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float DisplacementSpeed{0.0f};
/**
* World-space rotation of the character.
* 角色的世界空间旋转。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FRotator Rotation{ForceInit};
/**
* Quaternion representation of the rotation.
* 旋转的四元数表示。
*/
UPROPERTY()
FQuat RotationQuaternion{ForceInit};
/**
* Current velocity vector.
* 当前速度向量。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector Velocity{ForceInit};
/**
* 2D local-space velocity vector.
* 2D本地空间速度向量。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector LocalVelocity2D{ForceInit};
/**
* Indicates if the character has velocity.
* 指示角色是否有速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bHasVelocity = false;
/**
* Current speed of the character (cm/s).
* 角色的当前速度(厘米/秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ForceUnits = "cm/s"))
float Speed{0.0f};
/**
* Yaw angle of the local velocity (degrees).
* 本地速度的偏航角(度)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float LocalVelocityYawAngle{0.0f};
/**
* Yaw angle of the local velocity with offset (degrees).
* 带偏移的本地速度偏航角(度)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float LocalVelocityYawAngleWithOffset{0.0f};
/**
* Cardinal direction of the local velocity.
* 本地速度的主要方向。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
EGMS_MovementDirection LocalVelocityDirection{EGMS_MovementDirection::Forward};
/**
* Cardinal direction of the local velocity without offset.
* 无偏移的本地速度主要方向。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
EGMS_MovementDirection LocalVelocityDirectionNoOffset{EGMS_MovementDirection::Forward};
/**
* Octagonal direction of the local velocity.
* 本地速度的八方向。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
EGMS_MovementDirection_8Way LocalVelocityOctagonalDirection{EGMS_MovementDirection_8Way::Forward};
/**
* Indicates if there is active input.
* 指示是否有活跃输入。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bHasInput{false};
/**
* Velocity acceleration vector.
* 速度加速度向量。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector VelocityAcceleration{ForceInit};
/**
* 2D local-space acceleration vector.
* 2D本地空间加速度向量。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector LocalAcceleration2D{ForceInit};
/**
* Indicates if the character is moving.
* 指示角色是否在移动。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bMoving{false};
/**
* Actor's rotation yaw speed.
* Actor的旋转偏航角变化速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float YawVelocity{0.0f};
/**
* Scale factor for animations.
* 动画的缩放因子。
*/
float Scale{1.0f};
/**
* Radius of the character's capsule.
* 角色胶囊体的半径。
*/
float CapsuleRadius{0.0f};
/**
* Half-height of the character's capsule.
* 角色胶囊体的一半高度。
*/
float CapsuleHalfHeight{0.0f};
/**
* Maximum acceleration of the character.
* 角色的最大加速度。
*/
float MaxAcceleration{0.0f};
/**
* Maximum braking deceleration of the character.
* 角色的最大制动减速度。
*/
float MaxBrakingDeceleration{0.0f};
/**
* Z value for walkable floor detection.
* 可行走地板检测的Z值。
*/
float WalkableFloorZ{0.0f};
};
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_Trajectory
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FTransformTrajectory Trajectory;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector PastVelocity{FVector::ZeroVector};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector CurrentVelocity{FVector::ZeroVector};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector FutureVelocity{FVector::ZeroVector};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float DesiredControllerYaw{0.0f};
};
/**
* Stores root bone animation state data.
* 存储根骨骼动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_Root
{
GENERATED_BODY()
/**
* Translation mode for the root bone.
* 根骨骼的平移模式。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
EOffsetRootBoneMode TranslationMode{EOffsetRootBoneMode::Release};
/**
* Rotation mode for the root bone.
* 根骨骼的旋转模式。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
EOffsetRootBoneMode RotationMode{EOffsetRootBoneMode::Release};
/**
* Current world-space transform of the root bone.
* 根骨骼的当前世界空间变换。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FTransform RootTransform{FTransform::Identity};
/**
* Yaw offset relative to the actor's rotation (degrees; <0 left, >0 right).
* 相对于Actor旋转的偏航偏移<0左侧>0右侧
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="State")
float YawOffset{0};
/**
* Maximum rotation error in degrees; values <0 disable the limit.
* 最大旋转误差(度);值<0禁用限制。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float MaxRotationError{-1.0f};
};
/**
* Stores turn-in-place animation state data.
* 存储原地转身动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_TurnInPlace
{
GENERATED_BODY()
/**
* Indicates if the state was updated this frame.
* 指示此帧是否更新了状态。
*/
UPROPERTY()
uint8 bUpdatedThisFrame : 1 {false};
/**
* Animation sequence for turn-in-place.
* 原地转身的动画序列。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequenceBase> Animation;
/**
* Indicates if the character should turn.
* 指示角色是否应该转身。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
uint8 bShouldTurn : 1 {false};
/**
* Accumulated time for the animation.
* 动画的累计时间。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float AccumulatedTime{0};
/**
* Playback rate for the animation.
* 动画的播放速率。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ForceUnits = "x"))
float PlayRate{1.0f};
/**
* Scaled playback rate for the animation.
* 动画的缩放播放速率。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ForceUnits = "x"))
float ScaledPlayRate{1.0f};
/**
* Delay before activating the turn-in-place animation.
* 原地转身动画激活前的延迟。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ForceUnits = "s"))
float ActivationDelay{0.0f};
/**
* Angle that triggers the turn-in-place animation.
* 触发原地转身动画的角度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float TriggeredAngle{0.0f};
/**
* Indicates if the turn is 180 degrees.
* 指示是否为180度转身。
*/
bool b180{false};
};
/**
* Stores in-air animation state data.
* 存储空中动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_InAir
{
GENERATED_BODY()
/**
* Vertical speed of the character (cm/s).
* 角色的垂直速度(厘米/秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ForceUnits = "cm/s"))
float VerticalSpeed{0.0f};
/**
* Indicates if the character is jumping.
* 指示角色是否在跳跃。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bJumping{false};
/**
* Indicates if the character is falling.
* 指示角色是否在下落。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bFalling{false};
/**
* Playback rate for the jump animation.
* 跳跃动画的播放速率。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ForceUnits = "x"))
float JumpPlayRate{1.0f};
/**
* Indicates if valid ground is detected.
* 指示是否检测到有效地面。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bValidGround{false};
/**
* Distance to the ground (cm).
* 到地面的距离(厘米)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(ForceUnits = "cm"))
float GroundDistance{0.0f};
/**
* Time to reach the jump apex (s).
* 到达跳跃顶点的时间(秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1, ForceUnits = "s"))
float TimeToJumpApex{0.0f};
/**
* Time spent falling (s).
* 下落的时间(秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1, ForceUnits = "s"))
float FallingTime{0.0f};
};
/**
* Stores idle animation state data.
* 存储空闲动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_Idle
{
GENERATED_BODY()
/**
* Animation sequence for idle.
* 空闲动画序列。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Animation{nullptr};
/**
* Indicates if the idle animation is looped.
* 指示空闲动画是否循环。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bLoop{true};
/**
* Playback rate for the idle animation.
* 空闲动画的播放速率。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float PlayRate{1.0f};
/**
* Blend time for the idle animation.
* 空闲动画的混合时间。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float BlendTime{1.0f};
/**
* Blend profile for the idle animation.
* 空闲动画的混合配置文件。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UBlendProfile> BlendProfile{nullptr};
};
/**
* Stores runtime state for idle break animations.
* 存储空闲中断动画的运行时状态。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_IdleBreak
{
GENERATED_BODY()
/**
* Time until the next idle break animation (s).
* 到下一个空闲中断动画的时间(秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float TimeUntilNextIdleBreak{0.0f};
/**
* Index of the current idle break animation.
* 当前空闲中断动画的索引。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
int32 CurrentIdleBreakIndex{0};
/**
* Delay between idle break animations (s).
* 空闲中断动画之间的延迟(秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float IdleBreakDelayTime{0.0f};
};
/**
* Stores lean animation state data.
* 存储倾斜动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_Lean
{
GENERATED_BODY()
/**
* Horizontal acceleration amount for leaning (clamped -1 to 1).
* 用于倾斜的水平加速度量(限制在-1到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -1, ClampMax = 1))
float RightAmount{0.0f};
/**
* Vertical acceleration amount for leaning (clamped -1 to 1).
* 用于倾斜的垂直加速度量(限制在-1到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -1, ClampMax = 1))
float ForwardAmount{0.0f};
};
/**
* Stores pivot animation state data.
* 存储枢轴动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_Pivot
{
GENERATED_BODY()
/**
* Animation sequence for the pivot.
* 枢轴动画序列。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Animation;
/**
* Acceleration at the start of the pivot.
* 枢轴开始时的加速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector StartingAcceleration{ForceInit};
/**
* Accumulated time for the pivot animation.
* 枢轴动画的累计时间。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float AccumulatedTime{ForceInit};
/**
* Time at which the pivot stops for distance matching.
* 距离匹配时枢轴停止的时间。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float TimeAtPivotStop{ForceInit};
/**
* Alpha for stride warping during the pivot.
* 枢轴期间步幅适配的Alpha值。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float StrideWarpingAlpha{ForceInit};
/**
* Clamp for the playback rate (min, max).
* 播放速率的限制(最小,最大)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector2D PlayRateClamp{0.f, 0.f};
/**
* 2D direction of the pivot.
* 枢轴的2D方向。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
FVector Direction2D{ForceInit};
/**
* Desired movement direction for the pivot.
* 枢轴的期望移动方向。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
EGMS_MovementDirection DesiredDirection{ForceInit};
/**
* Initial movement direction for the pivot.
* 枢轴的初始移动方向。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
EGMS_MovementDirection InitialDirection{ForceInit};
/**
* Remaining cooldown time for the pivot (s).
* 枢轴的剩余冷却时间(秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
float RemainingCooldown{ForceInit};
/**
* Indicates if the pivot is moving perpendicular to the initial direction.
* 指示枢轴是否垂直于初始方向移动。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
bool bMovingPerpendicularToInitialDirection{ForceInit};
};
/**
* Stores start animation state data.
* 存储开始动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_Start
{
GENERATED_BODY()
/**
* Animation sequence for the start movement.
* 开始移动的动画序列。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Animation{nullptr};
/**
* Smooth target rotation for the start movement.
* 开始移动的平滑目标旋转。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FRotator SmoothTargetRotation{ForceInit};
/**
* Yaw delta between acceleration direction and root direction at start (degrees).
* 开始时加速度方向与根方向的偏航差(度)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float YawDeltaToAcceleration{ForceInit};
/**
* Local velocity direction at the start.
* 开始时的本地速度方向。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
EGMS_MovementDirection LocalVelocityDirection{ForceInit};
/**
* Alpha for stride warping during the start.
* 开始期间步幅适配的Alpha值。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float StrideWarpingAlpha{ForceInit};
/**
* Clamp for the playback rate (min, max).
* 播放速率的限制(最小,最大)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector2D PlayRateClamp{ForceInit};
/**
* Alpha for orientation warping during the start.
* 开始期间朝向扭曲的Alpha值。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float OrientationAlpha{1.0f};
/**
* Time spent in the start state (s).
* 开始状态的持续时间(秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float TimeInState{0.0f};
/**
* Blend profile for the start animation.
* 开始动画的混合配置文件。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UBlendProfile> BlendProfile{nullptr};
/**
* Remaining time for the start animation (s).
* 开始动画的剩余时间(秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float TimeRemaining{0.0f};
/**
* Playback rate for the start animation.
* 开始动画的播放速率。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float PlayRate{0.0f};
};
/**
* Stores cycle animation state data.
* 存储循环动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_Cycle
{
GENERATED_BODY()
/**
* Animation sequence for the cycle.
* 循环动画序列。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Animation{nullptr};
/**
* Alpha for orientation warping during the cycle.
* 循环期间朝向扭曲的Alpha值。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float OrientationAlpha{ForceInit};
/**
* Alpha for stride warping during the cycle.
* 循环期间步幅适配的Alpha值。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float StrideWarpingAlpha{ForceInit};
/**
* Playback rate for the cycle animation.
* 循环动画的播放速率。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float PlayRate{1.0f};
/**
* Blend profile for the cycle animation.
* 循环动画的混合配置文件。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UBlendProfile> BlendProfile{nullptr};
};
/**
* Stores stop animation state data.
* 存储停止动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_Stop
{
GENERATED_BODY()
/**
* Animation sequence for the stop.
* 停止动画序列。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Animation{nullptr};
/**
* Alpha for orientation warping during the stop.
* 停止期间朝向扭曲的Alpha值。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float OrientationAlpha{1.0f};
};
/**
* Stores view-related animation state data.
* 存储与视图相关的动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_View
{
GENERATED_BODY()
/**
* Rotation of the view.
* 视图的旋转。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FRotator Rotation{ForceInit};
/**
* Yaw angle between the actor and camera (degrees).
* Actor与相机之间的偏航角
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float YawAngle{0.0f};
/**
* Speed of yaw angle change (degrees/s).
* 偏航角变化速度(度/秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ForceUnits = "deg/s"))
float YawSpeed{0.0f};
/**
* Pitch angle of the view (degrees).
* 视图的俯仰角(度)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -90, ClampMax = 90, ForceUnits = "deg"))
float PitchAngle{0.0f};
/**
* Amount of pitch applied (clamped 0 to 1).
* 应用的俯仰量限制在0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float PitchAmount{0.5f};
};
/**
* Stores layering animation state data for body parts.
* 存储身体部位的分层动画状态数据。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_Layering
{
GENERATED_BODY()
virtual ~FGMS_AnimState_Layering() = default;
/**
* Blend amount for the head (0 to 1).
* 头部混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float HeadBlendAmount{0.0f};
/**
* Additive blend amount for the head (0 to 1).
* 头部附加混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float HeadAdditiveBlendAmount{0.0f};
/**
* Slot blend amount for the head (0 to 1).
* 头部槽混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float HeadSlotBlendAmount{1.0f};
/**
* Blend amount for the left arm (0 to 1).
* 左臂混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmLeftBlendAmount{0.0f};
/**
* Additive blend amount for the left arm (0 to 1).
* 左臂附加混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmLeftAdditiveBlendAmount{0.0f};
/**
* Slot blend amount for the left arm (0 to 1).
* 左臂槽混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmLeftSlotBlendAmount{1.0f};
/**
* Local space blend amount for the left arm (0 to 1).
* 左臂本地空间混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmLeftLocalSpaceBlendAmount{0.0f};
/**
* Mesh space blend amount for the left arm (0 to 1).
* 左臂网格空间混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmLeftMeshSpaceBlendAmount{0.0f};
/**
* Blend amount for the right arm (0 to 1).
* 右臂混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmRightBlendAmount{0.0f};
/**
* Additive blend amount for the right arm (0 to 1).
* 右臂附加混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmRightAdditiveBlendAmount{0.0f};
/**
* Slot blend amount for the right arm (0 to 1).
* 右臂槽混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmRightSlotBlendAmount{1.0f};
/**
* Local space blend amount for the right arm (0 to 1).
* 右臂本地空间混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmRightLocalSpaceBlendAmount{0.0f};
/**
* Mesh space blend amount for the right arm (0 to 1).
* 右臂网格空间混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmRightMeshSpaceBlendAmount{0.0f};
/**
* Blend amount for the left hand (0 to 1).
* 左手混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float HandLeftBlendAmount{0.0f};
/**
* Blend amount for the right hand (0 to 1).
* 右手混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float HandRightBlendAmount{0.0f};
/**
* Blend amount for the spine (0 to 1).
* 脊椎混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float SpineBlendAmount{0.0f};
/**
* Additive blend amount for the spine (0 to 1).
* 脊椎附加混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float SpineAdditiveBlendAmount{0.0f};
/**
* Slot blend amount for the spine (0 to 1).
* 脊椎槽混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float SpineSlotBlendAmount{1.0f};
/**
* Blend amount for the pelvis (0 to 1).
* 骨盆混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float PelvisBlendAmount{0.0f};
/**
* Slot blend amount for the pelvis (0 to 1).
* 骨盆槽混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float PelvisSlotBlendAmount{1.0f};
/**
* Blend amount for the legs (0 to 1).
* 腿部混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float LegsBlendAmount{0.0f};
/**
* Slot blend amount for the legs (0 to 1).
* 腿部槽混合量0到1
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float LegsSlotBlendAmount{1.0f};
void ApplyValueFromSequence(const UAnimSequence* InSequence, float ExplicitTime);
virtual void ZeroOut();
};
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimState_LayeringSmooth
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float HeadBlendAmount{0.0f};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmLeftBlendAmount{0.0f};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float ArmRightBlendAmount{0.0f};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float SpineBlendAmount{0.0f};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float PelvisBlendAmount{0.0f};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ClampMax = 1))
float LegsBlendAmount{0.0f};
};

View File

@@ -0,0 +1,37 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "GMS_LocomotionEnumLibrary.generated.h"
/**
* Defines four-directional movement directions.
* 定义四方向移动方向。
*/
UENUM(BlueprintType)
enum class EGMS_MovementDirection : uint8
{
Forward, // Forward movement. 前进移动。
Backward, // Backward movement. 后退移动。
Left, // Left movement. 左移。
Right // Right movement. 右移。
};
/**
* Defines eight-directional movement directions.
* 定义八方向移动方向。
*/
UENUM(BlueprintType)
enum class EGMS_MovementDirection_8Way : uint8
{
Forward, // Forward movement. 前进移动。
ForwardLeft, // Forward-left movement. 前左移动。
ForwardRight, // Forward-right movement. 前右移动。
Backward, // Backward movement. 后退移动。
BackwardLeft, // Backward-left movement. 后左移动。
BackwardRight, // Backward-right movement. 后右移动。
Left, // Left movement. 左移。
Right // Right movement. 右移。
};

View File

@@ -0,0 +1,601 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Animation/CachedAnimData.h"
#include "UObject/Object.h"
#include "GMS_LocomotionStructLibrary.generated.h"
class UBlendSpace1D;
class UAnimSequenceBase;
class UAnimSequence;
/**
* Stores the locomotion state of a character.
* 存储角色的运动状态。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_LocomotionState
{
GENERATED_BODY()
/**
* Indicates if there is active input.
* 表示是否有活跃的输入。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bHasInput{false};
/**
* Input yaw angle in world space.
* 世界空间中的输入偏航角。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float InputYawAngle{0.0f};
/**
* Indicates if the character has speed.
* 表示角色是否有速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bHasVelocity{false};
/**
* Current speed of the character.
* 角色的当前速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ForceUnits = "cm/s"))
float Speed{0.0f};
/**
* Current velocity vector.
* 当前速度向量。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector Velocity{ForceInit};
/**
* Yaw angle of the character's velocity.
* 角色速度的偏航角。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float VelocityYawAngle{0.0f};
/**
* Indicates if the character is moving.
* 表示角色是否在移动。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bMoving{false};
/**
* Target yaw angle for the actor's rotation.
* Actor旋转的目标偏航角。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float TargetYawAngle{0.0f};
/**
* Smoothed target yaw angle for extra smooth rotation.
* 用于平滑旋转的平滑目标偏航角。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float SmoothTargetYawAngle{0.0f};
/**
* Angle between view yaw and target yaw.
* 视角偏航角与目标偏航角之间的角度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float ViewRelativeTargetYawAngle{0.0f};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
uint8 bAimingLimitAppliedThisFrame : 1 {false};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
uint8 bResetAimingLimit : 1 {true};
/**
* Limit for the aiming yaw angle.
* 瞄准偏航角的限制。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float AimingYawAngleLimit{180.0f};
};
/**
* Stores the view state of a character.
* 存储角色的视图状态。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_ViewState
{
GENERATED_BODY()
/**
* Smoothed view rotation, set by replicated view rotation.
* 平滑的视角旋转,由复制的视角旋转设置。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FRotator Rotation{ForceInit};
/**
* Speed of camera rotation from left to right.
* 相机左右旋转的速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ForceUnits = "deg/s"))
float YawSpeed{0.0f};
/**
* View yaw angle from the previous frame.
* 上一帧的视角偏航角。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = -180, ClampMax = 180, ForceUnits = "deg"))
float PreviousYawAngle{0.0f};
};
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_MovementBaseState
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite, Category="GMS")
TObjectPtr<UPrimitiveComponent> Primitive;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FName BoneName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
uint8 bBaseChanged : 1 {false};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
uint8 bHasRelativeLocation : 1 {false};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
uint8 bHasRelativeRotation : 1 {false};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FVector Location{ForceInit};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FQuat Rotation{ForceInit};
/**
* 基础对象(例如移动平台)从上一帧到当前帧的旋转变化。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
FRotator DeltaRotation{ForceInit};
};
/**
* Parameters for predicting ground movement stop location.
* 预测地面运动停止位置的参数。
*/
USTRUCT()
struct FGMS_PredictGroundMovementStopLocationParams
{
GENERATED_BODY()
/**
* Current velocity vector.
* 当前速度向量。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FVector Velocity{ForceInit};
/**
* Whether to use separate braking friction.
* 是否使用单独的制动摩擦。
*/
UPROPERTY(EditAnywhere, Category="GMS")
bool bUseSeparateBrakingFriction{ForceInit};
/**
* Braking friction value.
* 制动摩擦值。
*/
UPROPERTY(EditAnywhere, Category="GMS")
float BrakingFriction{ForceInit};
/**
* Ground friction value.
* 地面摩擦值。
*/
UPROPERTY(EditAnywhere, Category="GMS")
float GroundFriction{ForceInit};
/**
* Braking friction factor.
* 制动摩擦因子。
*/
UPROPERTY(EditAnywhere, Category="GMS")
float BrakingFrictionFactor{ForceInit};
/**
* Braking deceleration for walking.
* 行走时的制动减速度。
*/
UPROPERTY(EditAnywhere, Category="GMS")
float BrakingDecelerationWalking{ForceInit};
};
/**
* Parameters for predicting ground movement pivot location.
* 预测地面运动枢轴位置的参数。
*/
USTRUCT()
struct FGMS_PredictGroundMovementPivotLocationParams
{
GENERATED_BODY()
/**
* Current acceleration vector.
* 当前加速度向量。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FVector Acceleration{ForceInit};
/**
* Current velocity vector.
* 当前速度向量。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FVector Velocity{ForceInit};
/**
* Ground friction value.
* 地面摩擦值。
*/
UPROPERTY(EditAnywhere, Category="GMS")
float GroundFriction{0.0f};
};
/**
* Stores animations for four-directional movement.
* 存储四方向移动的动画。
*/
USTRUCT(BlueprintType)
struct FGMS_Animations_4Direction
{
GENERATED_BODY()
/**
* Animation for forward movement.
* 前进移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Forward = nullptr;
/**
* Animation for backward movement.
* 后退移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Backward = nullptr;
/**
* Animation for left movement.
* 左移移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Left = nullptr;
/**
* Animation for right movement.
* 右移移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Right = nullptr;
/**
* Checks if all animations are valid.
* 检查所有动画是否有效。
* @return True if all animations are set. 如果所有动画都设置返回true。
*/
bool ValidAnimations() const;
/**
* Checks if any animation has root motion.
* 检查是否有动画包含根运动。
* @return True if any animation has root motion. 如果有动画包含根运动返回true。
*/
bool HasRootMotion() const;
};
/**
* Stores animations for eight-directional movement.
* 存储八方向移动的动画。
*/
USTRUCT(BlueprintType)
struct FGMS_Animations_8Direction
{
GENERATED_BODY()
/**
* Animation for forward movement.
* 前进移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Forward = nullptr;
/**
* Animation for forward-left movement.
* 前左移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> ForwardLeft = nullptr;
/**
* Animation for forward-right movement.
* 前右移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> ForwardRight = nullptr;
/**
* Animation for backward movement.
* 后退移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Backward = nullptr;
/**
* Animation for backward-left movement.
* 后左移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> BackwardLeft = nullptr;
/**
* Animation for backward-right movement.
* 后右移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> BackwardRight = nullptr;
/**
* Animation for left movement.
* 左移移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Left = nullptr;
/**
* Animation for right movement.
* 右移移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> Right = nullptr;
/**
* Checks if all animations are valid.
* 检查所有动画是否有效。
* @return True if all animations are set. 如果所有动画都设置返回true。
*/
bool ValidAnimations() const;
/**
* Checks if any animation has root motion.
* 检查是否有动画包含根运动。
* @return True if any animation has root motion. 如果有动画包含根运动返回true。
*/
bool HasRootMotion() const;
};
/**
* Stores 1D blend space animations for forward and backward movement.
* 存储用于前后移动的1D混合空间动画。
*/
USTRUCT(BlueprintType)
struct FGMS_Animations_BS1D_FwdBwd
{
GENERATED_BODY()
/**
* Blend space for forward movement.
* 前进移动的混合空间。
*/
UPROPERTY(EditAnywhere, Category="GMS")
TObjectPtr<UBlendSpace1D> Forward{nullptr};
/**
* Blend space for backward movement.
* 后退移动的混合空间。
*/
UPROPERTY(EditAnywhere, Category="GMS")
TObjectPtr<UBlendSpace1D> Backward{nullptr};
};
/**
* Stores animations for starting movement while facing forward.
* 存储面向前进时开始移动的动画。
*/
USTRUCT(BlueprintType)
struct FGMS_Animations_StartForwardFacing
{
GENERATED_BODY()
/**
* Animation for starting movement forward.
* 前进开始移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> StartForward = nullptr;
/**
* Animation for starting movement forward with a 90-degree left turn.
* 前进并向左90度开始移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> StartForwardL90 = nullptr;
/**
* Animation for starting movement forward with a 90-degree right turn.
* 前进并向右90度开始移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> StartForwardR90 = nullptr;
/**
* Animation for starting movement forward with a 180-degree left turn.
* 前进并向左180度开始移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> StartForwardL180 = nullptr;
/**
* Animation for starting movement forward with a 180-degree right turn.
* 前进并向右180度开始移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> StartForwardR180 = nullptr;
};
/**
* Stores animations for starting movement while facing forward in eight directions.
* 存储面向前进时八方向开始移动的动画。
*/
USTRUCT(BlueprintType)
struct FGMS_Animations_StartForwardFacing_8Direction
{
GENERATED_BODY()
/**
* Animation for starting movement forward.
* 前进开始移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> StartForward = nullptr;
/**
* Animation for starting movement forward with a 90-degree left turn.
* 前进并向左90度开始移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> StartForwardL90 = nullptr;
/**
* Animation for starting movement forward with a 90-degree right turn.
* 前进并向右90度开始移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> StartForwardR90 = nullptr;
/**
* Animation for starting movement forward with a 180-degree left turn.
* 前进并向左180度开始移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> StartForwardL180 = nullptr;
/**
* Animation for starting movement forward with a 180-degree right turn.
* 前进并向右180度开始移动的动画。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UAnimSequence> StartForwardR180 = nullptr;
};
/**
* Stores an animation with an associated distance.
* 存储与距离关联的动画。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimationWithDistance
{
GENERATED_BODY()
/**
* The animation sequence.
* 动画序列。
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="GMS")
TObjectPtr<UAnimSequence> Animation = nullptr;
/**
* The distance associated with the animation.
* 与动画关联的距离。
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="GMS", meta=(ClampMin=0))
float Distance{200};
#if WITH_EDITORONLY_DATA
/**
* Editor-friendly name for the animation.
* 动画的编辑器友好名称。
*/
UPROPERTY(VisibleAnywhere, Category=AlwaysHidden, Meta=(EditCondition=False, EditConditionHides))
FString EditorFriendlyName;
#endif
};
/**
* Maps animation state names to gameplay tags.
* 将动画状态名称映射到游戏标签。
*/
USTRUCT(BlueprintType)
struct FGMS_AnimStateNameToTag
{
GENERATED_BODY()
/**
* The gameplay tag for the animation state.
* 动画状态的游戏标签。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(Categories="GMS.SM"))
FGameplayTag Tag;
/**
* The cached animation state data.
* 缓存的动画状态数据。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "GMS")
FCachedAnimStateData State;
/**
* Equality operator for comparing two animation state mappings.
* 比较两个动画状态映射的相等运算符。
* @param Lhs Left-hand side mapping. 左侧映射。
* @param RHS Right-hand side mapping. 右侧映射。
* @return True if mappings are equal. 如果映射相等返回true。
*/
friend bool operator==(const FGMS_AnimStateNameToTag& Lhs, const FGMS_AnimStateNameToTag& RHS)
{
return Lhs.Tag == RHS.Tag
&& Lhs.State.StateMachineName == RHS.State.StateMachineName && Lhs.State.StateName == RHS.State.StateName;
}
/**
* Inequality operator for comparing two animation state mappings.
* 比较两个动画状态映射的不等运算符。
* @param Lhs Left-hand side mapping. 左侧映射。
* @param RHS Right-hand side mapping. 右侧映射。
* @return True if mappings are not equal. 如果映射不相等返回true。
*/
friend bool operator!=(const FGMS_AnimStateNameToTag& Lhs, const FGMS_AnimStateNameToTag& RHS)
{
return !(Lhs == RHS);
}
};
/**
* Wrapper for a list of animation state to tag mappings.
* 动画状态到标签映射列表的包装器。
*/
USTRUCT()
struct FGMS_AnimStateNameToTagWrapper
{
GENERATED_BODY()
/**
* Array of animation state to tag mappings.
* 动画状态到标签映射的数组。
*/
UPROPERTY()
TArray<FGMS_AnimStateNameToTag> AnimStateNameToTagMapping;
};

View File

@@ -0,0 +1,651 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "GMS_LocomotionStructLibrary.h"
#include "Engine/TimerHandle.h"
#include "TimerManager.h"
#include "Animation/AnimExecutionContext.h"
#include "Animation/AnimInstance.h"
#include "Animation/AnimNodeReference.h"
#include "Engine/World.h"
#include "Settings/GMS_SettingStructLibrary.h"
#include "Locomotions/GMS_AnimState.h"
#include "Utility/GMS_Tags.h"
#include "GMS_MainAnimInstance.generated.h"
class IPoseSearchTrajectoryPredictorInterface;
class UGMS_AnimLayerSetting_Additive;
class UGMS_AnimLayer;
class UGMS_AnimLayerSetting;
class UGMS_AnimLayerSetting_View;
class UGMS_AnimLayerSetting_Overlay;
class UGMS_AnimLayerSetting_States;
class UGMS_MovementDefinition;
class UGMS_MovementSystemComponent;
/**
* Base animation template for the main animation instance.
* 主动画实例的动画模板基类。
*/
UCLASS(BlueprintType)
class GENERICMOVEMENTSYSTEM_API UGMS_MainAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
/**
* Gets the movement system component.
* 获取运动系统组件。
* @return The movement system component. 运动系统组件。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Animation", meta=(BlueprintThreadSafe))
UGMS_MovementSystemComponent* GetMovementSystemComponent() const;
/**
* Constructor.
* 构造函数。
*/
UGMS_MainAnimInstance();
/**
* Initializes the animation.
* 初始化动画。
*/
virtual void NativeInitializeAnimation() override;
/**
* Uninitializes the animation.
* 取消初始化动画。
*/
virtual void NativeUninitializeAnimation() override;
/**
* Called when the game starts.
* 游戏开始时调用。
*/
virtual void NativeBeginPlay() override;
/**
* Updates the animation.
* 更新动画。
* @param DeltaTime Time since last update. 自上次更新以来的时间。
*/
virtual void NativeUpdateAnimation(float DeltaTime) override;
/**
* Thread-safe animation update.
* 线程安全的动画更新。
* @param DeltaTime Time since last update. 自上次更新以来的时间。
*/
virtual void NativeThreadSafeUpdateAnimation(float DeltaTime) override;
/**
* Applies an animation layer setting to an animation layer instance.
* 将动画层设置应用于动画层实例。
* @param LayerSetting The layer setting to apply. 要应用的层设置。
* @param LayerInstance The layer instance to apply the setting to. 要应用设置的层实例。
*/
virtual void SetAnimLayerBySetting(const UGMS_AnimLayerSetting* LayerSetting, TObjectPtr<UGMS_AnimLayer>& LayerInstance);
/**
* Registers animation state name to tag mappings for a given animation instance.
* 为给定的动画实例注册动画状态名称到标签的映射。
* @param SourceAnimInstance The animation instance to register mappings for. 要注册映射的动画实例。
* @param Mapping The state name to tag mappings. 状态名称到标签的映射。
*/
UFUNCTION(BlueprintCallable, Category="GMS|Animation")
virtual void RegisterStateNameToTagMapping(UAnimInstance* SourceAnimInstance, TArray<FGMS_AnimStateNameToTag> Mapping);
/**
* Unregisters animation state name to tag mappings for a given animation instance.
* 为给定的动画实例取消注册动画状态名称到标签的映射。
* @param SourceAnimInstance The animation instance to unregister mappings for. 要取消注册映射的动画实例。
*/
UFUNCTION(BlueprintCallable, Category="GMS|Animation")
virtual void UnregisterStateNameToTagMapping(UAnimInstance* SourceAnimInstance);
/**
* Refreshes layer settings when core state changes (e.g., movement set, locomotion mode).
* 当核心状态(如运动集、运动模式)更改时刷新层设置。
* @note Override if custom movement definitions include additional animation layer settings. 如果自定义运动定义包含额外的动画层设置,则需覆盖。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|Animation")
void RefreshLayerSettings();
virtual void RefreshLayerSettings_Implementation();
/**
* Sets the offset root bone rotation mode.
* 设置偏移根骨骼旋转模式。
* @param NewRotationMode The new rotation mode. 新的旋转模式。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|Animation", meta=(BlueprintThreadSafe))
void SetOffsetRootBoneRotationMode(EOffsetRootBoneMode NewRotationMode);
/**
* Gets the current offset root bone rotation mode.
* 获取当前偏移根骨骼旋转模式。
* @return The current rotation mode. 当前旋转模式。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GMS|Animation", meta=(BlueprintThreadSafe))
EOffsetRootBoneMode GetOffsetRootBoneRotationMode() const;
/**
* Gets the current offset root bone translation mode.
* 获取当前偏移根骨骼平移模式。
* @return The current translation mode. 当前平移模式。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GMS|Animation", meta=(BlueprintThreadSafe))
EOffsetRootBoneMode GetOffsetRootBoneTranslationMode() const;
/**
* Sets the offset root bone translation mode.
* 设置偏移根骨骼平移模式。
* @param NewTranslationMode The new translation mode. 新的平移模式。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|Animation", meta=(BlueprintThreadSafe))
void SetOffsetRootBoneTranslationMode(EOffsetRootBoneMode NewTranslationMode);
protected:
/**
* Called when the locomotion mode changes.
* 运动模式更改时调用。
* @param Prev The previous locomotion mode. 之前的运动模式。
*/
UFUNCTION(BlueprintNativeEvent, Category="GMS|Animation")
void OnLocomotionModeChanged(const FGameplayTag& Prev);
/**
* Called when the rotation mode changes.
* 旋转模式更改时调用。
* @param Prev The previous rotation mode. 之前的旋转模式。
*/
UFUNCTION(BlueprintNativeEvent, Category="GMS|Animation")
void OnRotationModeChanged(const FGameplayTag& Prev);
/**
* Called when the movement set changes.
* 运动集更改时调用。
* @param Prev The previous movement set. 之前的运动集。
*/
UFUNCTION(BlueprintNativeEvent, Category="GMS|Animation")
void OnMovementSetChanged(const FGameplayTag& Prev);
/**
* Called when the movement state changes.
* 运动状态更改时调用。
* @param Prev The previous movement state. 之前的运动状态。
*/
UFUNCTION(BlueprintNativeEvent, Category="GMS|Animation")
void OnMovementStateChanged(const FGameplayTag& Prev);
/**
* Called when the overlay mode changes.
* 叠层模式更改时调用。
* @param Prev The previous overlay mode. 之前的叠层模式。
*/
UFUNCTION(BlueprintNativeEvent, Category="GMS|Animation")
void OnOverlayModeChanged(const FGameplayTag& Prev);
/**
* Refreshes Trajectory-related data.
* 刷新Trajectory相关数据。
* @param DeltaTime Time since last update. 自上次更新以来的时间。
*/
virtual void RefreshTrajectoryState(float DeltaTime);
/**
* Refreshes view-related data.
* 刷新视图相关数据。
* @param DeltaTime Time since last update. 自上次更新以来的时间。
*/
virtual void RefreshView(float DeltaTime);
/**
* Refreshes locomotion data.
* 刷新运动数据。
* @param DeltaTime Time since last update. 自上次更新以来的时间。
*/
virtual void RefreshLocomotion(const float DeltaTime);
/**
* Refreshes block state.
* 刷新阻塞状态。
*/
virtual void RefreshBlock();
/**
* Gather information from game world.
* 从游戏世界获取信息。
*/
virtual void RefreshStateOnGameThread();
/**
* Refreshes animation node relevance tags on the game thread.
* 在游戏线程上刷新动画节点相关性标签。
*/
virtual void RefreshRelevanceOnGameThread();
/**
* Refreshes grounded state data.
* 刷新地面状态数据。
*/
virtual void RefreshGrounded();
/**
* Refreshes lean data for grounded state.
* 刷新地面状态的倾斜数据。
*/
virtual void RefreshGroundedLean();
/**
* Gets the relative acceleration amount for leaning.
* 获取用于倾斜的相对加速度量。
* @return 2D vector of relative acceleration. 相对加速度的2D向量。
*/
virtual FVector2f GetRelativeAccelerationAmount() const;
/**
* Refreshes in-air state data.
* 刷新空中状态数据。
*/
virtual void RefreshInAir();
/**
* Refreshes ground prediction data.
* 刷新地面预测数据。
*/
virtual void RefreshGroundPrediction();
/**
* Refreshes lean data for in-air state.
* 刷新空中状态的倾斜数据。
*/
virtual void RefreshInAirLean();
/**
* Refreshes the offset root bone state.
* 刷新偏移根骨骼状态。
* @param Context Animation update context. 动画更新上下文。
* @param Node Animation node reference. 动画节点引用。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GMS|Animation", meta=(BlueprintThreadSafe))
void RefreshOffsetRootBone(UPARAM(ref)
FAnimUpdateContext& Context, UPARAM(ref)
FAnimNodeReference& Node);
public:
/**
* Gets the clamped curve value (0 to 1) for a given curve name.
* 获取给定曲线名称的限制曲线值0到1
* @param CurveName The name of the curve. 曲线名称。
* @return The clamped curve value. 限制的曲线值。
*/
float GetCurveValueClamped01(const FName& CurveName) const;
/**
* Gets the named blend profile.
* 获取命名的混合配置文件。
* @param BlendProfileName The name of the blend profile. 混合配置文件名称。
* @return The blend profile. 混合配置文件。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Animation", meta=(BlueprintThreadSafe))
UBlendProfile* GetNamedBlendProfile(const FName& BlendProfileName) const;
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Animation", meta=(BlueprintThreadSafe))
FGameplayTagContainer GetAggregatedTags() const;
/**
* Gets the yaw value for aim offset.
* 获取瞄准偏移的偏航值。
* @return The aim offset yaw value. 瞄准偏移偏航值。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Animation", meta=(BlueprintThreadSafe))
float GetAOYawValue() const;
/**
* Selects a cardinal direction based on an angle.
* 根据角度选择主要方向。
* @param Angle The angle to evaluate. 要评估的角度。
* @param DeadZone The dead zone for direction changes. 方向变化的死区。
* @param CurrentDirection The current direction. 当前方向。
* @param bUseCurrentDirection Whether to consider the current direction. 是否考虑当前方向。
* @return The selected cardinal direction. 选择的主要方向。
*/
EGMS_MovementDirection SelectCardinalDirectionFromAngle(float Angle, float DeadZone, EGMS_MovementDirection CurrentDirection, bool bUseCurrentDirection) const;
/**
* Selects an octagonal direction based on an angle.
* 根据角度选择八方向。
* @param Angle The angle to evaluate. 要评估的角度。
* @param DeadZone The dead zone for direction changes. 方向变化的死区。
* @param CurrentDirection The current direction. 当前方向。
* @param bUseCurrentDirection Whether to consider the current direction. 是否考虑当前方向。
* @return The selected octagonal direction. 选择的八方向。
*/
EGMS_MovementDirection_8Way SelectOctagonalDirectionFromAngle(float Angle, float DeadZone, EGMS_MovementDirection_8Way CurrentDirection, bool bUseCurrentDirection) const;
/**
* Gets the opposite cardinal direction.
* 获取相反的主要方向。
* @param CurrentDirection The current direction. 当前方向。
* @return The opposite cardinal direction. 相反的主要方向。
*/
EGMS_MovementDirection GetOppositeCardinalDirection(EGMS_MovementDirection CurrentDirection) const;
/**
* Checks if any core state has changed (movement set, state, locomotion, rotation, or overlay mode).
* 检查是否有核心状态更改(运动集、状态、运动、旋转或叠层模式)。
* @return True if any core state has changed. 如果任何核心状态更改则返回true。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Animation", meta=(BlueprintThreadSafe))
bool HasCoreStateChanges() const;
/**
* Checks if specified core states have changed.
* 检查指定的核心状态是否更改。
* @param bCheckLocomotionMode Check locomotion mode changes. 检查运动模式更改。
* @param bCheckMovementSet Check movement set changes. 检查运动集更改。
* @param bCheckRotationMode Check rotation mode changes. 检查旋转模式更改。
* @param bCheckMovementState Check movement state changes. 检查运动状态更改。
* @param bCheckOverlayMode Check overlay mode changes. 检查叠层模式更改。
* @return True if any specified state has changed. 如果任何指定状态更改则返回true。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Animation", meta=(BlueprintThreadSafe))
bool CheckCoreStateChanges(bool bCheckLocomotionMode, bool bCheckMovementSet, bool bCheckRotationMode, bool bCheckMovementState, bool bCheckOverlayMode) const;
/**
* Animation state name to gameplay tag mappings.
* 动画状态名称到游戏标签的映射。
* @note Used to check if a state node is active via NodeRelevantTags. 用于通过NodeRelevantTags检查状态节点是否活跃。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings", meta=(TitleProperty="Tag"))
TArray<FGMS_AnimStateNameToTag> AnimStateNameToTagMapping;
/**
* Current locomotion mode.
* 当前运动模式。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings", meta=(Categories="GMS.LocomotionMode"))
FGameplayTag LocomotionMode{GMS_MovementModeTags::Grounded};
/**
* Container for locomotion mode (for chooser only).
* 运动模式的容器(仅用于选择器)。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings", meta=(Categories="GMS.LocomotionMode"))
FGameplayTagContainer LocomotionModeContainer;
/**
* Current rotation mode.
* 当前旋转模式。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings", meta=(Categories="GMS.RotationMode"))
FGameplayTag RotationMode{GMS_RotationModeTags::ViewDirection};
/**
* Container for rotation mode (for chooser only).
* 旋转模式的容器(仅用于选择器)。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings", meta=(Categories="GMS.RotationMode"))
FGameplayTagContainer RotationModeContainer;
/**
* Current movement state.
* 当前运动状态。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings", meta=(Categories="GMS.MovementState"))
FGameplayTag MovementState{GMS_MovementStateTags::Jog};
/**
* Container for movement state (for chooser only).
* 运动状态的容器(仅用于选择器)。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings", meta=(Categories="GMS.MovementState"))
FGameplayTagContainer MovementStateContainer;
/**
* Current movement set.
* 当前运动集。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings", meta=(Categories="GMS.MovementSet"))
FGameplayTag MovementSet;
/**
* Container for movement set (for chooser only).
* 运动集的容器(仅用于选择器)。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings", meta=(Categories="GMS.MovementSet"))
FGameplayTagContainer MovementSetContainer;
/**
* Current overlay mode.
* 当前叠层模式。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings", meta=(Categories="GMS.OverlayMode"))
FGameplayTag OverlayMode{GMS_OverlayModeTags::Default};
/**
* Container for overlay mode (for chooser only).
* 叠层模式的容器(仅用于选择器)。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings", meta=(Categories="GMS.OverlayMode"))
FGameplayTagContainer OverlayModeContainer;
/**
* Enables ground prediction.
* 启用地面预测。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings")
bool bEnableGroundPrediction{false};
/**
* General animation settings.
* 通用动画设置。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Settings")
FGMS_AnimDataSetting_General GeneralSetting;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Settings", meta=(BlueprintThreadSafe))
TObjectPtr<const UGMS_MovementControlSetting_Default> ControlSetting{nullptr};
UPROPERTY(Transient)
FGMS_MovementBaseState MovementBase;
/**
* Locomotion state for the game thread.
* 游戏线程的运动状态。
*/
UPROPERTY(Transient)
FGMS_LocomotionState GameLocomotionState;
/**
* Root bone animation state.
* 根骨骼动画状态。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
FGMS_AnimState_Root RootState;
/**
* Locomotion animation state.
* 运动动画状态。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
FGMS_AnimState_Locomotion LocomotionState;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
FGMS_AnimState_Trajectory TrajectoryState;
/**
* View-related animation state.
* 视图相关动画状态。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
FGMS_AnimState_View ViewState;
/**
* Lean animation state.
* 倾斜动画状态。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
FGMS_AnimState_Lean LeanState;
/**
* Movement intent vector.
* 运动意图向量。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State", meta=(DisplayName="Movement Intent"))
FVector MovementIntent;
/**
* In-air animation state.
* 空中动画状态。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="State")
FGMS_AnimState_InAir InAirState;
/**
* Tags owned by the animation instance.
* 动画实例拥有的标签。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="State")
FGameplayTagContainer OwnedTags;
/**
* Tags indicating active animation nodes.
* 指示活跃动画节点的标签。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="State")
FGameplayTagContainer NodeRelevanceTags;
/**
* Indicates if any montage is playing.
* 指示是否有蒙太奇在播放。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="State")
bool bAnyMontagePlaying{false};
/**
* Indicates if the movement state has changed.
* 指示运动状态是否更改。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="State")
bool bMovementStateChanged{false};
/**
* Indicates if the locomotion mode has changed.
* 指示运动模式是否更改。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="State")
bool bLocomotionModeChanged{false};
/**
* Indicates if the movement set has changed.
* 指示运动集是否更改。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="State")
bool bMovementSetChanged{false};
/**
* Indicates if the rotation mode has changed.
* 指示旋转模式是否更改。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="State")
bool bRotationModeChanged{false};
/**
* Indicates if the overlay mode has changed.
* 指示叠层模式是否更改。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="State")
bool bOverlayModeChanged{false};
/**
* Indicates if movement is blocked.
* 指示移动是否被阻塞。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="State", meta=(DisplayName="Movement Blocked"))
bool bBlocked{false};
/**
* Indicates if the character has just landed.
* 指示角色是否刚刚着陆。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="State")
bool bJustLanded{false};
/**
* Indicates if this is the first update.
* 指示这是否是第一次更新。
*/
bool bFirstUpdate = true;
UPROPERTY()
TScriptInterface<IPoseSearchTrajectoryPredictorInterface> TrajectoryPredictor;
protected:
/**
* Reference to the movement system component.
* 运动系统组件的引用。
*/
UPROPERTY()
TObjectPtr<UGMS_MovementSystemComponent> MovementSystem;
/**
* Reference to the owning pawn.
* 拥有Pawn的引用。
*/
UPROPERTY(Transient)
TObjectPtr<APawn> PawnOwner;
/**
* Runtime mappings of animation state names to tags.
* 动画状态名称到标签的运行时映射。
*/
UPROPERTY()
TMap<TObjectPtr<UAnimInstance>, FGMS_AnimStateNameToTagWrapper> RuntimeAnimStateNameToTagMappings;
/**
* Timer handle for initial updates.
* 初始更新的计时器句柄。
*/
FTimerHandle InitialTimerHandle;
/**
* Animation layer instance for states.
* 状态动画层实例。
*/
UPROPERTY(VisibleInstanceOnly, Category="AnimLayers", meta=(ShowInnerProperties))
TObjectPtr<UGMS_AnimLayer> StateLayerInstance;
/**
* Animation layer instance for overlays.
* 叠层动画层实例。
*/
UPROPERTY(VisibleInstanceOnly, Category="AnimLayers", meta=(ShowInnerProperties))
TObjectPtr<UGMS_AnimLayer> OverlayLayerInstance;
/**
* Animation layer instance for view.
* 视图动画层实例。
*/
UPROPERTY(VisibleInstanceOnly, Category="AnimLayers", meta=(ShowInnerProperties))
TObjectPtr<UGMS_AnimLayer> ViewLayerInstance;
/**
* Animation layer instance for additive animations.
* 附加动画层实例。
*/
UPROPERTY(VisibleInstanceOnly, Category="AnimLayers", meta=(ShowInnerProperties))
TObjectPtr<UGMS_AnimLayer> AdditiveLayerInstance;
/**
* Animation layer instance for skeletal controls.
* 骨骼控制动画层实例。
*/
UPROPERTY(VisibleInstanceOnly, Category="AnimLayers", meta=(ShowInnerProperties))
TObjectPtr<UGMS_AnimLayer> SkeletonControlsLayerInstance;
};

View File

@@ -0,0 +1,57 @@
// // Copyright 2025 https://yuewu.dev/en All Rights Reserved.
//
// #pragma once
//
// #include "CoreMinimal.h"
// #include "GameFramework/CharacterMovementComponent.h"
// #include "Locomotions/GMS_LocomotionStructLibrary.h"
// #include "GMS_CharacterMovementComponent.generated.h"
//
// class UGMS_CharacterMovementSystemComponent;
// class UGMS_CMC_MovementMode;
//
// UCLASS(ClassGroup=(GMS), Blueprintable)
// class GENERICMOVEMENTSYSTEM_API UGMS_CharacterMovementComponent : public UCharacterMovementComponent
// {
// GENERATED_BODY()
//
// friend UGMS_CMC_MovementMode;
//
// public:
// // Sets default values for this component's properties
// UGMS_CharacterMovementComponent(const FObjectInitializer& ObjectInitializer);
//
// protected:
// virtual void InitializeComponent() override;
// virtual void SetUpdatedComponent(USceneComponent* NewUpdatedComponent) override;
// virtual bool HasValidData() const override;
//
// virtual void TickCharacterPose(float DeltaTime) override;
// virtual void PhysicsRotation(float DeltaTime) override;
//
// void GMS_TurnToDesiredRotation(const FRotator& CurrentRotation, FRotator DesiredRotation, FRotator DeltaRot);
//
// void GMS_TurnToDesiredRotationWithRotationRate(const FRotator& CurrentRotation, FRotator DesiredRotation, FRotator DeltaRot);
//
//
// UFUNCTION(BlueprintNativeEvent, Category="GMS|Movement", meta=(DisplayName="Physics Rotation"))
// void GMS_PhysicsRotation(float DeltaTime);
//
// UFUNCTION(BlueprintNativeEvent, Category="GMS|Movement", meta=(DisplayName="Compute Orient To Desired Movement Rotation"))
// FRotator GMS_ComputeOrientToDesiredMovementRotation(const FRotator& CurrentRotation, float DeltaTime, FRotator& DeltaRotation) const;
//
// UFUNCTION(BlueprintNativeEvent, Category="GMS|Movement", meta=(DisplayName="Compute Orient To Desired View Rotation"))
// FRotator GMS_ComputeOrientToDesiredViewRotation(const FRotator& CurrentRotation, float DeltaTime, FRotator& DeltaRotation) const;
//
// UFUNCTION(BlueprintNativeEvent, Category="GMS|Movement", meta=(DisplayName="Compute Orient To Desired View Rotation"))
// bool HasAnimationRotationDeltaYawAngle(float DeltaTime, float& OutDeltaYawAngle) const;
//
// /**
// * Using the default physic rotation of CMC, instead of GMS one.
// */
// UPROPERTY(Category="Character Movement (Rotation Settings)", EditAnywhere, BlueprintReadWrite)
// bool bUseNativeRotation{false};
//
// UPROPERTY(Transient, DuplicateTransient)
// TObjectPtr<UGMS_CharacterMovementSystemComponent> MovementSystem;
// };

View File

@@ -0,0 +1,16 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "DefaultMovementSet/Modes/FlyingMode.h"
#include "GMS_FlyingMode.generated.h"
/**
*
*/
UCLASS()
class GENERICMOVEMENTSYSTEM_API UGMS_FlyingMode : public UFlyingMode
{
GENERATED_BODY()
};

View File

@@ -0,0 +1,18 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "MovementMode.h"
#include "UObject/Object.h"
#include "GMS_MoverSettingObjectLibrary.generated.h"
/**
* CommonLegacyMovementSettings: collection of settings that are shared between several of the legacy movement modes
*/
UCLASS(BlueprintType)
class GENERICMOVEMENTSYSTEM_API UGMS_MoverGroundedMovementSettings : public UObject
{
GENERATED_BODY()
};

View File

@@ -0,0 +1,159 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "MoverTypes.h"
#include "GMS_MoverStructLibrary.generated.h"
/**
* Data block containing extended movement actions inputs used by Generic Movement System.
* 包含GMS所用到的运动行为输入定义的数据块。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_MoverActionInputs : public FMoverDataStructBase
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadWrite, Category = Mover)
bool bIsDashJustPressed = false;
UPROPERTY(BlueprintReadWrite, Category = Mover)
bool bIsAimPressed = false;
UPROPERTY(BlueprintReadWrite, Category = Mover)
bool bIsVaultJustPressed = false;
UPROPERTY(BlueprintReadWrite, Category = Mover)
bool bWantsToStartZiplining = false;
UPROPERTY(BlueprintReadWrite, Category = Mover)
bool bWantsToBeCrouched = false;
// @return newly allocated copy of this FGMS_MoverActionInputs. Must be overridden by child classes
virtual FMoverDataStructBase* Clone() const override
{
// TODO: ensure that this memory allocation jives with deletion method
FGMS_MoverActionInputs* CopyPtr = new FGMS_MoverActionInputs(*this);
return CopyPtr;
}
virtual bool NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess) override
{
Super::NetSerialize(Ar, Map, bOutSuccess);
Ar.SerializeBits(&bIsDashJustPressed, 1);
Ar.SerializeBits(&bIsAimPressed, 1);
Ar.SerializeBits(&bIsVaultJustPressed, 1);
Ar.SerializeBits(&bWantsToStartZiplining, 1);
Ar.SerializeBits(&bWantsToBeCrouched, 1);
bOutSuccess = true;
return true;
}
virtual UScriptStruct* GetScriptStruct() const override { return StaticStruct(); }
virtual void ToString(FAnsiStringBuilderBase& Out) const override
{
Super::ToString(Out);
Out.Appendf("bIsDashJustPressed: %i\n", bIsDashJustPressed);
Out.Appendf("bIsAimPressed: %i\n", bIsAimPressed);
Out.Appendf("bIsVaultJustPressed: %i\n", bIsVaultJustPressed);
Out.Appendf("bWantsToStartZiplining: %i\n", bWantsToStartZiplining);
Out.Appendf("bWantsToBeCrouched: %i\n", bWantsToBeCrouched);
}
virtual void AddReferencedObjects(FReferenceCollector& Collector) override { Super::AddReferencedObjects(Collector); }
};
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_MoverTagInputs : public FMoverDataStructBase
{
GENERATED_USTRUCT_BODY()
/**
* A container which you can query tag to indicate a input is requested.
*/
UPROPERTY(BlueprintReadWrite, Category = Mover)
FGameplayTagContainer Tags;
// @return newly allocated copy of this FGMS_MoverActionInputs. Must be overridden by child classes
virtual FMoverDataStructBase* Clone() const override
{
// TODO: ensure that this memory allocation jives with deletion method
FGMS_MoverTagInputs* CopyPtr = new FGMS_MoverTagInputs(*this);
return CopyPtr;
}
virtual bool NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess) override
{
bool bSuccess = Super::NetSerialize(Ar, Map, bOutSuccess);
Tags.NetSerialize(Ar, Map, bSuccess);
return bSuccess;
}
virtual UScriptStruct* GetScriptStruct() const override { return StaticStruct(); }
virtual void ToString(FAnsiStringBuilderBase& Out) const override
{
Super::ToString(Out);
Out.Appendf("MoverTagInputs Tags[%s] \n", *Tags.ToString());
}
virtual void AddReferencedObjects(FReferenceCollector& Collector) override { Super::AddReferencedObjects(Collector); }
};
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_MoverMovementControlInputs : public FMoverDataStructBase
{
GENERATED_USTRUCT_BODY()
/**
* 当前是什么运动集?影响角色动画表现方式。
*/
UPROPERTY(BlueprintReadWrite, Category = Mover)
FGameplayTag DesiredMovementSet;
/**
* 想以什么状态移动?影响角色移动参数
*/
UPROPERTY(BlueprintReadWrite, Category = Mover)
FGameplayTag DesiredMovementState;
/**
* 想以什么方式旋转?影响角色的朝向方式。
*/
UPROPERTY(BlueprintReadWrite, Category = Mover)
FGameplayTag DesiredRotationMode;
// @return newly allocated copy of this FGMS_MoverActionInputs. Must be overridden by child classes
virtual FMoverDataStructBase* Clone() const override
{
// TODO: ensure that this memory allocation jives with deletion method
FGMS_MoverMovementControlInputs* CopyPtr = new FGMS_MoverMovementControlInputs(*this);
return CopyPtr;
}
virtual bool NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess) override
{
bool bSuccess = Super::NetSerialize(Ar, Map, bOutSuccess);
DesiredMovementSet.NetSerialize(Ar, Map, bSuccess);
DesiredMovementState.NetSerialize(Ar, Map, bSuccess);
DesiredRotationMode.NetSerialize(Ar, Map, bSuccess);
return bSuccess;
}
virtual UScriptStruct* GetScriptStruct() const override { return StaticStruct(); }
virtual void ToString(FAnsiStringBuilderBase& Out) const override
{
Super::ToString(Out);
Out.Appendf("DesiredMovementSet [%s] \n", *DesiredMovementSet.ToString());
Out.Appendf("DesiredMovementState [%s] \n", *DesiredMovementState.ToString());
Out.Appendf("DesiredRotationMode [%s] \n", *DesiredRotationMode.ToString());
}
virtual void AddReferencedObjects(FReferenceCollector& Collector) override { Super::AddReferencedObjects(Collector); }
};

View File

@@ -0,0 +1,84 @@
// // Copyright 2025 https://yuewu.dev/en All Rights Reserved.
//
// #pragma once
//
// #include "CoreMinimal.h"
// #include "MovementModifier.h"
// #include "UObject/Object.h"
// #include "GMS_MovementStateModifer.generated.h"
//
//
// USTRUCT(BlueprintType)
// struct FGMS_MovementStateModifierEntry
// {
// GENERATED_BODY()
//
// /** Maximum speed in the movement plane */
// UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="General", meta = (ClampMin = "0", UIMin = "0", ForceUnits = "cm/s"))
// float MaxSpeed = 800.f;
//
// /** Default max linear rate of deceleration when there is no controlled input */
// UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="General", meta = (ClampMin = "0", UIMin = "0", ForceUnits = "cm/s^2"))
// float Deceleration = 4000.f;
//
// /** Default max linear rate of acceleration for controlled input. May be scaled based on magnitude of input. */
// UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="General", meta = (ClampMin = "0", UIMin = "0", ForceUnits = "cm/s^2"))
// float Acceleration = 4000.f;
//
// /** Maximum rate of turning rotation (degrees per second). Negative numbers indicate instant rotation and should cause rotation to snap instantly to desired direction. */
// UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="General", meta = (ClampMin = "-1", UIMin = "0", ForceUnits = "degrees/s"))
// float TurningRate = 500.f;
//
// /** Speeds velocity direction changes while turning, to reduce sliding */
// UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="General", meta = (ClampMin = "0", UIMin = "0", ForceUnits = "Multiplier"))
// float TurningBoost = 8.f;
// };
//
//
// /**
// * States: Applies settings to the actor to make them go into different states like walk or jog, sprint, affects actor speed and acceleration/deceleration, turn etc,
// */
// USTRUCT(BlueprintType)
// struct MOVER_API FGMS_MovementStateModifier : public FMovementModifierBase
// {
// GENERATED_BODY()
//
// FGMS_MovementStateModifier();
//
// virtual ~FGMS_MovementStateModifier() override
// {
// }
//
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Modifer")
// bool bRevertMovementSettingsOnEnd{false};
//
// /** Fired when this modifier is activated. */
// virtual void OnStart(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep, const FMoverSyncState& SyncState, const FMoverAuxStateContext& AuxState) override;
//
// /** Fired when this modifier is deactivated. */
// virtual void OnEnd(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep, const FMoverSyncState& SyncState, const FMoverAuxStateContext& AuxState) override;
//
// /** Fired just before a Substep */
// virtual void OnPreMovement(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep) override;
//
// /** Fired after a Substep */
// virtual void OnPostMovement(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep, const FMoverSyncState& SyncState, const FMoverAuxStateContext& AuxState) override;
//
// // @return newly allocated copy of this FMovementModifier. Must be overridden by child classes
// virtual FMovementModifierBase* Clone() const override;
//
// virtual void NetSerialize(FArchive& Ar) override;
//
// virtual UScriptStruct* GetScriptStruct() const override;
//
// virtual FString ToSimpleString() const override;
//
// virtual void AddReferencedObjects(class FReferenceCollector& Collector) override;
//
// protected:
// // Applies any movement settings like acceleration or max speed changes
// void ApplyMovementSettings(UMoverComponent* MoverComp);
//
// // Reverts any movement settings like acceleration or max speed changes
// void RevertMovementSettings(UMoverComponent* MoverComp);
// };

View File

@@ -0,0 +1,20 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "DefaultMovementSet/Modes/WalkingMode.h"
#include "GMS_WalkingMode.generated.h"
class UGMS_MoverGroundedMovementSettings;
/**
*
*/
UCLASS()
class GENERICMOVEMENTSYSTEM_API UGMS_WalkingMode : public UWalkingMode
{
GENERATED_BODY()
public:
UGMS_WalkingMode(const FObjectInitializer& ObjectInitializer);
};

View File

@@ -0,0 +1,30 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "GMS_ZiplineInterface.generated.h"
// This class does not need to be modified.
UINTERFACE()
class GENERICMOVEMENTSYSTEM_API UGMS_ZiplineInterface : public UInterface
{
GENERATED_BODY()
};
/**
*
*/
class GENERICMOVEMENTSYSTEM_API IGMS_ZiplineInterface
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Zipline")
USceneComponent* GetStartComponent();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Zipline")
USceneComponent* GetEndComponent();
};

View File

@@ -0,0 +1,51 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Runtime/Launch/Resources/Version.h"
#include "GMS_ZipliningMode.h"
#include "MovementModeTransition.h"
#include "GMS_ZiplineModeTransition.generated.h"
/**
* Transition that handles starting ziplining based on input. Character must be airborne to catch the
* zipline, regardless of input.
*/
UCLASS(Blueprintable, BlueprintType)
class UGMS_ZiplineStartTransition : public UBaseMovementModeTransition
{
GENERATED_UCLASS_BODY()
#if ENGINE_MINOR_VERSION >= 6
virtual FTransitionEvalResult Evaluate_Implementation(const FSimulationTickParams& Params) const override;
#else
virtual FTransitionEvalResult OnEvaluate(const FSimulationTickParams& Params) const override;
#endif
UPROPERTY(EditAnywhere, Category = "Ziplining")
FName ZipliningModeName = ExtendedModeNames::Ziplining;
UPROPERTY(EditAnywhere, Category = "Ziplining")
FGameplayTag ZipliningInputTag;
};
/**
* Transition that handles exiting ziplining based on input
*/
UCLASS(Blueprintable, BlueprintType)
class UGMS_ZiplineEndTransition : public UBaseMovementModeTransition
{
GENERATED_UCLASS_BODY()
#if ENGINE_MINOR_VERSION >= 6
virtual FTransitionEvalResult Evaluate_Implementation(const FSimulationTickParams& Params) const override;
virtual void Trigger_Implementation(const FSimulationTickParams& Params) override;
#else
virtual FTransitionEvalResult OnEvaluate(const FSimulationTickParams& Params) const override;
virtual void OnTrigger(const FSimulationTickParams& Params) override;
#endif
// Mode to enter when exiting the zipline
UPROPERTY(EditAnywhere, Category = "Ziplining")
FName AutoExitToMode = DefaultModeNames::Falling;
};

View File

@@ -0,0 +1,55 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Runtime/Launch/Resources/Version.h"
#include "MovementMode.h"
#include "GMS_ZipliningMode.generated.h"
namespace ExtendedModeNames
{
const FName Ziplining = TEXT("Ziplining");
}
// ZipliningMode: movement mode that traverses an actor implementing the IZipline interface
UCLASS(Blueprintable, BlueprintType)
class UGMS_ZipliningMode : public UBaseMovementMode
{
GENERATED_UCLASS_BODY()
#if ENGINE_MINOR_VERSION >=6
virtual void GenerateMove_Implementation(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, FProposedMove& OutProposedMove) const override;
virtual void SimulationTick_Implementation(const FSimulationTickParams& Params, FMoverTickEndData& OutputState) override;
#else
virtual void OnGenerateMove(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, FProposedMove& OutProposedMove) const override;
virtual void OnSimulationTick(const FSimulationTickParams& Params, FMoverTickEndData& OutputState) override;
#endif
// Maximum speed
UPROPERTY(EditAnywhere, Category = "Ziplining", meta = (ClampMin = "1", UIMin = "1", ForceUnits = "cm/s"))
float MaxSpeed = 1000.0f;
};
// Data block containing ziplining state info, used while ZipliningMode is active
USTRUCT()
struct FGMS_ZipliningState : public FMoverDataStructBase
{
GENERATED_USTRUCT_BODY()
TObjectPtr<AActor> ZiplineActor;
bool bIsMovingAtoB;
FGMS_ZipliningState()
: bIsMovingAtoB(true)
{
}
virtual FMoverDataStructBase* Clone() const override;
virtual bool NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess) override;
virtual UScriptStruct* GetScriptStruct() const override { return StaticStruct(); }
virtual void ToString(FAnsiStringBuilderBase& Out) const override;
virtual bool ShouldReconcile(const FMoverDataStructBase& AuthorityState) const override;
virtual void Interpolate(const FMoverDataStructBase& From, const FMoverDataStructBase& To, float Pct) override;
};

View File

@@ -0,0 +1,61 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "Animation/AnimNodeBase.h"
#include "GMS_AnimNode_CurvesBlend.generated.h"
UENUM(BlueprintType)
enum class EGMS_CurvesBlendMode : uint8
{
// Blend poses using blend amount. Same as ECurveBlendOption::BlendByWeight.
BlendByAmount,
// Only set the value if the curves pose has the curve value. Same as ECurveBlendOption::Override.
Combine,
// Only set the value if the source pose doesn't have the curve value. Same as ECurveBlendOption::DoNotOverride.
CombinePreserved,
// Find the highest curve value from multiple poses and use that. Same as ECurveBlendOption::UseMaxValue.
UseMaxValue,
// Find the lowest curve value from multiple poses and use that. Same as ECurveBlendOption::UseMinValue.
UseMinValue,
// Completely override source pose. Same as ECurveBlendOption::UseBasePose.
Override
};
USTRUCT(BlueprintInternalUseOnly)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimNode_CurvesBlend : public FAnimNode_Base
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings")
FPoseLink SourcePose;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings")
FPoseLink CurvesPose;
#if WITH_EDITORONLY_DATA
UPROPERTY(EditAnywhere, Category = "Settings", Meta = (ClampMin = 0, ClampMax = 1, FoldProperty, PinShownByDefault))
float BlendAmount{1.0f};
UPROPERTY(EditAnywhere, Category = "Settings", Meta = (FoldProperty))
EGMS_CurvesBlendMode BlendMode{EGMS_CurvesBlendMode::BlendByAmount};
#endif
public:
virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override;
virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) override;
virtual void Update_AnyThread(const FAnimationUpdateContext& Context) override;
virtual void Evaluate_AnyThread(FPoseContext& Output) override;
virtual void GatherDebugData(FNodeDebugData& DebugData) override;
public:
float GetBlendAmount() const;
EGMS_CurvesBlendMode GetBlendMode() const;
};

View File

@@ -0,0 +1,34 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "GameplayTagContainer.h"
#include "AnimNodes/AnimNode_BlendListBase.h"
#include "GMS_AnimNode_GameplayTagsBlend.generated.h"
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimNode_GameplayTagsBlend : public FAnimNode_BlendListBase
{
GENERATED_BODY()
public:
#if WITH_EDITORONLY_DATA
UPROPERTY(EditAnywhere, Category="Settings", Meta = (FoldProperty, PinShownByDefault))
FGameplayTag ActiveTag;
UPROPERTY(EditAnywhere, Category="Settings", Meta = (FoldProperty))
TArray<FGameplayTag> Tags;
#endif
protected:
virtual int32 GetActiveChildIndex() override;
public:
const FGameplayTag& GetActiveTag() const;
const TArray<FGameplayTag>& GetTags() const;
#if WITH_EDITOR
void RefreshPoses();
#endif
};

View File

@@ -0,0 +1,168 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Animation/AnimTypes.h"
#include "Animation/AnimNodeBase.h"
#include "Animation/AnimData/BoneMaskFilter.h"
#include "Settings/GMS_SettingStructLibrary.h"
#include "GMS_AnimNode_LayeredBoneBlend.generated.h"
// Layered blend (per bone); has dynamic number of blendposes that can blend per different bone sets
USTRUCT(BlueprintInternalUseOnly)
struct FGMS_AnimNode_LayeredBoneBlend : public FAnimNode_Base
{
GENERATED_USTRUCT_BODY()
public:
/** The source pose */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links)
FPoseLink BasePose;
/** Each layer's blended pose */
UPROPERTY(EditAnywhere, BlueprintReadWrite, editfixedsize, Category=Links, meta=(BlueprintCompilerGeneratedDefaults))
TArray<FPoseLink> BlendPoses;
/** Whether to use branch filters or a blend mask to specify an input pose per-bone influence */
// UPROPERTY(EditAnywhere, Category = Config)
// ELayeredBoneBlendMode BlendMode;
/**
* The blend masks to use for our layer inputs. Allows the use of per-bone alphas.
* Blend masks are used when BlendMode is BlendMask.
*/
// UPROPERTY(EditAnywhere, editfixedsize, Category=Config, meta=(UseAsBlendMask=true))
// TArray<TObjectPtr<UBlendProfile>> BlendMasks;
/**
* Configuration for the parts of the skeleton to blend for each layer. Allows
* certain parts of the tree to be blended out or omitted from the pose.
* LayerSetup is used when BlendMode is BranchFilter.
*/
UPROPERTY()
TArray<FInputBlendPose> LayerSetup;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(PinShownByDefault), Category=GMS)
FGMS_InputBlendPose ExternalLayerSetup;
/** The weights of each layer */
UPROPERTY(EditAnywhere, BlueprintReadWrite, editfixedsize, Category=GMS, meta=(BlueprintCompilerGeneratedDefaults, PinShownByDefault))
TArray<float> BlendWeights;
/** Whether to blend bone rotations in mesh space or in local space */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Config, meta=(PinShownByDefault))
bool bMeshSpaceRotationBlend;
/** Whether to blend bone scales in mesh space or in local space */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Config)
bool bMeshSpaceScaleBlend;
/** How to blend the layers together */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Config)
TEnumAsByte<enum ECurveBlendOption::Type> CurveBlendOption;
/** Whether to incorporate the per-bone blend weight of the root bone when lending root motion */
UPROPERTY(EditAnywhere, Category = Config)
bool bBlendRootMotionBasedOnRootBone;
bool bHasRelevantPoses;
/*
* Max LOD that this node is allowed to run
* For example if you have LODThreshold to be 2, it will run until LOD 2 (based on 0 index)
* when the component LOD becomes 3, it will stop update/evaluate
* currently transition would be issue and that has to be re-visited
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Performance, meta = (DisplayName = "LOD Threshold"))
int32 LODThreshold;
protected:
// Per-bone weights for the skeleton. Serialized as these are only relative to the skeleton, but can potentially
// be regenerated at runtime if the GUIDs dont match
UPROPERTY()
TArray<FPerBoneBlendWeight> PerBoneBlendWeights;
// Guids for skeleton used to determine whether the PerBoneBlendWeights need rebuilding
UPROPERTY()
FGuid SkeletonGuid;
// Guid for virtual bones used to determine whether the PerBoneBlendWeights need rebuilding
UPROPERTY()
FGuid VirtualBoneGuid;
// transient data to handle weight and target weight
// this array changes based on required bones
TArray<FPerBoneBlendWeight> DesiredBoneBlendWeights;
TArray<FPerBoneBlendWeight> CurrentBoneBlendWeights;
// Per-curve source pose index
TBaseBlendedCurve<FDefaultAllocator, UE::Anim::FCurveElementIndexed> CurvePoseSourceIndices;
// Serial number of the required bones container
uint16 RequiredBonesSerialNumber;
public:
FGMS_AnimNode_LayeredBoneBlend()
: bMeshSpaceRotationBlend(false)
, bMeshSpaceScaleBlend(false)
, CurveBlendOption(ECurveBlendOption::Override)
, bBlendRootMotionBasedOnRootBone(true)
, bHasRelevantPoses(false)
, LODThreshold(INDEX_NONE)
, RequiredBonesSerialNumber(0)
{
}
// FAnimNode_Base interface
GENERICMOVEMENTSYSTEM_API virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override;
GENERICMOVEMENTSYSTEM_API virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) override;
GENERICMOVEMENTSYSTEM_API virtual void Update_AnyThread(const FAnimationUpdateContext& Context) override;
GENERICMOVEMENTSYSTEM_API virtual void Evaluate_AnyThread(FPoseContext& Output) override;
GENERICMOVEMENTSYSTEM_API virtual void GatherDebugData(FNodeDebugData& DebugData) override;
virtual int32 GetLODThreshold() const override { return LODThreshold; }
// End of FAnimNode_Base interface
void AddPose()
{
BlendWeights.Add(1.f);
BlendPoses.AddDefaulted();
LayerSetup.AddDefaulted();
}
void RemovePose(int32 PoseIndex)
{
BlendWeights.RemoveAt(PoseIndex);
BlendPoses.RemoveAt(PoseIndex);
if (LayerSetup.IsValidIndex(PoseIndex))
{
LayerSetup.RemoveAt(PoseIndex);
}
}
// Invalidate the cached per-bone blend weights from the skeleton
void InvalidatePerBoneBlendWeights()
{
RequiredBonesSerialNumber = 0;
SkeletonGuid = FGuid();
VirtualBoneGuid = FGuid();
}
// Invalidates the cached bone data so it is recalculated the next time this node is updated
void InvalidateCachedBoneData() { RequiredBonesSerialNumber = 0; }
public:
// Rebuild cache per bone blend weights from the skeleton
GENERICMOVEMENTSYSTEM_API void RebuildPerBoneBlendWeights(const USkeleton* InSkeleton);
// Check whether per-bone blend weights are valid according to the skeleton (GUID check)
GENERICMOVEMENTSYSTEM_API bool ArePerBoneBlendWeightsValid(const USkeleton* InSkeleton) const;
// Update cached data if required
GENERICMOVEMENTSYSTEM_API void UpdateCachedBoneData(const FBoneContainer& RequiredBones, const USkeleton* Skeleton);
friend class UAnimGraphNode_LayeredBoneBlend;
};

View File

@@ -0,0 +1,205 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "BoneControllers/AnimNode_OrientationWarping.h"
#include "BoneControllers/BoneControllerTypes.h"
#include "BoneControllers/AnimNode_SkeletalControlBase.h"
#include "Settings/GMS_SettingStructLibrary.h"
#include "GMS_AnimNode_OrientationWarping.generated.h"
struct FAnimationInitializeContext;
struct FComponentSpacePoseContext;
struct FNodeDebugData;
USTRUCT(BlueprintInternalUseOnly)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimNode_OrientationWarping : public FAnimNode_SkeletalControlBase
{
GENERATED_BODY()
// Orientation warping evaluation mode (Graph or Manual)
UPROPERTY(EditAnywhere,BlueprintReadWrite, Category=Evaluation, meta=(PinShownByDefault))
EWarpingEvaluationMode Mode = EWarpingEvaluationMode::Manual;
// The desired orientation angle (in degrees) to warp by relative to the specified RotationAxis
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Evaluation, meta=(PinShownByDefault))
float OrientationAngle = 0.f;
// The character locomotion angle (in degrees) relative to the specified RotationAxis
// This will be used in the following equation for computing the orientation angle: [Orientation = RotationBetween(RootMotionDirection, LocomotionDirection)]
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Evaluation, meta=(PinShownByDefault))
float LocomotionAngle = 0.f;
// The character movement direction vector in world space
// This will be used to compute LocomotionAngle automatically
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Evaluation, meta=(PinShownByDefault))
FVector LocomotionDirection = { 0.f, 0.f, 0.f };
// Minimum root motion speed required to apply orientation warping
// This is useful to prevent unnatural re-orientation when the animation has a portion with no root motion (i.e starts/stops/idles)
// When this value is greater than 0, it's recommended to enable interpolation with RotationInterpSpeed > 0
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Evaluation, meta = (ClampMin = "0.0", PinHiddenByDefault))
float MinRootMotionSpeedThreshold = 10.0f;
// Specifies an angle threshold to prevent erroneous over-rotation of the character, disabled with a value of 0
//
// When the effective orientation warping angle is detected to be greater than this value (default: 90 degrees) the locomotion direction will be inverted prior to warping
// This will be used in the following equation: [Orientation = RotationBetween(RootMotionDirection, -LocomotionDirection)]
//
// Example: Playing a forward running animation while the motion is going backward
// Rather than orientation warping by 180 degrees, the system will warp by 0 degrees
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Evaluation, meta=(PinHiddenByDefault), meta=(ClampMin="0.0", ClampMax="180.0"))
float LocomotionAngleDeltaThreshold = 90.f;
// Spine bone definitions
// Used to counter rotate the body in order to keep the character facing forward
// The amount of counter rotation applied is driven by DistributedBoneOrientationAlpha
// UPROPERTY(EditAnywhere, Category="Settings") 由ExternalBoneReference代替代替。
UPROPERTY()
TArray<FBoneReference> SpineBones;
// IK Foot Root Bone definition
// UPROPERTY(EditAnywhere, Category="Settings", meta=(DisplayName="IK Foot Root Bone")) 由ExternalBoneReference代替。
UPROPERTY()
FBoneReference IKFootRootBone;
// IK Foot definitions
// UPROPERTY(EditAnywhere, Category="Settings", meta=(DisplayName="IK Foot Bones")) 由ExternalBoneReference代替。
UPROPERTY()
TArray<FBoneReference> IKFootBones;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= Settings, meta=(PinShownByDefault))
FGMS_OrientationWarpingBoneReference ExternalBoneReference;
// Rotation axis used when rotating the character body
UPROPERTY(EditAnywhere, Category=Settings)
TEnumAsByte<EAxis::Type> RotationAxis = EAxis::Z;
// Specifies how much rotation is applied to the character body versus IK feet
UPROPERTY(EditAnywhere, Category=Settings, meta=(ClampMin="0.0", ClampMax="1.0", PinHiddenByDefault))
float DistributedBoneOrientationAlpha = 0.5f;
// Specifies the interpolation speed (in Alpha per second) towards reaching the final warped rotation angle
// A value of 0 will cause instantaneous rotation, while a greater value will introduce smoothing
UPROPERTY(EditAnywhere, Category=Settings, meta=(ClampMin="0.0"))
float RotationInterpSpeed = 10.f;
// Max correction we're allowed to do per-second when using interpolation.
// This minimizes pops when we have a large difference between current and target orientation.
UPROPERTY(EditAnywhere, Category=Settings, meta=(ClampMin="0.0", EditCondition="RotationInterpSpeed > 0.0f"))
float MaxCorrectionDegrees = 180.f;
// Don't compensate our interpolator when the instantaneous root motion delta is higher than this. This is likely a pivot.
UPROPERTY(EditAnywhere, Category=Settings, meta=(ClampMin="0.0", EditCondition="RotationInterpSpeed > 0.0f"))
float MaxRootMotionDeltaToCompensateDegrees = 45.f;
// Whether to counter compensate interpolation by the animated root motion angle change over time.
// This helps to conserve the motion from our animation.
// Disable this if your root motion is expected to be jittery, and you want orientation warping to smooth it out.
UPROPERTY(EditAnywhere, Category=Settings, meta=(EditCondition="RotationInterpSpeed > 0.0f"))
bool bCounterCompenstateInterpolationByRootMotion = true;
UPROPERTY(EditAnywhere, Category=Experimental, meta=(PinHiddenByDefault))
bool bScaleByGlobalBlendWeight = false;
UPROPERTY(EditAnywhere, Category=Experimental, meta=(PinHiddenByDefault))
bool bUseManualRootMotionVelocity = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Experimental, meta=(PinHiddenByDefault))
FVector ManualRootMotionVelocity = FVector::ZeroVector;
//RootBoneTransform is the same as CustomTransform as
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Evaluation, meta=(PinHiddenByDefault))
EOrientationWarpingSpace WarpingSpace = EOrientationWarpingSpace::ComponentTransform;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Evaluation, meta=(PinHiddenByDefault))
FTransform WarpingSpaceTransform;
#if WITH_EDITORONLY_DATA
// Scale all debug drawing visualization by a factor
UPROPERTY(EditAnywhere, Category=Debug, meta=(ClampMin="0.0"))
float DebugDrawScale = 1.f;
// Enable/Disable orientation warping debug drawing
UPROPERTY(EditAnywhere, Category=Debug)
bool bEnableDebugDraw = false;
#endif
public:
// FAnimNode_Base interface
virtual void GatherDebugData(FNodeDebugData& DebugData) override;
virtual void UpdateInternal(const FAnimationUpdateContext& Context) override;
// End of FAnimNode_Base interface
// FAnimNode_SkeletalControlBase interface
virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override;
virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms) override;
virtual bool IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) override;
// End of FAnimNode_SkeletalControlBase interface
private:
// FAnimNode_SkeletalControlBase interface
virtual void InitializeBoneReferences(const FBoneContainer& RequiredBones) override;
// End of FAnimNode_SkeletalControlBase interface
struct FOrientationWarpingSpineBoneData
{
FCompactPoseBoneIndex BoneIndex;
float Weight;
FOrientationWarpingSpineBoneData()
: BoneIndex(INDEX_NONE)
, Weight(0.f)
{
}
FOrientationWarpingSpineBoneData(FCompactPoseBoneIndex InBoneIndex)
: BoneIndex(InBoneIndex)
, Weight(0.f)
{
}
// Comparison Operator for Sorting
struct FCompareBoneIndex
{
FORCEINLINE bool operator()(const FOrientationWarpingSpineBoneData& A, const FOrientationWarpingSpineBoneData& B) const
{
return A.BoneIndex < B.BoneIndex;
}
};
};
struct FOrientationWarpingFootData
{
TArray<FCompactPoseBoneIndex> IKFootBoneIndexArray;
FCompactPoseBoneIndex IKFootRootBoneIndex;
FOrientationWarpingFootData()
: IKFootBoneIndexArray()
, IKFootRootBoneIndex(INDEX_NONE)
{
}
};
// Computed spine bone indices and alpha weights for the specified spine definition
TArray<FOrientationWarpingSpineBoneData> SpineBoneDataArray;
// Computed IK bone indices for the specified foot definitions
FOrientationWarpingFootData IKFootData;
// Internal current frame root motion delta direction
FVector RootMotionDeltaDirection = FVector::ZeroVector;
// Internal orientation warping angle
float ActualOrientationAngleRad = 0.f;
float BlendWeight = 0.0f;
FGraphTraversalCounter UpdateCounter;
bool bIsFirstUpdate = false;
void Reset(const FAnimationBaseContext& Context);
#if WITH_EDITORONLY_DATA
// Whether we found a root motion delta attribute in the attribute stream on graph driven mode
bool bFoundRootMotionAttribute = false;
#endif
};

View File

@@ -0,0 +1,78 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "GMS_SettingEnumLibrary.generated.h"
/**
* Defines rotation behavior when the character is in the air.
* 定义角色在空中的旋转行为。
*/
UENUM(BlueprintType)
enum class EGMS_InAirRotationMode : uint8
{
// Rotate to velocity direction on jump. 在跳跃时旋转到速度方向。
RotateToVelocityOnJump,
// Maintain relative rotation. 保持相对旋转。
KeepRelativeRotation,
// Maintain world rotation. 保持世界旋转。
KeepWorldRotation
};
/**
* Defines how turn-in-place animations are triggered.
* 定义如何触发原地转向动画。
*/
UENUM(BlueprintType)
enum class EGMS_TurnInPlacePlayMethod : uint8
{
Graph, // Trigger turn-in-place in animation graph. 在动画图中触发原地转向。
Montage // Trigger turn-in-place as a dynamic slot montage. 作为动态槽蒙太奇触发原地转向。
};
/**
* Defines how overlay animations are played.
* 定义如何播放覆盖动画。
*/
UENUM(BlueprintType)
enum class EGMS_OverlayPlayMode : uint8
{
SequencePlayer, // Play as a sequence player. 作为序列播放器播放。
SequenceEvaluator // Play as a sequence evaluator. 作为序列评估器播放。
};
/**
* Defines the blending mode for layered bone animations.
* 定义分层骨骼动画的混合模式。
*/
UENUM(BlueprintType)
enum class EGMS_LayeredBoneBlendMode : uint8
{
BranchFilter, // Use branch filter for blending. 使用分支过滤器进行混合。
BlendMask // Use blend mask for blending. 使用混合蒙版进行混合。
};
/**
* Defines how velocity direction is determined.
* 定义如何确定速度方向。
*/
UENUM(BlueprintType)
enum class EGMS_VelocityDirectionMode_DEPRECATED : uint8
{
OrientToLastVelocityDirection, // Orient to the last velocity direction. 朝向最后的速度方向。
OrientToInputDirection, // Orient to the input direction. 朝向输入方向。
TurningCircle // Use a turning circle for orientation. 使用转向圆进行朝向。
};
/**
* Defines view direction modes.
* 定义视图方向模式。
*/
UENUM(BlueprintType)
enum class EGMS_ViewDirectionMode_DEPRECATED : uint8
{
Default, // Default view direction mode. 默认视图方向模式。
Aiming // Aiming view direction mode. 瞄准视图方向模式。
};

View File

@@ -0,0 +1,221 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_SettingStructLibrary.h"
#include "UObject/Object.h"
#include "Engine/DataAsset.h"
#include "Runtime/Launch/Resources/Version.h"
#if ENGINE_MINOR_VERSION < 5
#include "InstancedStruct.h"
#else
#include "StructUtils/InstancedStruct.h"
#endif
#include "Utility/GMS_Tags.h"
#include "GMS_SettingObjectLibrary.generated.h"
#pragma region CommonSettings
class UGMS_AnimLayer;
class UGMS_AnimLayerSetting;
/**
* Defines multiple movement set settings for a character.
* 定义角色的多个运动集设置。
*/
UCLASS(BlueprintType, Const)
class GENERICMOVEMENTSYSTEM_API UGMS_MovementDefinition : public UDataAsset
{
GENERATED_BODY()
public:
/**
* Map of gameplay tags to movement set settings.
* 游戏标签到运动集设置的映射。
*/
UPROPERTY(EditAnywhere, Category="GMS", meta=(ForceInlineRow))
TMap<FGameplayTag, FGMS_MovementSetSetting> MovementSets;
#if WITH_EDITOR
/**
* Called before saving the asset in the editor.
* 在编辑器中保存资产前调用。
* @param SaveContext The save context. 保存上下文。
*/
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
/**
* Validates data in the editor.
* 在编辑器中验证数据。
* @param Context The validation context. 验证上下文。
* @return The validation result. 验证结果。
*/
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
#endif
};
/**
* Stores animation graph-specific settings, one per unique skeleton.
* 存储动画图特定的设置,每个唯一骨架一个。
*/
UCLASS()
class GENERICMOVEMENTSYSTEM_API UGMS_AnimGraphSetting : public UDataAsset
{
GENERATED_BODY()
public:
/**
* Maps animation layer settings to their corresponding animation layer implementations.
* 将动画层设置映射到对应的动画层实现。
* @note Add custom animation layer settings/implementations to this mapping. 将自定义动画层设置/实现添加到此映射。
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Settings")
TMap<TSubclassOf<UGMS_AnimLayerSetting>, TSubclassOf<UGMS_AnimLayer>> AnimLayerSettingToInstanceMapping;
/**
* Settings for orientation warping.
* 朝向扭曲的设置。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Settings")
FGMS_OrientationWarpingSettings OrientationWarping;
};
#pragma endregion
#pragma region ControlSettings
/**
* Default Movement control settings.
* 默认运动控制设置。
*/
UCLASS(BlueprintType, Blueprintable, Const, DisplayName="GMS Movement Control Setting")
class GENERICMOVEMENTSYSTEM_API UGMS_MovementControlSetting_Default : public UDataAsset
{
GENERATED_BODY()
public:
/**
* Matches a speed to a movement state tag based on a threshold.
* 根据阈值将速度匹配到运动状态标签。
* @param Speed The current speed. 当前速度。
* @param Threshold The speed threshold. 速度阈值。
* @return The matching gameplay tag. 匹配的游戏标签。
*/
FGameplayTag MatchStateTagBySpeed(float Speed, float Threshold) const;
/**
* Gets a movement state setting by index.
* 通过索引获取运动状态设置。
* @param Index The index of the state. 状态索引。
* @param OutSetting The output movement state setting. 输出的运动状态设置。
* @return True if found. 如果找到返回true。
*/
bool GetStateByIndex(const int32& Index, FGMS_MovementStateSetting& OutSetting) const;
/**
* Gets a movement state setting by speed level.
* 通过速度级别获取运动状态设置。
* @param Level The speed level. 速度级别。
* @param OutSetting The output movement state setting. 输出的运动状态设置。
* @return True if found. 如果找到返回true。
*/
bool GetStateBySpeedLevel(const int32& Level, FGMS_MovementStateSetting& OutSetting) const;
/**
* Gets a movement state setting by tag.
* 通过标签获取运动状态设置。
* @param Tag The gameplay tag. 游戏标签。
* @param OutSetting The output movement state setting. 输出的运动状态设置。
* @return True if found. 如果找到返回true。
*/
bool GetStateByTag(const FGameplayTag& Tag, FGMS_MovementStateSetting& OutSetting) const;
const FGMS_MovementStateSetting* GetMovementStateSetting(const FGameplayTag& Tag) const;
const FGMS_MovementStateSetting* GetMovementStateSetting(const FGameplayTag& Tag, bool bHasFallback) const;
/**
* Speed threshold for determining movement.
* 确定移动的速度阈值。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Control", Meta = (ClampMin = 0, ForceUnits = "cm/s"))
float MovingSpeedThreshold{50.0f};
/**
* Array of movement states, sorted by speed.
* 按速度排序的运动状态数组。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Control", meta=(TitleProperty="EditorFriendlyName"))
TArray<FGMS_MovementStateSetting> MovementStates;
/**
* Setting for velocity based rotation mode.
* 基于速率的旋转模式设置。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="RotationControl", meta=(ExcludeBaseStruct))
TInstancedStruct<FGMS_VelocityDirectionSetting_Base> VelocityDirectionSetting;
/**
* Setting for view based rotation mode.
* 基于视角的旋转模式设置。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="RotationControl")
TInstancedStruct<FGMS_ViewDirectionSetting_Base> ViewDirectionSetting;
/**
* Rotation mode when in air.
* 空中时的旋转模式。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="RotationControl")
EGMS_InAirRotationMode InAirRotationMode{EGMS_InAirRotationMode::KeepRelativeRotation};
/**
* Interpolation speed for in-air rotation.
* 空中旋转的插值速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="RotationControl", meta=(ClampMin=0))
float InAirRotationInterpolationSpeed = 5.0f;
/**
* Maps speed levels to movement state indices.
* 将速度级别映射到运动状态索引。
*/
UPROPERTY(EditAnywhere, Category="Advanced", meta=(EditCondition=false, ForceInlineRow))
TMap<int32, int32> SpeedLevelToArrayIndex;
#if WITH_EDITORONLY_DATA
float MigrateRotationInterpolationSpeed(float Old);
/**
* Called before saving the asset in the editor.
* 在编辑器中保存资产前调用。
* @param SaveContext The save context. 保存上下文。
*/
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
/**
* Validates data in the editor.
* 在编辑器中验证数据。
* @param Context The validation context. 验证上下文。
* @return The validation result. 验证结果。
*/
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
#endif
};
#pragma endregion
#pragma region AnimSettings
/**
* Base class for custom movement set user settings.
* 自定义运动集用户设置的基类。
* @note Subclass to add custom settings without modifying movement set settings. 子类化以添加自定义设置,而无需修改运动集设置。
*/
UCLASS(BlueprintType, Blueprintable, Abstract, Const, EditInlineNew, DefaultToInstanced)
class GENERICMOVEMENTSYSTEM_API UGMS_MovementSetUserSetting : public UObject
{
GENERATED_BODY()
};
#pragma endregion

View File

@@ -0,0 +1,871 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GMS_SettingEnumLibrary.h"
#include "Animation/AnimData/BoneMaskFilter.h"
#include "UObject/Object.h"
#include "Curves/CurveFloat.h"
#include "Engine/EngineTypes.h"
#include "BoneControllers/BoneControllerTypes.h"
#include "Utility/GMS_Tags.h"
#include "GMS_SettingStructLibrary.generated.h"
class UGMS_MovementSetUserSetting;
class UGMS_AnimLayerSetting_SkeletalControls;
class UGMS_MovementControlSetting_Default;
class UGMS_AnimLayerSetting_Additive;
class UGMS_CharacterMovementSetting;
class UGMS_CharacterRotationSetting;
class UGMS_AnimLayerSetting_View;
class UGMS_AnimLayerSetting_Overlay;
class UGMS_AnimLayerSetting_StateOverlays;
class UGMS_AnimLayerSetting_States;
/**
* Stores bone references for orientation warping.
* 存储用于朝向扭曲的骨骼引用。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_OrientationWarpingBoneReference
{
GENERATED_USTRUCT_BODY()
/**
* Spine bones used to counter-rotate the body to keep facing forward.
* 用于反向旋转身体以保持向前朝向的脊椎骨骼。
*/
UPROPERTY(EditAnywhere, Category="GMS")
TArray<FBoneReference> SpineBones;
/**
* IK foot root bone definition.
* IK脚根骨骼定义。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FBoneReference IKFootRootBone;
/**
* IK foot bone definitions.
* IK脚骨骼定义。
*/
UPROPERTY(EditAnywhere, Category="GMS")
TArray<FBoneReference> IKFootBones;
};
/**
* Settings for the orientation warping node in the animation graph.
* 动画图中朝向扭曲节点的设置。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_OrientationWarpingSettings
{
GENERATED_BODY()
/**
* Evaluation mode for orientation warping (Graph or Manual).
* 朝向扭曲的评估模式Graph或Manual
* @note Use Manual mode for animations without root motion. 对于无根运动的动画使用Manual模式。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
EWarpingEvaluationMode Mode = EWarpingEvaluationMode::Graph;
/**
* Bone references for orientation warping.
* 朝向扭曲的骨骼引用。
*/
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="GMS")
FGMS_OrientationWarpingBoneReference BoneReference;
/**
* Specifies how much rotation is applied to the body versus IK feet.
* 指定身体与IK脚的旋转分配比例。
*/
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="GMS", meta=(ClampMin="0.0", ClampMax="1.0"))
float DistributedBoneOrientationAlpha = 0.75f;
/**
* Interpolation speed for reaching the final warped rotation angle (alpha per second).
* 达到最终扭曲旋转角度的插值速度每秒alpha
* @note 0 means instantaneous rotation; higher values introduce smoothing. 0表示瞬时旋转较高值引入平滑。
*/
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="GMS", meta=(ClampMin="0.0"))
float RotationInterpSpeed = 10.f;
};
/**
* Bone references for stride warping.
* 步幅适配的骨骼引用。
*/
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_StrideWarpingBoneReference
{
GENERATED_BODY()
/**
* Pelvis bone reference.
* 骨盆骨骼引用。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FBoneReference PelvisBone;
/**
* IK foot root bone reference.
* IK脚根骨骼引用。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FBoneReference IKFootRootBone;
};
/**
* Foot bone definitions for stride warping.
* 步幅适配的脚骨骼定义。
*/
USTRUCT()
struct GENERICMOVEMENTSYSTEM_API FGMS_StrideWarpingFootDefinition
{
GENERATED_BODY()
/**
* IK foot bone.
* IK脚骨骼。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FBoneReference IKFootBone;
/**
* FK foot bone.
* FK脚骨骼。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FBoneReference FKFootBone;
/**
* Thigh bone.
* 大腿骨骼。
*/
UPROPERTY(EditAnywhere, Category="GMS")
FBoneReference ThighBone;
};
/**
* Settings for the stride warping node in the animation graph.
* 动画图中步幅适配节点的设置。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_StrideWarpingSettings
{
GENERATED_BODY()
/**
* Enables or disables stride warping.
* 启用或禁用步幅适配。
* @note Disable for non-humanoid characters. 对于非人形角色禁用。
*/
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="GMS")
bool bEnabled{true};
/**
* Start time for blending in stride warping.
* 步幅适配混入的开始时间。
* @note For animations with turns, set to when the character moves in a straight line. 对于带转身的动画,设置为角色直线移动的时刻。
*/
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="GMS", meta=(ClampMin=0))
float BlendInStartOffset{0.15f};
/**
* Duration for fully blending in stride warping.
* 步幅适配完全混入的持续时间。
*/
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="GMS")
float BlendInDurationScaled{0.2f};
};
/**
* Settings for the steering node in the animation graph.
* 动画图中Steering节点的设置。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_SteeringSettings
{
GENERATED_BODY()
/**
* Default constructor.
* 默认构造函数。
*/
FGMS_SteeringSettings()
{
bEnabled = true;
ProceduralTargetTime = 0.2f;
AnimatedTargetTime = 0.5f;
}
/**
* Constructor with parameters.
* 带参数的构造函数。
* @param bInEnabled Whether steering is enabled. 是否启用Steering。
* @param InProceduralTargetTime Procedural target time. 程序化目标时间。
* @param InAnimatedTargetTime Animated target time. 动画目标时间。
*/
FGMS_SteeringSettings(bool bInEnabled, float InProceduralTargetTime, float InAnimatedTargetTime)
{
bEnabled = bInEnabled;
ProceduralTargetTime = InProceduralTargetTime;
AnimatedTargetTime = InAnimatedTargetTime;
};
/**
* Enables or disables steering.
* 启用或禁用Steering。
*/
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="GMS")
bool bEnabled{true};
/**
* Time to reach target orientation for animations without root motion rotation.
* 无根运动旋转动画达到目标朝向的时间。
* @note Large values disable procedural turns. 大值禁用程序化转身。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float ProceduralTargetTime = 0.2f;
/**
* Time to reach target orientation for animations with root motion rotation.
* 有根运动旋转动画达到目标朝向的时间。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float AnimatedTargetTime = 0.5f;
};
/**
* Wrapper for branch filters used in blueprint.
* 用于蓝图的分支过滤器包装器。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_InputBlendPose
{
GENERATED_USTRUCT_BODY()
/**
* Array of bone branch filters.
* 骨骼分支过滤器数组。
*/
UPROPERTY(EditAnywhere, Category=Filter)
TArray<FBranchFilter> BranchFilters;
};
/**
* General settings for the main animation instance.
* 主动画实例的通用设置。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_AnimDataSetting_General
{
GENERATED_BODY()
/**
* Allows the pawn's root bone to rotate independently of the actor's rotation.
* 允许Pawn的根骨骼独立于Actor旋转进行旋转。
* @note Global setting; animation layers should only change root rotation mode if enabled. 全局设置;动画层仅在启用时更改根旋转模式。
* @details Enables complex rotational animations like turn-in-place via root motion. 通过根运动支持复杂的旋转动画,如原地转身。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Common")
bool bEnableOffsetRootBoneRotation{true};
/**
* Controls the speed of blending out root bone rotation offset.
* 控制根骨骼旋转偏移混出的速度。
* @note Closer to 0 is faster; 0 means instant blend out. 越接近0越快0表示立即混出。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Common", Meta = (ClampMin = 0, ClampMax = 1, EditCondition="bEnableOffsetRootBoneRotation"))
float RootBoneRotationHalfLife{0.2};
/**
* Allows the pawn's root bone to translate independently of the actor's location.
* 允许Pawn的根骨骼独立于Actor位置进行平移。
* @note Global setting; animation layers should only change root translation mode if enabled. 全局设置;动画层仅在启用时更改根平移模式。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Common")
bool bEnableOffsetRootBoneTranslation{true};
/**
* Controls the speed of blending out root bone translation offset.
* 控制根骨骼位移偏移混出的速度。
* @note Closer to 0 is faster; 0 means instant blend out. 越接近0越快0表示立即混出。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Common", Meta = (ClampMin = 0, ClampMax = 1, EditCondition="bEnableOffsetRootBoneTranslation"))
float RootBoneTranslationHalfLife{0.2};
/**
* Controls the speed of lean interpolation in grounded or in-air states.
* 控制地面或空中状态下倾斜插值的速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Common", Meta = (ClampMin = 0))
float LeanInterpolationSpeed{4.0f};
/**
* Speed threshold for determining movement in animation states.
* 动画状态中确定移动的速度阈值。
*/
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Common", Meta = (ClampMin = 0, ForceUnits = "cm/s"))
// float MovingSmoothSpeedThreshold{150.0f};
/**
* Curve mapping vertical velocity to lean amount in air.
* 空中垂直速度到倾斜量的曲线映射。
* @note If empty, lean state is not refreshed in air. 如果为空,空中不刷新倾斜状态。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="InAir")
TObjectPtr<UCurveFloat> InAirLeanAmountCurve{nullptr};
/**
* Collision channel for ground prediction sweep.
* 地面预测扫描的碰撞通道。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="InAir")
TEnumAsByte<ECollisionChannel> GroundPredictionSweepChannel{ECC_Visibility};
/**
* Response channels for ground prediction.
* 地面预测的响应通道。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="InAir")
TArray<TEnumAsByte<ECollisionChannel>> GroundPredictionResponseChannels;
/**
* Collision response container for ground prediction sweep.
* 地面预测扫描的碰撞响应容器。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="InAir")
FCollisionResponseContainer GroundPredictionSweepResponses{ECR_Ignore};
#if WITH_EDITOR
/**
* Handles property changes in the editor.
* 处理编辑器中的属性更改。
* @param PropertyChangedEvent The property change event. 属性更改事件。
*/
void PostEditChangeProperty(const FPropertyChangedEvent& PropertyChangedEvent);
#endif
};
#pragma region Rotation Mode Settings
USTRUCT(BlueprintType, meta=(Hidden))
struct GENERICMOVEMENTSYSTEM_API FGMS_RotationModeSetting
{
GENERATED_BODY()
/**
* Determines if turn-in-place animation follows actor rotation or drives it.
* 确定原地转身动画是跟随Actor旋转还是驱动Actor旋转。
* @note If enabled, animation catches up with actor rotation; if disabled, it drives rotation. 如果启用动画跟随Actor旋转如果禁用动画驱动旋转。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bEnableRotationWhenNotMoving{true};
};
/**
* Setting for view direction based rotation mode.
* 基于视角方向的旋转模式设置
*/
USTRUCT(BlueprintType, meta=(Hidden))
struct GENERICMOVEMENTSYSTEM_API FGMS_ViewDirectionSetting_Base : public FGMS_RotationModeSetting
{
GENERATED_BODY()
/**
* Primary rotation interpolation speed for character's rotation.
* 角色的主要旋转插值速度。
* @details Smaller value means faster InterpolationSpeed. 值越小,插值速度越大。
* @note <=0 disables smoothing. <=0禁用平滑。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(ClampMin=0))
float RotationInterpolationSpeed = 0.1f;
/**
* Secondary(Extras) rotation interpolation speed for character's rotation.
* 角色的次要(额外)旋转插值速度。
* @note <=0 disables smoothing <=0禁用平滑。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(ClampMin=0))
float TargetYawAngleRotationSpeed = 800.0f;
};
/**
* Setting for deprecated view direction based rotation mode.
* 基于弃用的视角方向的旋转模式设置
*/
USTRUCT(BlueprintType, meta=(Hidden))
struct FGMS_ViewDirectionSetting : public FGMS_ViewDirectionSetting_Base
{
GENERATED_BODY()
/**
* View direction mode (Default or Aiming).
* 视图方向模式(默认或瞄准)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
EGMS_ViewDirectionMode_DEPRECATED DirectionMode{EGMS_ViewDirectionMode_DEPRECATED::Default};
/**
* Determines if turn-in-place animation follows actor rotation or drives it.
* 确定原地转身动画是跟随Actor旋转还是驱动Actor旋转。
* @note If enabled, animation catches up with actor rotation; if disabled, it drives rotation. 如果启用动画跟随Actor旋转如果禁用动画驱动旋转。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
bool bRotateToViewDirectionWhileNotMoving{true};
/**
* Minimum angle limit between character and camera orientation in aiming mode.
* 瞄准模式下角色与相机朝向的最小夹角限制。
* @note Ensures character rotation keeps up with fast camera movement. 确保角色旋转跟上快速相机移动。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(EditCondition="DirectionMode == EGMS_ViewDirectionMode_DEPRECATED::Aiming", EditConditionHides))
float MinAimingYawAngleLimit{90.0f};
};
/**
* Setting for default view direction based rotation mode.
* 基于默认的视角方向的旋转模式设置
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_ViewDirectionSetting_Default : public FGMS_ViewDirectionSetting_Base
{
GENERATED_BODY()
/**
* Primary rotation interpolation speed curve for character's rotation.
* 角色的主要旋转插值速度曲线。
* @details This curve maps the speed level of each movement state to InterpolationSpeed, allowing different rotation rate on different movement state. 此曲线将每个运动状态的速度等级映射为插值速度,允许不同运动状态下有不同的旋转速度。
* @note Only used when character is moving and have higher priority than "RotationInterpolationSpeed". 仅在角色移动时使用比“RotationInterpolationSpeed”优先级更高。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(ClampMin=0))
TObjectPtr<UCurveFloat> RotationInterpolationSpeedCurve{nullptr};
};
/**
* Setting for Aiming view direction based rotation mode.
* 基于瞄准视角方向的旋转模式设置
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_ViewDirectionSetting_Aiming : public FGMS_ViewDirectionSetting_Base
{
GENERATED_BODY()
/**
* Minimum angle limit between character and camera orientation in aiming mode.
* 瞄准模式下角色与相机朝向的最小夹角限制。
* @note Ensures character rotation keeps up with fast camera movement. 确保角色旋转跟上快速相机移动。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float MinAimingYawAngleLimit{90.0f};
};
/**
* Setting for velocity direction based rotation mode.
* 基于速率方向的旋转模式设置
* @note Velocity can come from input ,acceleration, or last movement velocity;
*/
USTRUCT(BlueprintType, meta=(Hidden))
struct GENERICMOVEMENTSYSTEM_API FGMS_VelocityDirectionSetting_Base : public FGMS_RotationModeSetting
{
GENERATED_BODY()
// If checked, the character will rotate relative to the object it is standing on in the velocity
// direction rotation mode, otherwise the character will ignore that object and keep its world rotation.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Settings")
bool bInheritBaseRotation{false};
};
/**
* Default setting for velocity direction based rotation mode.
* 基于默认速率方向的旋转模式设置
*/
USTRUCT(BlueprintType, meta=(Hidden))
struct FGMS_VelocityDirectionSetting : public FGMS_VelocityDirectionSetting_Base
{
GENERATED_BODY()
/**
* Velocity direction mode (e.g., orient to velocity or input).
* 速度方向模式(例如,朝向速度或输入)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
EGMS_VelocityDirectionMode_DEPRECATED DirectionMode{EGMS_VelocityDirectionMode_DEPRECATED::OrientToLastVelocityDirection};
/**
* Rotation rate for turning circle mode (degrees per second).
* 转向圆模式的旋转速率(度每秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(EditCondition="DirectionMode == EGMS_VelocityDirectionMode_DEPRECATED::TurningCircle", EditConditionHides))
float TurningRate{30};
};
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_VelocityDirectionSetting_Default : public FGMS_VelocityDirectionSetting_Base
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMS")
bool bOrientateToMoveInputIntent = false;
/**
* If true, the actor will continue orienting towards the last intended orientation (from input) even after movement intent input has ceased.
* This makes the character finish orienting after a quick stick flick from the player. If false, character will not turn without input.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GMS")
bool bMaintainLastInputOrientation = false;
/**
* Primary rotation interpolation speed for character's rotation.
* 角色的主要旋转插值速度。
* @details Smaller value means faster InterpolationSpeed. 值越小,插值速度越大。
* @note <=0 disables smoothing. <=0禁用平滑。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(ClampMin=0))
float RotationInterpolationSpeed = 0.1f;
/**
* Primary rotation interpolation speed curve for character's rotation.
* 角色的主要旋转插值速度曲线。
* @details This curve maps the speed level of each movement state to InterpolationSpeed, allowing different rotation rate on different movement state. 此曲线将每个运动状态的速度等级映射为插值速度,允许不同运动状态下有不同的旋转速度。
* @note Only used when character is moving and have higher priority than "RotationInterpolationSpeed". 仅在角色移动时使用比“RotationInterpolationSpeed”优先级更高。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(ClampMin=0))
TObjectPtr<UCurveFloat> RotationInterpolationSpeedCurve{nullptr};
/**
* Secondary(Extras) moothing for character's rotation speed.
* 角色旋转速度的次平滑。
* @note <=0 disables smoothing <=0禁用平滑。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(ClampMin=0))
float TargetYawAngleRotationSpeed = 800.0f;
};
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_VelocityDirectionSetting_RateBased : public FGMS_VelocityDirectionSetting_Base
{
GENERATED_BODY()
/**
* Rotation rate for turning(degrees per second).
* 转向圆模式的旋转速率(度每秒)。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float TurnRate{30};
/**
* Rotation rate for turning at different speed.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(ClampMin=0))
TObjectPtr<UCurveFloat> TurnRateSpeedCurve{nullptr};
};
#pragma endregion
/**
* Settings for in-air movement.
* 空中移动的设置。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_InAirSetting
{
GENERATED_BODY()
/**
* Curve mapping vertical velocity to lean amount.
* 垂直速度到倾斜量的曲线映射。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TObjectPtr<UCurveFloat> LeanAmountCurve{nullptr};
/**
* Collision channel for ground prediction sweep.
* 地面预测扫描的碰撞通道。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TEnumAsByte<ECollisionChannel> GroundPredictionSweepChannel{ECC_Visibility};
/**
* Response channels for ground prediction.
* 地面预测的响应通道。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TArray<TEnumAsByte<ECollisionChannel>> GroundPredictionResponseChannels;
/**
* Collision response container for ground prediction sweep.
* 地面预测扫描的碰撞响应容器。
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="GMS")
FCollisionResponseContainer GroundPredictionSweepResponses{ECR_Ignore};
};
#pragma region Movement State Settings
/**
* Settings for a single movement state (e.g., walk, jog, sprint).
* 单一运动状态的设置(例如,走、慢跑、冲刺)。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_MovementStateSetting
{
GENERATED_BODY()
/**
* Gameplay tag identifying the movement state.
* 标识运动状态的游戏标签。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", meta=(Categories="GMS.MovementState"))
FGameplayTag Tag;
/**
* Speed level of the movement state; <0 indicates reverse movement.
* 运动状态的速度级别;<0表示反向移动。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
int32 SpeedLevel = INDEX_NONE;
/**
* Speed of the movement state.
* 运动状态的速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS", Meta = (ClampMin = 0, ForceUnits = "cm/s"))
float Speed{375.0f};
/**
* Acceleration of the movement state.
* 运动状态的加速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float Acceleration = 800.f;
/**
* Deceleration of the movement state.
* 运动状态的减速度。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
float BrakingDeceleration = 1024.f;
/**
* Allowed rotation modes for this movement state.
* 此运动状态允许的旋转模式。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GMS")
TArray<FGameplayTag> AllowedRotationModes{GMS_RotationModeTags::VelocityDirection, GMS_RotationModeTags::ViewDirection};
/**
* Equality operator for comparing with another movement state setting.
* 与另一个运动状态设置比较的相等运算符。
* @param Lhs Left-hand side setting. 左侧设置。
* @param RHS Right-hand side setting. 右侧设置。
* @return True if tags match. 如果标签匹配返回true。
*/
friend bool operator==(const FGMS_MovementStateSetting& Lhs, const FGMS_MovementStateSetting& RHS)
{
return Lhs.Tag == RHS.Tag;
}
/**
* Inequality operator for comparing with another movement state setting.
* 与另一个运动状态设置比较的不等运算符。
* @param Lhs Left-hand side setting. 左侧设置。
* @param RHS Right-hand side setting. 右侧设置。
* @return True if tags do not match. 如果标签不匹配返回true。
*/
friend bool operator!=(const FGMS_MovementStateSetting& Lhs, const FGMS_MovementStateSetting& RHS)
{
return !(Lhs == RHS);
}
/**
* Equality operator for comparing with a gameplay tag.
* 与游戏标签比较的相等运算符。
* @param Other The gameplay tag to compare. 要比较的游戏标签。
* @return True if tags match. 如果标签匹配返回true。
*/
bool operator==(const FGameplayTag& Other) const
{
return Tag == Other;
}
/**
* Inequality operator for comparing with a gameplay tag.
* 与游戏标签比较的不等运算符。
* @param Other The gameplay tag to compare. 要比较的游戏标签。
* @return True if tags do not match. 如果标签不匹配返回true。
*/
bool operator!=(const FGameplayTag& Other) const
{
return Tag != Other;
}
#if WITH_EDITORONLY_DATA
/**
* Velocity direction settings for this movement state.
* 此运动状态的速度方向设置。
*/
UE_DEPRECATED(1.5, "Settings for rotation mode was decoupd from movementstate, see Movement Control Setting.")
UPROPERTY(VisibleAnywhere, Category="Deprecated", meta=(EditCondition=false, EditConditionHides))
FGMS_VelocityDirectionSetting VelocityDirectionSetting;
/**
* View direction settings for this movement state.
* 此运动状态的视图方向设置。
*/
UE_DEPRECATED(1.5, "Settings for rotation mode was decoupd from movementstate, see Movement Control Setting.")
UPROPERTY(VisibleAnywhere, Category="Deprecated", meta=(EditCondition=false, EditConditionHides))
FGMS_ViewDirectionSetting ViewDirectionSetting;
/**
* Primary smoothing for character's rotation speed.
* 角色旋转的主平滑速度。
* @note <=0 disables smoothing. <=0禁用平滑。
*/
UE_DEPRECATED(1.5, "Settings for rotation mode was decoupd from movementstate, see Movement Control Setting.")
UPROPERTY(VisibleAnywhere, Category="Deprecated", meta=(ClampMin=0, EditCondition=false, EditConditionHides))
float RotationInterpolationSpeed = 12.0f;
/**
* Speed for smoothing SmoothTargetYawAngle to TargetYawAngle.
* 将SmoothTargetYawAngle平滑到TargetYawAngle的速度。
* @note <=0 disables smoothing (instant transition). <=0禁用平滑瞬时过渡
*/
UE_DEPRECATED(1.5, "Settings for rotation mode was decoupd from movementstate, see Movement Control Setting.")
UPROPERTY(VisibleAnywhere, Category="Deprecated", meta=(ClampMin=0, EditCondition=false, EditConditionHides))
float TargetYawAngleRotationSpeed = 800.0f;
/**
* Editor-friendly name for the movement state.
* 运动状态的编辑器友好名称。
*/
UPROPERTY(EditAnywhere, Category="Settings", meta=(EditCondition=false, EditConditionHides))
FString EditorFriendlyName;
#endif
};
#pragma endregion
/**
* Defines control and animation settings for a movement set.
* 定义运动集的控制和动画设置。
*/
USTRUCT(BlueprintType)
struct GENERICMOVEMENTSYSTEM_API FGMS_MovementSetSetting
{
GENERATED_BODY()
/**
* Default movement control setting.
* 默认运动控制设置。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Control")
TObjectPtr<UGMS_MovementControlSetting_Default> ControlSetting;
/**
* Enables per-overlay mode control settings.
* 启用按叠层模式的控制设置。
* @note If no control setting is found for an overlay mode, the default is used. 如果未找到叠层模式的控制设置,则使用默认设置。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Control")
bool bControlSettingPerOverlayMode{false};
/**
* Maps overlay modes to specific control settings.
* 将叠层模式映射到特定控制设置。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Control", meta=(Categories="GMS.OverlayMode", EditCondition="bControlSettingPerOverlayMode"))
TMap<FGameplayTag, TObjectPtr<UGMS_MovementControlSetting_Default>> ControlSettings;
/**
* General animation settings shared across the movement set.
* 运动集共享的通用动画设置。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Animation")
FGMS_AnimDataSetting_General AnimDataSetting_General;
/**
* Determines if instanced states settings are used.
* 确定是否使用实例化的状态设置。
* @note Uncheck if multiple movement sets share the same states layer setting. 如果多个运动集共享相同的状态层设置,则取消勾选。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Animation")
bool bUseInstancedStatesSetting{true};
/**
* Settings for the states animation layer (instanced).
* 状态动画层的设置(实例化)。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Instanced, Category="Animation", meta = (EditCondition="bUseInstancedStatesSetting", EditConditionHides, DisplayName="Anim Layer Setting (States)"))
TObjectPtr<UGMS_AnimLayerSetting_States> AnimLayerSetting_States;
/**
* Settings for the states animation layer (non-instanced).
* 状态动画层的设置(非实例化)。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Animation",
meta = (EditCondition="!bUseInstancedStatesSetting", EditConditionHides, DisplayName="Anim Layer Setting (States)"))
TObjectPtr<UGMS_AnimLayerSetting_States> DA_AnimLayerSetting_States;
/**
* Determines if instanced overlay settings are used.
* 确定是否使用实例化的叠层设置。
* @note Uncheck if multiple movement sets share the same overlay layer setting. 如果多个运动集共享相同的叠层设置,则取消勾选。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Animation")
bool bUseInstancedOverlaySetting{true};
/**
* Settings for the overlay animation layer (non-instanced).
* 叠层动画层的设置(非实例化)。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Animation",
meta = (EditCondition="!bUseInstancedOverlaySetting", EditConditionHides, DisplayName="Anim Layer Setting (Overlay)"))
TObjectPtr<UGMS_AnimLayerSetting_Overlay> DA_AnimLayerSetting_Overlay;
/**
* Settings for the overlay animation layer (instanced).
* 叠层动画层的设置(实例化)。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Instanced, Category="Animation",
meta = (EditCondition="bUseInstancedOverlaySetting", EditConditionHides, DisplayName="Anim Layer Setting (Overlay)"))
TObjectPtr<UGMS_AnimLayerSetting_Overlay> AnimLayerSetting_Overlay;
/**
* Settings for the view animation layer.
* 视图动画层的设置。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Instanced, Category="Animation", meta = (DisplayName="Anim Layer Setting (View)"))
TObjectPtr<UGMS_AnimLayerSetting_View> AnimLayerSetting_View;
/**
* Settings for the additive animation layer.
* 附加动画层的设置。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Instanced, Category="Animation", meta = (DisplayName="Anim Layer Setting (Additive)"))
TObjectPtr<UGMS_AnimLayerSetting_Additive> AnimLayerSetting_Additive;
/**
* Settings for the skeletal controls animation layer.
* 骨骼控制动画层的设置。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Instanced, Category="Animation", meta = (DisplayName="Anim Layer Setting (SkeletalControls)"))
TObjectPtr<UGMS_AnimLayerSetting_SkeletalControls> AnimLayerSetting_SkeletalControls;
/**
* Custom user settings for the movement set.
* 运动集的自定义用户设置。
* @note Subclass UGMS_MovementSetUserSetting and consume in animation layer blueprints. 子类化UGMS_MovementSetUserSetting并在动画层蓝图中使用。
* @example Access via GetMovementSystemComponent()->GetMovementSetSetting()->GetMovementSetUserSetting(SettingClass). 通过GetMovementSystemComponent()->GetMovementSetSetting()->GetMovementSetUserSetting(SettingClass)访问。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Instanced, Category="Extension", meta=(AllowAbstract=false))
TArray<TObjectPtr<UGMS_MovementSetUserSetting>> UserSettings;
};

View File

@@ -0,0 +1,61 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GMS_Constants.generated.h"
UCLASS(Meta = (BlueprintThreadSafe))
class GENERICMOVEMENTSYSTEM_API UGMS_Constants : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Constants|Animation Slots", Meta = (ReturnDisplayName = "Slot Name"))
static const FName& TurnInPlaceSlotName();
// Other Animation Curves
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Constants|Animation Curves", Meta = (ReturnDisplayName = "Curve Name"))
static const FName& RotationYawSpeedCurveName();
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Constants|Animation Curves", Meta = (ReturnDisplayName = "Curve Name"))
static const FName& RotationYawOffsetCurveName();
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Constants|Animation Curves", Meta = (ReturnDisplayName = "Curve Name"))
static const FName& AllowTurnInPlaceCurveName();
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Constants|Animation Curves", Meta = (ReturnDisplayName = "Curve Name"))
static const FName& AllowAimingCurveName();
};
inline const FName& UGMS_Constants::TurnInPlaceSlotName()
{
static const FName Name{TEXTVIEW("TurnInPlace")};
return Name;
}
inline const FName& UGMS_Constants::RotationYawSpeedCurveName()
{
static const FName Name{TEXTVIEW("RotationYawSpeed")};
return Name;
}
inline const FName& UGMS_Constants::RotationYawOffsetCurveName()
{
static const FName Name{TEXTVIEW("RotationYawOffset")};
return Name;
}
inline const FName& UGMS_Constants::AllowTurnInPlaceCurveName()
{
static const FName Name{TEXTVIEW("AllowTurnInPlace")};
return Name;
}
inline const FName& UGMS_Constants::AllowAimingCurveName()
{
static const FName Name{TEXTVIEW("AllowAiming")};
return Name;
}

View File

@@ -0,0 +1,38 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "Logging/LogMacros.h"
namespace GMSLog
{
GENERICMOVEMENTSYSTEM_API extern const FName MessageLogName;
}
DECLARE_STATS_GROUP(TEXT("GMS"), STATGROUP_GMS, STATCAT_Advanced)
FString GetGMSLogContextString(const UObject* ContextObject = nullptr);
GENERICMOVEMENTSYSTEM_API DECLARE_LOG_CATEGORY_EXTERN(LogGMS, Log, All)
GENERICMOVEMENTSYSTEM_API DECLARE_LOG_CATEGORY_EXTERN(LogGMS_Animation, Log, All)
#define GMS_LOG(Verbosity, Format, ...) \
{ \
UE_LOG(LogGMS, Verbosity, TEXT("%S: %s"),__FUNCTION__, *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
}
#define GMS_ANIMATION_LOG(Verbosity, Format, ...) \
{ \
UE_LOG(LogGMS_Animation, Verbosity, TEXT("%S: %s"),__FUNCTION__, *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
}
#define GMS_CLOG(Verbosity, Format, ...) \
{ \
UE_LOG(LogGMS, Verbosity, TEXT("%S: ctx(%s) %s"),__FUNCTION__, *GetGMSLogContextString(this), *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
}
#define GMS_ANIMATION_CLOG(Verbosity, Format, ...) \
{ \
UE_LOG(LogGMS_Animation, Verbosity, TEXT("%S: ctx(%s) %s"),__FUNCTION__, *GetGMSLogContextString(this), *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
}

View File

@@ -0,0 +1,57 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GMS_Math.generated.h"
UCLASS(Meta = (BlueprintThreadSafe))
class GENERICMOVEMENTSYSTEM_API UGMS_Math : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
static constexpr auto Ln2{0.6931471805599453f}; // FMath::Loge(2.0f).
public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Math Utility", Meta = (ReturnDisplayName = "Value"))
static float Clamp01(float Value);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Math Utility", Meta = (ReturnDisplayName = "Value"))
static float LerpClamped(float From, float To, float Ratio);
//Output the frame-rate stable alpha used for smoothly lerp current to target.
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Math Utility", Meta = (ReturnDisplayName = "Alpha"))
static float DamperExactAlpha(float DeltaTime, float HalfLife);
// HalfLife is the time it takes for the distance to the target to be reduced by half.
template <typename ValueType>
static ValueType DamperExact(const ValueType& Current, const ValueType& Target, float DeltaTime, float HalfLife);
};
inline float UGMS_Math::Clamp01(const float Value)
{
return Value > 0.0f
? Value < 1.0f
? Value
: 1.0f
: 0.0f;
}
inline float UGMS_Math::LerpClamped(const float From, const float To, const float Ratio)
{
return From + (To - From) * Clamp01(Ratio);
}
inline float UGMS_Math::DamperExactAlpha(const float DeltaTime, const float HalfLife)
{
// https://theorangeduck.com/page/spring-roll-call#exactdamper
return 1.0f - FMath::InvExpApprox(Ln2 / (HalfLife + UE_SMALL_NUMBER) * DeltaTime);
}
template <typename ValueType>
ValueType UGMS_Math::DamperExact(const ValueType& Current, const ValueType& Target, const float DeltaTime, const float HalfLife)
{
return FMath::Lerp(Current, Target, DamperExactAlpha(DeltaTime, HalfLife));
}

View File

@@ -0,0 +1,233 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "GMS_Math.h"
#include "GMS_Rotation.generated.h"
UCLASS(Meta = (BlueprintThreadSafe))
class GENERICMOVEMENTSYSTEM_API UGMS_Rotation : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
static constexpr auto CounterClockwiseRotationAngleThreshold{5.0f};
public:
template <typename ValueType> requires std::is_floating_point_v<ValueType>
static constexpr ValueType RemapAngleForCounterClockwiseRotation(ValueType Angle);
static VectorRegister4Double RemapRotationForCounterClockwiseRotation(const VectorRegister4Double& Rotation);
// Remaps the angle from the [175, 180] range to [-185, -180]. Used to
// make the character rotate counterclockwise during a 180 degree turn.
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (ReturnDisplayName = "Angle"))
static float RemapAngleForCounterClockwiseRotation(float Angle);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (ReturnDisplayName = "Angle"))
static float LerpAngle(float From, float To, float Ratio);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (AutoCreateRefTerm = "From, To", ReturnDisplayName = "Rotation"))
static FRotator LerpRotation(const FRotator& From, const FRotator& To, float Ratio);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (ReturnDisplayName = "Angle"))
static float InterpolateAngleConstant(float Current, float Target, float DeltaTime, float Speed);
/**
* Smoothly lerp current to target in fame-rate stable way.
* 以帧率稳定方式平滑地从Current过渡到Target。
* @param HalfLife How fast to reach target.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (ReturnDisplayName = "Angle"))
static float DamperExactAngle(float Current, float Target, float DeltaTime, float HalfLife);
// HalfLife is the time it takes for the distance to the target to be reduced by half.
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility",
Meta = (AutoCreateRefTerm = "Current, Target", ReturnDisplayName = "Rotation"))
static FRotator DamperExactRotation(const FRotator& Current, const FRotator& Target, float DeltaTime, float HalfLife);
// Same as FMath::QInterpTo(), but uses FQuat::FastLerp() instead of FQuat::Slerp().
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (ReturnDisplayName = "Quaternion"))
static FQuat InterpolateQuaternionFast(const FQuat& Current, const FQuat& Target, float DeltaTime, float Speed);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (AutoCreateRefTerm = "TwistAxis", ReturnDisplayName = "Twist"))
static FQuat GetTwist(const FQuat& Quaternion, const FVector& TwistAxis = FVector::UpVector);
};
template <typename ValueType> requires std::is_floating_point_v<ValueType>
constexpr ValueType UGMS_Rotation::RemapAngleForCounterClockwiseRotation(const ValueType Angle)
{
if (Angle > 180.0f - CounterClockwiseRotationAngleThreshold)
{
return Angle - 360.0f;
}
return Angle;
}
inline VectorRegister4Double UGMS_Rotation::RemapRotationForCounterClockwiseRotation(const VectorRegister4Double& Rotation)
{
static constexpr auto RemapThreshold{
MakeVectorRegisterDoubleConstant(180.0f - CounterClockwiseRotationAngleThreshold, 180.0f - CounterClockwiseRotationAngleThreshold,
180.0f - CounterClockwiseRotationAngleThreshold, 180.0f - CounterClockwiseRotationAngleThreshold)
};
static constexpr auto RemapAngles{MakeVectorRegisterDoubleConstant(360.0f, 360.0f, 360.0f, 0.0f)};
const auto ReverseRotationMask{VectorCompareGE(Rotation, RemapThreshold)};
const auto ReversedRotation{VectorSubtract(Rotation, RemapAngles)};
return VectorSelect(ReverseRotationMask, ReversedRotation, Rotation);
}
inline float UGMS_Rotation::RemapAngleForCounterClockwiseRotation(const float Angle)
{
return RemapAngleForCounterClockwiseRotation<float>(Angle);
}
inline float UGMS_Rotation::LerpAngle(const float From, const float To, const float Ratio)
{
auto Delta{FMath::UnwindDegrees(To - From)};
Delta = RemapAngleForCounterClockwiseRotation(Delta);
return FMath::UnwindDegrees(From + Delta * Ratio);
}
inline FRotator UGMS_Rotation::LerpRotation(const FRotator& From, const FRotator& To, const float Ratio)
{
#if PLATFORM_ENABLE_VECTORINTRINSICS
const auto FromRegister{VectorLoadFloat3_W0(&From)};
const auto ToRegister{VectorLoadFloat3_W0(&To)};
auto Delta{VectorSubtract(ToRegister, FromRegister)};
Delta = VectorNormalizeRotator(Delta);
if (!VectorAnyGreaterThan(VectorAbs(Delta), GlobalVectorConstants::DoubleKindaSmallNumber))
{
return To;
}
Delta = RemapRotationForCounterClockwiseRotation(Delta);
auto ResultRegister{VectorMultiplyAdd(Delta, VectorLoadFloat1(&Ratio), FromRegister)};
ResultRegister = VectorNormalizeRotator(ResultRegister);
FRotator Result;
VectorStoreFloat3(ResultRegister, &Result);
return Result;
#else
auto Result{To - From};
Result.Normalize();
Result.Pitch = RemapAngleForCounterClockwiseRotation(Result.Pitch);
Result.Yaw = RemapAngleForCounterClockwiseRotation(Result.Yaw);
Result.Roll = RemapAngleForCounterClockwiseRotation(Result.Roll);
Result *= Ratio;
Result += From;
Result.Normalize();
return Result;
#endif
}
inline float UGMS_Rotation::InterpolateAngleConstant(const float Current, const float Target, const float DeltaTime, const float Speed)
{
auto Delta{FMath::UnwindDegrees(Target - Current)};
const auto MaxDelta{Speed * DeltaTime};
if (Speed <= 0.0f || FMath::Abs(Delta) <= MaxDelta)
{
return Target;
}
Delta = RemapAngleForCounterClockwiseRotation(Delta);
return FMath::UnwindDegrees(Current + FMath::Sign(Delta) * MaxDelta);
}
inline float UGMS_Rotation::DamperExactAngle(const float Current, const float Target, const float DeltaTime, const float HalfLife)
{
auto Delta{FMath::UnwindDegrees(Target - Current)};
if (FMath::IsNearlyZero(Delta, UE_KINDA_SMALL_NUMBER))
{
return Target;
}
Delta = RemapAngleForCounterClockwiseRotation(Delta);
const auto Alpha{UGMS_Math::DamperExactAlpha(DeltaTime, HalfLife)};
return FMath::UnwindDegrees(Current + Delta * Alpha);
}
inline FRotator UGMS_Rotation::DamperExactRotation(const FRotator& Current, const FRotator& Target,
const float DeltaTime, const float HalfLife)
{
#if PLATFORM_ENABLE_VECTORINTRINSICS
const auto CurrentRegister{VectorLoadFloat3_W0(&Current)};
const auto TargetRegister{VectorLoadFloat3_W0(&Target)};
auto Delta{VectorSubtract(TargetRegister, CurrentRegister)};
Delta = VectorNormalizeRotator(Delta);
if (!VectorAnyGreaterThan(VectorAbs(Delta), GlobalVectorConstants::DoubleKindaSmallNumber))
{
return Target;
}
Delta = RemapRotationForCounterClockwiseRotation(Delta);
const double Alpha{UGMS_Math::DamperExactAlpha(DeltaTime, HalfLife)};
auto ResultRegister{VectorMultiplyAdd(Delta, VectorLoadDouble1(&Alpha), CurrentRegister)};
ResultRegister = VectorNormalizeRotator(ResultRegister);
FRotator Result;
VectorStoreFloat3(ResultRegister, &Result);
return Result;
#else
auto Result{Target - Current};
Result.Normalize();
if (FMath::IsNearlyZero(Result.Pitch, UE_KINDA_SMALL_NUMBER) &&
FMath::IsNearlyZero(Result.Yaw, UE_KINDA_SMALL_NUMBER) &&
FMath::IsNearlyZero(Result.Roll, UE_KINDA_SMALL_NUMBER))
{
return Target;
}
Result.Pitch = RemapAngleForCounterClockwiseRotation(Result.Pitch);
Result.Yaw = RemapAngleForCounterClockwiseRotation(Result.Yaw);
Result.Roll = RemapAngleForCounterClockwiseRotation(Result.Roll);
const auto Alpha{UGMS_Math::DamperExactAlpha(DeltaTime, HalfLife)};
Result *= Alpha;
Result += Current;
Result.Normalize();
return Result;
#endif
}
inline FQuat UGMS_Rotation::InterpolateQuaternionFast(const FQuat& Current, const FQuat& Target, const float DeltaTime, const float Speed)
{
if (Speed <= 0.0f || Current.Equals(Target))
{
return Target;
}
return FQuat::FastLerp(Current, Target, UGMS_Math::Clamp01(Speed * DeltaTime)).GetNormalized();
}
inline FQuat UGMS_Rotation::GetTwist(const FQuat& Quaternion, const FVector& TwistAxis)
{
// Based on TQuat<T>::ToSwingTwist().
const auto Projection{(TwistAxis | FVector{Quaternion.X, Quaternion.Y, Quaternion.Z}) * TwistAxis};
return FQuat{Projection.X, Projection.Y, Projection.Z, Quaternion.W}.GetNormalized();
}

View File

@@ -0,0 +1,49 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "NativeGameplayTags.h"
namespace GMS_MovementModeTags
{
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(None)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Grounded)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InAir)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Flying)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Swimming)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Zipline)
}
namespace GMS_RotationModeTags
{
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(VelocityDirection)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(ViewDirection)
}
namespace GMS_MovementStateTags
{
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Walk)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Jog)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Sprint)
}
namespace GMS_OverlayModeTags
{
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(None)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Default)
}
namespace GMS_SMTags
{
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Root)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InAir)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InAir_Jump)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InAir_Fall)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Grounded)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Grounded_Idle)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Grounded_Start)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Grounded_Cycle)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Grounded_Stop)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Grounded_Pivot)
GENERICMOVEMENTSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Grounded_Land)
}

View File

@@ -0,0 +1,76 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "GameplayTagContainer.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Locomotions/GMS_LocomotionStructLibrary.h"
#include "Settings/GMS_SettingStructLibrary.h"
#include "GMS_Utility.generated.h"
class UGMS_MovementSetUserSetting;
class UGMS_AnimLayer;
class UGMS_MainAnimInstance;
class UChooserTable;
class UPoseSearchDatabase;
class UAnimSequenceBase;
UCLASS()
class GENERICMOVEMENTSYSTEM_API UGMS_Utility : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
static constexpr auto DrawImpactPointSize{32.0f};
static constexpr auto DrawLineThickness{1.0f};
static constexpr auto DrawArrowSize{50.0f};
static constexpr auto DrawCircleSidesCount{16};
static constexpr FStringView BoolToString(bool bValue);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Utility", Meta = (AutoCreateRefTerm = "Name", ReturnDisplayName = "Display String"))
static FString NameToDisplayString(const FName& Name, bool bNameIsBool);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Utility",
Meta = (DefaultToSelf = "Character", AutoCreateRefTerm = "CurveName", ReturnDisplayName = "Curve Value"))
static float GetAnimationCurveValueFromCharacter(const ACharacter* Character, const FName& CurveName);
UFUNCTION(BlueprintCallable, Category="GMS|Utility", Meta = (AutoCreateRefTerm = "Tag", ReturnDisplayName = "Child Tags"))
static FGameplayTagContainer GetChildTags(const FGameplayTag& Tag);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Utility", Meta = (AutoCreateRefTerm = "Tag", ReturnDisplayName = "Tag Name"))
static FName GetSimpleTagName(const FGameplayTag& Tag);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Utility",
Meta = (DefaultToSelf = "Actor", AutoCreateRefTerm = "DisplayName", ReturnDisplayName = "Value"))
static bool ShouldDisplayDebugForActor(const AActor* Actor, const FName& DisplayName);
UFUNCTION(BlueprintCallable, Category="GMS|Animation", meta=(BlueprintThreadSafe))
static float CalculateAnimatedSpeed(const UAnimSequenceBase* AnimSequence);
/**
* @param Animations List of Animations to select.
* @param ReferenceValue The animation with distance closest to ReferenceValue will be selected.
* @return Selected AnimSequence.
*/
UFUNCTION(BlueprintCallable, Category="GMS|Animation", meta=(BlueprintThreadSafe))
static UAnimSequence* SelectAnimationWithFloat(const TArray<FGMS_AnimationWithDistance>& Animations, const float& ReferenceValue);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Animation", meta=(BlueprintThreadSafe))
static bool ValidatePoseSearchDatabasesChooser(const UChooserTable* ChooserTable, FText& OutMessage);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Animation", meta=(BlueprintThreadSafe))
static bool IsValidPoseSearchDatabasesChooser(const UChooserTable* ChooserTable);
UFUNCTION(BlueprintCallable, BlueprintPure=false, Category="GMS|Animation", meta=(BlueprintThreadSafe))
static TArray<UPoseSearchDatabase*> EvaluatePoseSearchDatabasesChooser(const UGMS_MainAnimInstance* MainAnimInstance, const UGMS_AnimLayer* AnimLayerInstance, UChooserTable* ChooserTable);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Utility", meta=(BlueprintThreadSafe, DeterminesOutputType=DesiredClass, DynamicOutputParam="ReturnValue"))
static const UGMS_MovementSetUserSetting* GetMovementSetUserSetting(const FGMS_MovementSetSetting& MovementSetSetting, TSubclassOf<UGMS_MovementSetUserSetting> DesiredClass);
};
constexpr FStringView UGMS_Utility::BoolToString(const bool bValue)
{
return bValue ? TEXTVIEW("True") : TEXTVIEW("False");
}

View File

@@ -0,0 +1,163 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GMS_Vector.generated.h"
UCLASS(Meta = (BlueprintThreadSafe))
class GENERICMOVEMENTSYSTEM_API UGMS_Vector : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", Meta = (AutoCreateRefTerm = "Vector", ReturnDisplayName = "Vector"))
static FVector ClampMagnitude01(const FVector& Vector);
static FVector3f ClampMagnitude01(const FVector3f& Vector);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", DisplayName = "Clamp Magnitude 01 2D",
Meta = (AutoCreateRefTerm = "Vector", ReturnDisplayName = "Vector"))
static FVector2D ClampMagnitude012D(const FVector2D& Vector);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", Meta = (ReturnDisplayName = "Direction"))
static FVector2D RadianToDirection(float Radian);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", Meta = (ReturnDisplayName = "Direction"))
static FVector RadianToDirectionXY(float Radian);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", Meta = (ReturnDisplayName = "Direction"))
static FVector2D AngleToDirection(float Angle);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", Meta = (ReturnDisplayName = "Direction"))
static FVector AngleToDirectionXY(float Angle);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", Meta = (AutoCreateRefTerm = "Direction", ReturnDisplayName = "Angle"))
static double DirectionToAngle(const FVector2D& Direction);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", Meta = (AutoCreateRefTerm = "Direction", ReturnDisplayName = "Angle"))
static double DirectionToAngleXY(const FVector& Direction);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", Meta = (AutoCreateRefTerm = "Vector", ReturnDisplayName = "Vector"))
static FVector PerpendicularClockwiseXY(const FVector& Vector);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", Meta = (AutoCreateRefTerm = "Vector", ReturnDisplayName = "Vector"))
static FVector PerpendicularCounterClockwiseXY(const FVector& Vector);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", DisplayName = "Angle Between (Skip Normalization)",
Meta = (AutoCreateRefTerm = "From, To", ReturnDisplayName = "Angle"))
static double AngleBetweenSkipNormalization(const FVector& From, const FVector& To);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", Meta = (AutoCreateRefTerm = "From, To", ReturnDisplayName = "Angle"))
static float AngleBetweenSignedXY(const FVector3f& From, const FVector3f& To);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Vector Utility", DisplayName = "Slerp (Skip Normalization)",
Meta = (AutoCreateRefTerm = "From, To", ReturnDisplayName = "Direction"))
static FVector SlerpSkipNormalization(const FVector& From, const FVector& To, float Ratio);
};
inline FVector UGMS_Vector::ClampMagnitude01(const FVector& Vector)
{
const auto MagnitudeSquared{Vector.SizeSquared()};
if (MagnitudeSquared <= 1.0f)
{
return Vector;
}
const auto Scale{FMath::InvSqrt(MagnitudeSquared)};
return {Vector.X * Scale, Vector.Y * Scale, Vector.Z * Scale};
}
inline FVector3f UGMS_Vector::ClampMagnitude01(const FVector3f& Vector)
{
const auto MagnitudeSquared{Vector.SizeSquared()};
if (MagnitudeSquared <= 1.0f)
{
return Vector;
}
const auto Scale{FMath::InvSqrt(MagnitudeSquared)};
return {Vector.X * Scale, Vector.Y * Scale, Vector.Z * Scale};
}
inline FVector2D UGMS_Vector::ClampMagnitude012D(const FVector2D& Vector)
{
const auto MagnitudeSquared{Vector.SizeSquared()};
if (MagnitudeSquared <= 1.0f)
{
return Vector;
}
const auto Scale{FMath::InvSqrt(MagnitudeSquared)};
return {Vector.X * Scale, Vector.Y * Scale};
}
inline FVector2D UGMS_Vector::RadianToDirection(const float Radian)
{
float Sin, Cos;
FMath::SinCos(&Sin, &Cos, Radian);
return {Cos, Sin};
}
inline FVector UGMS_Vector::RadianToDirectionXY(const float Radian)
{
float Sin, Cos;
FMath::SinCos(&Sin, &Cos, Radian);
return {Cos, Sin, 0.0f};
}
inline FVector2D UGMS_Vector::AngleToDirection(const float Angle)
{
return RadianToDirection(FMath::DegreesToRadians(Angle));
}
inline FVector UGMS_Vector::AngleToDirectionXY(const float Angle)
{
return RadianToDirectionXY(FMath::DegreesToRadians(Angle));
}
inline double UGMS_Vector::DirectionToAngle(const FVector2D& Direction)
{
return FMath::RadiansToDegrees(FMath::Atan2(Direction.Y, Direction.X));
}
inline double UGMS_Vector::DirectionToAngleXY(const FVector& Direction)
{
return FMath::RadiansToDegrees(FMath::Atan2(Direction.Y, Direction.X));
}
inline FVector UGMS_Vector::PerpendicularClockwiseXY(const FVector& Vector)
{
return {Vector.Y, -Vector.X, Vector.Z};
}
inline FVector UGMS_Vector::PerpendicularCounterClockwiseXY(const FVector& Vector)
{
return {-Vector.Y, Vector.X, Vector.Z};
}
inline double UGMS_Vector::AngleBetweenSkipNormalization(const FVector& From, const FVector& To)
{
return FMath::RadiansToDegrees(FMath::Acos(From | To));
}
inline float UGMS_Vector::AngleBetweenSignedXY(const FVector3f& From, const FVector3f& To)
{
FVector2f FromXY{From};
FromXY.Normalize();
FVector2f ToXY{To};
ToXY.Normalize();
// return FMath::RadiansToDegrees(FMath::Atan2(FromXY ^ ToXY, FromXY | ToXY));
return FMath::RadiansToDegrees(FMath::Acos(FromXY | ToXY)) * FMath::Sign(FromXY ^ ToXY);
}