754 lines
26 KiB
C++
754 lines
26 KiB
C++
// 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
|