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.
main
Nikolay Petrov 2026-01-06 21:42:31 +05:00
parent abe8f565b6
commit 14d3696805
2 changed files with 41 additions and 21 deletions

View File

@ -313,34 +313,50 @@ 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;
if (CurrentAccel > KINDA_SMALL_NUMBER)
{
HorizontalVelocity = FMath::VInterpTo(
HorizontalVelocity,
TargetVelocity,
FixedDeltaTime,
CurrentAccel // <-- Uses Air Control if flying
CurrentAccel
);
}
}
else
{
if (CurrentFriction > KINDA_SMALL_NUMBER)
{
HorizontalVelocity = FMath::VInterpTo(
HorizontalVelocity,
FVector::ZeroVector,
FixedDeltaTime,
CurrentFriction // <-- Usually 0 in air
CurrentFriction
);
}
}
PhysicsVelocity = HorizontalVelocity;
PhysicsVelocity.Z = CurrentZ;
@ -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
}

View File

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