Skip to content

Commit

Permalink
Add support for direction-dependent movement speed
Browse files Browse the repository at this point in the history
  • Loading branch information
watsonsong committed Jul 9, 2024
1 parent aca0383 commit 8d1817a
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 91 deletions.
3 changes: 3 additions & 0 deletions Config/DefaultALS.ini
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,6 @@
+FunctionRedirects=(OldName="/Script/ALS.AlsMath.ExponentialDecayAngle",NewName="/Script/ALS.AlsRotation.ExponentialDecayAngle")
+FunctionRedirects=(OldName="/Script/ALS.AlsMath.ExponentialDecayRotation",NewName="/Script/ALS.AlsRotation.ExponentialDecayRotation")
+FunctionRedirects=(OldName="/Script/ALS.AlsMath.GetTwist",NewName="/Script/ALS.AlsRotation.GetTwist")

+PropertyRedirects=(OldName="/Script/ALS.AlsMovementGaitSettings.WalkSpeed",NewName="/Script/ALS.AlsMovementGaitSettings.WalkForwardSpeed")
+PropertyRedirects=(OldName="/Script/ALS.AlsMovementGaitSettings.RunSpeed",NewName="/Script/ALS.AlsMovementGaitSettings.RunForwardSpeed")
Binary file modified Content/ALS/Data/Character/Movement/MS_Als_Normal.uasset
Binary file not shown.
Binary file modified Content/ALS/Data/Character/Movement/MS_Als_Responsive.uasset
Binary file not shown.
Binary file modified Content/ALS/Data/Character/Movement/MS_Als_Sluggish.uasset
Binary file not shown.
11 changes: 6 additions & 5 deletions Source/ALS/Private/AlsCharacter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1001,12 +1001,14 @@ FGameplayTag AAlsCharacter::CalculateActualGait(const FGameplayTag& MaxAllowedGa
// different from the desired gait or max allowed gait. For instance, if the max allowed gait becomes
// walking, the new gait will still be running until the character decelerates to the walking speed.

if (LocomotionState.Speed < AlsCharacterMovement->GetGaitSettings().WalkSpeed + 10.0f)
const auto& GaitSettings{AlsCharacterMovement->GetGaitSettings()};

if (LocomotionState.Speed < GaitSettings.GetMaxWalkSpeed() + 10.0f)
{
return AlsGaitTags::Walking;
}

if (LocomotionState.Speed < AlsCharacterMovement->GetGaitSettings().RunSpeed + 10.0f || MaxAllowedGait != AlsGaitTags::Sprinting)
if (LocomotionState.Speed < GaitSettings.GetMaxRunSpeed() + 10.0f || MaxAllowedGait != AlsGaitTags::Sprinting)
{
return AlsGaitTags::Running;
}
Expand Down Expand Up @@ -1490,8 +1492,7 @@ void AAlsCharacter::OnJumpedNetworked()

void AAlsCharacter::FaceRotation(const FRotator Rotation, const float DeltaTime)
{
// Left empty intentionally. We are ignoring rotation changes from external
// sources because ALS itself has full control over character rotation.
// Left empty intentionally. We ignore rotation changes from external sources because ALS itself has full control over actor rotation.
}

void AAlsCharacter::CharacterMovement_OnPhysicsRotation(const float DeltaTime)
Expand Down Expand Up @@ -1720,7 +1721,7 @@ float AAlsCharacter::CalculateGroundedMovingRotationInterpolationSpeed() const

const auto InterpolationSpeed{
ALS_ENSURE(IsValid(InterpolationSpeedCurve))
? InterpolationSpeedCurve->GetFloatValue(AlsCharacterMovement->CalculateGaitAmount())
? InterpolationSpeedCurve->GetFloatValue(AlsCharacterMovement->GetGaitAmount())
: DefaultInterpolationSpeed
};

Expand Down
158 changes: 99 additions & 59 deletions Source/ALS/Private/AlsCharacterMovementComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "Engine/World.h"
#include "GameFramework/Controller.h"
#include "Utility/AlsMacros.h"
#include "Utility/AlsRotation.h"
#include "Utility/AlsUtility.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(AlsCharacterMovementComponent)
Expand All @@ -29,7 +30,7 @@ bool FAlsCharacterNetworkMoveData::Serialize(UCharacterMovementComponent& Moveme

NetSerializeOptionalValue(Archive.IsSaving(), Archive, RotationMode, AlsRotationModeTags::ViewDirection.GetTag(), Map);
NetSerializeOptionalValue(Archive.IsSaving(), Archive, Stance, AlsStanceTags::Standing.GetTag(), Map);
NetSerializeOptionalValue(Archive.IsSaving(), Archive, MaxAllowedGait, AlsGaitTags::Walking.GetTag(), Map);
NetSerializeOptionalValue(Archive.IsSaving(), Archive, MaxAllowedGait, AlsGaitTags::Running.GetTag(), Map);

return !Archive.IsError();
}
Expand All @@ -47,7 +48,7 @@ void FAlsSavedMove::Clear()

RotationMode = AlsRotationModeTags::ViewDirection;
Stance = AlsStanceTags::Standing;
MaxAllowedGait = AlsGaitTags::Walking;
MaxAllowedGait = AlsGaitTags::Running;
}

void FAlsSavedMove::SetMoveFor(ACharacter* Character, const float NewDeltaTime, const FVector& NewAcceleration,
Expand Down Expand Up @@ -134,7 +135,9 @@ UAlsCharacterMovementComponent::UAlsCharacterMovementComponent()
MinAnalogWalkSpeed = 25.0f;
MaxWalkSpeed = 375.0f;
MaxWalkSpeedCrouched = 150.0f;
GroundFriction = 12.0f;
MaxAccelerationWalking = 2000.0f;
BrakingDecelerationWalking = 1500.0f;
GroundFriction = 4.0f;

AirControl = 0.15f;

Expand Down Expand Up @@ -284,20 +287,12 @@ void UAlsCharacterMovementComponent::CalcVelocity(const float DeltaTime, const f

float UAlsCharacterMovementComponent::GetMaxAcceleration() const
{
// Get the acceleration using the movement curve. This allows for fine control over movement behavior at each speed.

return IsMovingOnGround() && ALS_ENSURE(IsValid(GaitSettings.AccelerationAndDecelerationAndGroundFrictionCurve))
? GaitSettings.AccelerationAndDecelerationAndGroundFrictionCurve->FloatCurves[0].Eval(CalculateGaitAmount())
: Super::GetMaxAcceleration();
}

float UAlsCharacterMovementComponent::GetMaxBrakingDeceleration() const
{
// Get the deceleration using the movement curve. This allows for fine control over movement behavior at each speed.
if (IsMovingOnGround())
{
return MaxAccelerationWalking;
}

return IsMovingOnGround() && ALS_ENSURE(IsValid(GaitSettings.AccelerationAndDecelerationAndGroundFrictionCurve))
? GaitSettings.AccelerationAndDecelerationAndGroundFrictionCurve->FloatCurves[1].Eval(CalculateGaitAmount())
: Super::GetMaxBrakingDeceleration();
return Super::GetMaxAcceleration();
}

void UAlsCharacterMovementComponent::ControlledCharacterMove(const FVector& InputVector, const float DeltaTime)
Expand All @@ -321,15 +316,20 @@ void UAlsCharacterMovementComponent::PhysicsRotation(const float DeltaTime)
}
}

void UAlsCharacterMovementComponent::PhysWalking(const float DeltaTime, int32 Iterations)
void UAlsCharacterMovementComponent::MoveSmooth(const FVector& InVelocity, const float DeltaTime, FStepDownResult* StepDownResult)
{
if (ALS_ENSURE(IsValid(GaitSettings.AccelerationAndDecelerationAndGroundFrictionCurve)))
if (IsMovingOnGround())
{
// Get the ground friction using the movement curve. This allows for fine control over movement behavior at each speed.

GroundFriction = GaitSettings.AccelerationAndDecelerationAndGroundFrictionCurve->FloatCurves[2].Eval(CalculateGaitAmount());
RefreshGroundedMovementSettings();
}

Super::MoveSmooth(InVelocity, DeltaTime, StepDownResult);
}

void UAlsCharacterMovementComponent::PhysWalking(const float DeltaTime, int32 Iterations)
{
RefreshGroundedMovementSettings();

// TODO Copied with modifications from UCharacterMovementComponent::PhysWalking(). After the
// TODO release of a new engine version, this code should be updated to match the source code.

Expand Down Expand Up @@ -582,12 +582,7 @@ void UAlsCharacterMovementComponent::PhysWalking(const float DeltaTime, int32 It

void UAlsCharacterMovementComponent::PhysNavWalking(const float DeltaTime, const int32 Iterations)
{
if (ALS_ENSURE(IsValid(GaitSettings.AccelerationAndDecelerationAndGroundFrictionCurve)))
{
// Get the ground friction using the movement curve. This allows for fine control over movement behavior at each speed.

GroundFriction = GaitSettings.AccelerationAndDecelerationAndGroundFrictionCurve->FloatCurves[2].Eval(CalculateGaitAmount());
}
RefreshGroundedMovementSettings();

Super::PhysNavWalking(DeltaTime, Iterations);
}
Expand Down Expand Up @@ -896,15 +891,15 @@ void UAlsCharacterMovementComponent::SetMovementSettings(UAlsMovementSettings* N

void UAlsCharacterMovementComponent::RefreshGaitSettings()
{
if (ALS_ENSURE(IsValid(MovementSettings)))
if (!ALS_ENSURE(IsValid(MovementSettings)))
{
const auto* StanceSettings{MovementSettings->RotationModes.Find(RotationMode)};
const auto* NewGaitSettings{ALS_ENSURE(StanceSettings != nullptr) ? StanceSettings->Stances.Find(Stance) : nullptr};

GaitSettings = ALS_ENSURE(NewGaitSettings != nullptr) ? *NewGaitSettings : FAlsMovementGaitSettings{};
return;
}

RefreshMaxWalkSpeed();
const auto* StanceSettings{MovementSettings->RotationModes.Find(RotationMode)};
const auto* NewGaitSettings{ALS_ENSURE(StanceSettings != nullptr) ? StanceSettings->Stances.Find(Stance) : nullptr};

GaitSettings = ALS_ENSURE(NewGaitSettings != nullptr) ? *NewGaitSettings : FAlsMovementGaitSettings{};
}

void UAlsCharacterMovementComponent::SetRotationMode(const FGameplayTag& NewRotationMode)
Expand All @@ -927,47 +922,92 @@ void UAlsCharacterMovementComponent::SetStance(const FGameplayTag& NewStance)
}
}

void UAlsCharacterMovementComponent::SetMaxAllowedGait(const FGameplayTag& NewMaxAllowedGait)
void UAlsCharacterMovementComponent::RefreshGroundedMovementSettings()
{
if (MaxAllowedGait != NewMaxAllowedGait)
auto WalkSpeed{GaitSettings.WalkForwardSpeed};
auto RunSpeed{GaitSettings.RunForwardSpeed};

if (RotationMode != AlsRotationModeTags::VelocityDirection && IsValid(MovementSettings))
{
MaxAllowedGait = NewMaxAllowedGait;
auto VelocityDirection{Velocity};

RefreshMaxWalkSpeed();
}
}
if (VelocityDirection.Normalize())
{
const auto* Controller{GetController()};

void UAlsCharacterMovementComponent::RefreshMaxWalkSpeed()
{
MaxWalkSpeed = GaitSettings.GetSpeedByGait(MaxAllowedGait);
MaxWalkSpeedCrouched = MaxWalkSpeed;
}
const auto ViewRotation{
IsValid(Controller)
? GetController()->GetControlRotation()
: GetCharacterOwner()->GetViewRotation()
};

float UAlsCharacterMovementComponent::CalculateGaitAmount() const
{
// Map the character's current speed to the configured movement speeds ranging from 0 to 3,
// where 0 is stopped, 1 is walking, 2 is running, and 3 is sprinting. This allows us to vary
// movement speeds but still use the mapped range in calculations for consistent results.
const auto ViewRotation2D{UAlsRotation::GetTwist(ViewRotation.Quaternion(), -GetGravityDirection())};

const auto Speed{UE_REAL_TO_FLOAT(Velocity.Size2D())};
// Ideally we should use actor rotation here instead of view rotation, but we can't do that because ALS has
// full control over actor rotation and it is not synchronized over the network, so it would cause jitter.

if (Speed <= GaitSettings.WalkSpeed)
{
static const FVector2f GaitAmount{0.0f, 1.0f};
const auto RelativeVelocityDirection{ViewRotation2D.UnrotateVector(VelocityDirection)};

const auto MovingForwardAmount{
FMath::GetMappedRangeValueClamped(MovementSettings->VelocityDirectionToSpeedInterpolationRange,
{1.0f, 0.0f}, RelativeVelocityDirection.X)
};

return FMath::GetMappedRangeValueClamped({0.0f, GaitSettings.WalkSpeed}, GaitAmount, Speed);
WalkSpeed = FMath::Lerp(GaitSettings.WalkBackwardSpeed, GaitSettings.WalkForwardSpeed, MovingForwardAmount);
RunSpeed = FMath::Lerp(GaitSettings.RunBackwardSpeed, GaitSettings.RunForwardSpeed, MovingForwardAmount);
}
}

if (Speed <= GaitSettings.RunSpeed)
// Map the character's current speed to the to the speed ranges from the movement settings. This allows
// us to vary movement speeds but still use the mapped range in calculations for consistent results.

const auto Speed{UE_REAL_TO_FLOAT(Velocity.Size2D())};

if (Speed <= WalkSpeed)
{
GaitAmount = FMath::GetMappedRangeValueClamped(FVector2f{0.0f, WalkSpeed}, {0.0f, 1.0f}, Speed);
}
else if (Speed <= RunSpeed)
{
GaitAmount = FMath::GetMappedRangeValueClamped(FVector2f{WalkSpeed, RunSpeed}, {1.0f, 2.0f}, Speed);
}
else
{
static const FVector2f GaitAmount{1.0f, 2.0f};
GaitAmount = FMath::GetMappedRangeValueClamped(FVector2f{RunSpeed, GaitSettings.SprintSpeed}, {2.0f, 3.0f}, Speed);
}

return FMath::GetMappedRangeValueClamped({GaitSettings.WalkSpeed, GaitSettings.RunSpeed}, GaitAmount, Speed);
if (MaxAllowedGait == AlsGaitTags::Walking)
{
MaxWalkSpeed = WalkSpeed;
}
else if (MaxAllowedGait == AlsGaitTags::Running)
{
MaxWalkSpeed = RunSpeed;
}
else if (MaxAllowedGait == AlsGaitTags::Sprinting)
{
MaxWalkSpeed = GaitSettings.SprintSpeed;
}
else
{
MaxWalkSpeed = GaitSettings.RunForwardSpeed;
}

static const FVector2f GaitAmount{2.0f, 3.0f};
MaxWalkSpeedCrouched = MaxWalkSpeed;

// Get acceleration, deceleration and ground friction using a curve. This
// allows us to precisely control the movement behavior at each speed.

return FMath::GetMappedRangeValueClamped({GaitSettings.RunSpeed, GaitSettings.SprintSpeed}, GaitAmount, Speed);
if (ALS_ENSURE(IsValid(GaitSettings.AccelerationAndDecelerationAndGroundFrictionCurve)))
{
const auto& AccelerationAndDecelerationAndGroundFrictionCurves{
GaitSettings.AccelerationAndDecelerationAndGroundFrictionCurve->FloatCurves
};

MaxAccelerationWalking = AccelerationAndDecelerationAndGroundFrictionCurves[0].Eval(GaitAmount);
BrakingDecelerationWalking = AccelerationAndDecelerationAndGroundFrictionCurves[1].Eval(GaitAmount);
GroundFriction = AccelerationAndDecelerationAndGroundFrictionCurves[2].Eval(GaitAmount);
}
}

void UAlsCharacterMovementComponent::SetMovementModeLocked(const bool bNewMovementModeLocked)
Expand Down
16 changes: 16 additions & 0 deletions Source/ALS/Private/Settings/AlsMovementSettings.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "Settings/AlsMovementSettings.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(AlsMovementSettings)

#if WITH_EDITOR
void UAlsMovementSettings::PostEditChangeProperty(FPropertyChangedEvent& ChangedEvent)
{
if (ChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(ThisClass, VelocityDirectionToSpeedInterpolationRange))
{
VelocityDirectionToSpeedInterpolationRange.Y = FMath::Min(VelocityDirectionToSpeedInterpolationRange.X,
VelocityDirectionToSpeedInterpolationRange.Y);
}

Super::PostEditChangeProperty(ChangedEvent);
}
#endif
35 changes: 27 additions & 8 deletions Source/ALS/Public/AlsCharacterMovementComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ALS_API FAlsCharacterNetworkMoveData : public FCharacterNetworkMoveData

FGameplayTag Stance{AlsStanceTags::Standing};

FGameplayTag MaxAllowedGait{AlsGaitTags::Walking};
FGameplayTag MaxAllowedGait{AlsGaitTags::Running};

public:
virtual void ClientFillNetworkMoveData(const FSavedMove_Character& Move, ENetworkMoveType MoveType) override;
Expand All @@ -43,7 +43,7 @@ class ALS_API FAlsSavedMove : public FSavedMove_Character

FGameplayTag Stance{AlsStanceTags::Standing};

FGameplayTag MaxAllowedGait{AlsGaitTags::Walking};
FGameplayTag MaxAllowedGait{AlsGaitTags::Running};

public:
virtual void Clear() override;
Expand Down Expand Up @@ -93,7 +93,13 @@ class ALS_API UAlsCharacterMovementComponent : public UCharacterMovementComponen
FGameplayTag Stance{AlsStanceTags::Standing};

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "State", Transient)
FGameplayTag MaxAllowedGait{AlsGaitTags::Walking};
FGameplayTag MaxAllowedGait{AlsGaitTags::Running};

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "State", Transient, Meta = (ClampMin = 0, ClampMax = 3))
float GaitAmount{0.0f};

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "State", Transient, Meta = (ClampMin = 0))
float MaxAccelerationWalking{0.0f};

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "State", Transient)
uint8 bMovementModeLocked : 1 {false};
Expand Down Expand Up @@ -144,14 +150,15 @@ class ALS_API UAlsCharacterMovementComponent : public UCharacterMovementComponen

virtual float GetMaxAcceleration() const override;

virtual float GetMaxBrakingDeceleration() const override;

protected:
virtual void ControlledCharacterMove(const FVector& InputVector, float DeltaTime) override;

public:
virtual void PhysicsRotation(float DeltaTime) override;

// ReSharper disable once CppRedefinitionOfDefaultArgumentInOverrideFunction
virtual void MoveSmooth(const FVector& InVelocity, float DeltaTime, FStepDownResult* StepDownResult = nullptr) override;

protected:
virtual void PhysWalking(float DeltaTime, int32 Iterations) override;

Expand Down Expand Up @@ -201,12 +208,14 @@ class ALS_API UAlsCharacterMovementComponent : public UCharacterMovementComponen

void SetMaxAllowedGait(const FGameplayTag& NewMaxAllowedGait);

// Returns the character's current speed, mapped to the speed ranges from the movement settings.
// Varies from 0 to 3, where 0 is stopped, 1 is walking, 2 is running, and 3 is sprinting.
float GetGaitAmount() const;

private:
void RefreshMaxWalkSpeed();
void RefreshGroundedMovementSettings();

public:
float CalculateGaitAmount() const;

void SetMovementModeLocked(bool bNewMovementModeLocked);

void SetInputBlocked(bool bNewInputBlocked);
Expand All @@ -233,3 +242,13 @@ inline const FGameplayTag& UAlsCharacterMovementComponent::GetMaxAllowedGait() c
{
return MaxAllowedGait;
}

inline void UAlsCharacterMovementComponent::SetMaxAllowedGait(const FGameplayTag& NewMaxAllowedGait)
{
MaxAllowedGait = NewMaxAllowedGait;
}

inline float UAlsCharacterMovementComponent::GetGaitAmount() const
{
return GaitAmount;
}
Loading

0 comments on commit 8d1817a

Please sign in to comment.