From 14d36968054bc6a47e60529a122fb033505afb21 Mon Sep 17 00:00:00 2001 From: Nikolay Petrov Date: Tue, 6 Jan 2026 21:42:31 +0500 Subject: [PATCH] feat(movement): improve air control and ground snapping behavior - Add directional air control modifier to prevent instant braking mid-air * Reduce control effectiveness by 50% when reversing direction (Dot < 0) * Allows side-to-side adjustments while maintaining forward momentum - Remove input requirement for velocity projection on ground snap * Project velocity onto slope even without player input * Preserves momentum when landing on slopes - Add KINDA_SMALL_NUMBER checks to prevent division by zero - Add GetRenderLocation() Blueprint accessor for VFX positioning - Clean up comments: translate Russian text to English This makes air movement feel more realistic (less like flying) while improving slope handling consistency. --- .../Movement/TengriMovementComponent.cpp | 58 ++++++++++++------- .../Movement/TengriMovementComponent.h | 4 ++ 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/Source/TengriPlatformer/Movement/TengriMovementComponent.cpp b/Source/TengriPlatformer/Movement/TengriMovementComponent.cpp index ca70e52..5eb00c1 100644 --- a/Source/TengriPlatformer/Movement/TengriMovementComponent.cpp +++ b/Source/TengriPlatformer/Movement/TengriMovementComponent.cpp @@ -313,33 +313,49 @@ void UTengriMovementComponent::TickPhysics( const float CurrentZ = PhysicsVelocity.Z; FVector HorizontalVelocity(PhysicsVelocity.X, PhysicsVelocity.Y, 0.f); - // Select Acceleration/Friction based on state - const float CurrentAccel = bIsGrounded - ? MovementConfig->Acceleration - : (MovementConfig->Acceleration * MovementConfig->AirControl); - const float CurrentFriction = bIsGrounded ? MovementConfig->Friction : MovementConfig->AirFriction; if (!InputVector.IsNearlyZero()) { + // Calculate directional air control modifier + // Reduce control when trying to reverse direction mid-air + const FVector VelocityDir = HorizontalVelocity.GetSafeNormal(); + const float Dot = FVector::DotProduct(VelocityDir, InputVector); + + float FinalAirControl = MovementConfig->AirControl; + + // Reduce braking effectiveness in air (prevents instant direction changes) + if (Dot < 0.0f) + { + FinalAirControl *= 0.5f; + } + + const float CurrentAccel = MovementConfig->Acceleration * FinalAirControl; const FVector TargetVelocity = InputVector * MovementConfig->MaxSpeed; - HorizontalVelocity = FMath::VInterpTo( - HorizontalVelocity, - TargetVelocity, - FixedDeltaTime, - CurrentAccel // <-- Uses Air Control if flying - ); + + if (CurrentAccel > KINDA_SMALL_NUMBER) + { + HorizontalVelocity = FMath::VInterpTo( + HorizontalVelocity, + TargetVelocity, + FixedDeltaTime, + CurrentAccel + ); + } } else { - HorizontalVelocity = FMath::VInterpTo( - HorizontalVelocity, - FVector::ZeroVector, - FixedDeltaTime, - CurrentFriction // <-- Usually 0 in air - ); + if (CurrentFriction > KINDA_SMALL_NUMBER) + { + HorizontalVelocity = FMath::VInterpTo( + HorizontalVelocity, + FVector::ZeroVector, + FixedDeltaTime, + CurrentFriction + ); + } } PhysicsVelocity = HorizontalVelocity; @@ -450,8 +466,8 @@ void UTengriMovementComponent::TickPhysics( FHitResult SnapHit; bJustSnapped = PerformGroundSnapping(PhysicsLocation, SnapHit); - // Project velocity onto slope if snapped - if (bJustSnapped && !InputVector.IsNearlyZero()) + // Always project velocity onto slope when snapped (preserves momentum) + if (bJustSnapped) { PhysicsVelocity = UTengriCollisionResolver::ProjectVelocity( PhysicsVelocity, @@ -571,8 +587,8 @@ bool UTengriMovementComponent::PerformGroundSnapping( void UTengriMovementComponent::ForceRotation(const FRotator& NewRotation) { - // Обновляем все внутренние состояния, чтобы физика "подхватила" новый поворот + // Update all internal states so physics adopts new rotation immediately PhysicsRotation = NewRotation; RenderRotation = NewRotation; - PreviousPhysicsRotation = NewRotation; // Сбрасываем интерполяцию + PreviousPhysicsRotation = NewRotation; // Reset interpolation } \ No newline at end of file diff --git a/Source/TengriPlatformer/Movement/TengriMovementComponent.h b/Source/TengriPlatformer/Movement/TengriMovementComponent.h index 0730958..de7aa05 100644 --- a/Source/TengriPlatformer/Movement/TengriMovementComponent.h +++ b/Source/TengriPlatformer/Movement/TengriMovementComponent.h @@ -32,6 +32,10 @@ class TENGRIPLATFORMER_API UTengriMovementComponent : public UActorComponent public: UTengriMovementComponent(); + /** Get interpolated render location */ + UFUNCTION(BlueprintPure, Category = "Tengri Movement") + FVector GetRenderLocation() const { return RenderLocation; } + /** Event triggered when character lands (broadcast after physics loop completes) */ UPROPERTY(BlueprintAssignable, Category = "Tengri Movement|Events") FOnTengriLandingSignature OnLanded;