第一次提交
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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")})
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user