Download

Velocity Stutter In Custom Movement Component

Hello guys. I’m very new to C++ and only know the basics. Most of what I know about Movement Components is what I’ve understood reading through ProjectileMovementComponent.cpp, FloatingPawnMovement.cpp and CharacterMovementComponent.cpp in the source code of Unreal. I’ve also been reading the documentation pages of functions such as “SlideAlongSurface” and “SafeMoveUpdatedComponent”. So I’ve started to make my own Movement Component and through trial and error I think I’ve started to understand the rough basics of how they work.

But my novice understanding of how these systems work have left me unable to fix the current bug/problem I’m having.

There seems to be a stutter in the velocity whenever my pawn’s capsule collision rubs against a wall.

Here’s a video demonstrating my problem:

It might be a little hard to see but the pawn jolts backwards and to the side randomly while I walk along a wall.

The Movement Component (Stub01MovementComponent) is currently just a modified version of Floating Pawn Movement without the checks for WorldBounds and if the player is AI or LocalController:

The movement is created in blueprint by adding a new velocity to the current velocity and setting that velocity as the new velocity of the Movement Component:

Calculations of Gravity, Forward Movement, Right Movement, Friction, WallRun, Etc… is done in separate functions that I plan on porting from Blueprint to C++ functions once I’m happy with them:

But back to the problem at hand, the velocity stuttering. I’ve tested all of my blueprint functions for calculating the velocities and displayed them on my debugHud while playing to see if the changed during the stuttering. The only thing to change in sync with the stutter was the friction. So I removed the friction function and tested again, but the stutter were still happening. This left me scratching my head since all the other velocity variables did not show any sign of the stutter, yet the actual velocity of the Movement Component still showed stuttering. The “aha!” moment came when I realized that my blueprint functions was just adding velocity to the stutter, and not the cause of the stutter itself.

This led me to believe that the stutter was a problem with the Movement Component itself.

I tried my usual trial and error method again by commenting out these two lines in the code:

This seemed to fix the walk along wall stutter, but now the player’s collision would stutter and get stuck. The velocity also behaved differently with the player not keeping his momentum properly:

Here’s another video showing the same situations with the original component without the two lines commented out:

So with this explanation I hope that some of you can help me figure out a solution to this. Also does anyone have any good recommendations for resources where I can learn more about how to make my own Movement Component? Clueless trial and error can only get me so far:P

Thanks for your time!

-Headstub

After some more testing and research it seems like “SlideAlongSurface” is the main culprit. It works fine when it’s only sliding along one surface(the floor), but once it hits a second surface(the wall) it starts to stutter. If it hits a third surface(another wall) it completely freezes up and stutters for almost a whole second before proceeding to slide further.

Here’s a video showing it off:

I’ve started reading through the code for “SlideAlongSurface” but I’m still too new to understand what is causing the stutter. My guess right now is that it has something to do with the function named “TwoWallAdjust”

Here’s the code used in SlideAlongSurface for reference:


float UMovementComponent::SlideAlongSurface(const FVector& Delta, float Time, const FVector& Normal, FHitResult& Hit, bool bHandleImpact)
{
	if (!Hit.bBlockingHit)
	{
		return 0.f;
	}

	float PercentTimeApplied = 0.f;
	const FVector OldHitNormal = Normal;

	FVector SlideDelta = ComputeSlideVector(Delta, Time, Normal, Hit);

	if ((SlideDelta | Delta) > 0.f)
	{
		const FQuat Rotation = UpdatedComponent->GetComponentQuat();
		SafeMoveUpdatedComponent(SlideDelta, Rotation, true, Hit);

		const float FirstHitPercent = Hit.Time;
		PercentTimeApplied = FirstHitPercent;
		if (Hit.IsValidBlockingHit())
		{
			// Notify first impact
			if (bHandleImpact)
			{
				HandleImpact(Hit, FirstHitPercent * Time, SlideDelta);
			}

			// Compute new slide normal when hitting multiple surfaces.
			TwoWallAdjust(SlideDelta, Hit, OldHitNormal);

			// Only proceed if the new direction is of significant length and not in reverse of original attempted move.
			if (!SlideDelta.IsNearlyZero(1e-3f) && (SlideDelta | Delta) > 0.f)
			{
				// Perform second move
				SafeMoveUpdatedComponent(SlideDelta, Rotation, true, Hit);
				const float SecondHitPercent = Hit.Time * (1.f - FirstHitPercent);
				PercentTimeApplied += SecondHitPercent;

				// Notify second impact
				if (bHandleImpact && Hit.bBlockingHit)
				{
					HandleImpact(Hit, SecondHitPercent * Time, SlideDelta);
				}
			}
		}

		return FMath::Clamp(PercentTimeApplied, 0.f, 1.f);
	}

	return 0.f;
}


void UMovementComponent::TwoWallAdjust(FVector& OutDelta, const FHitResult& Hit, const FVector& OldHitNormal) const
{
	FVector Delta = OutDelta;
	const FVector HitNormal = Hit.Normal;

	if ((OldHitNormal | HitNormal) <= 0.f) //90 or less corner, so use cross product for direction
	{
		const FVector DesiredDir = Delta;
		FVector NewDir = (HitNormal ^ OldHitNormal);
		NewDir = NewDir.GetSafeNormal();
		Delta = (Delta | NewDir) * (1.f - Hit.Time) * NewDir;
		if ((DesiredDir | Delta) < 0.f)
		{
			Delta = -1.f * Delta;
		}
	}
	else //adjust to new wall
	{
		const FVector DesiredDir = Delta;
		Delta = ComputeSlideVector(Delta, 1.f - Hit.Time, HitNormal, Hit);
		if ((Delta | DesiredDir) <= 0.f)
		{
			Delta = FVector::ZeroVector;
		}
		else if ( FMath::Abs((HitNormal | OldHitNormal) - 1.f) < KINDA_SMALL_NUMBER )
		{
			// we hit the same wall again even after adjusting to move along it the first time
			// nudge away from it (this can happen due to precision issues)
			Delta += HitNormal * 0.01f;
		}
	}

	OutDelta = Delta;
}

Alright, I fixed it! :smiley:

The problem with “SlideAlongSurface” is that it only accounts for 1 second impact after hitting the initial surface. So I made my own function called “DeflectionSlide” which repeats the “TwoWallAdjust” and “SafeMoveUpdatedComponent” up to 4 times on the same frame/tick.

Here’s my code:


float UStub01MovementComponent::DeflectionSlide(const FVector& Delta, float Time, const FVector& Normal, FHitResult& Hit)
{
	const FVector OldHitNormal = Normal;

	FVector SlideDelta = ComputeSlideVector(Delta, Time, Normal, Hit);
	const FQuat Rotation = UpdatedComponent->GetComponentQuat();
	SafeMoveUpdatedComponent(SlideDelta, Rotation, true, Hit);

	//If you hit a SECOND wall, readjust, then slide along it
	if (Hit.IsValidBlockingHit())
	{
		TwoWallAdjust(SlideDelta, Hit, OldHitNormal);
		SafeMoveUpdatedComponent(SlideDelta, Rotation, true, Hit);

		//If you hit a THIRD wall, readjust, then slide along it
		if (Hit.IsValidBlockingHit())
		{
			TwoWallAdjust(SlideDelta, Hit, OldHitNormal);
			SafeMoveUpdatedComponent(SlideDelta, Rotation, true, Hit);

			//If you hit a FOURTH wall, readjust, then slide along it
			if (Hit.IsValidBlockingHit())
			{
				TwoWallAdjust(SlideDelta, Hit, OldHitNormal);
				SafeMoveUpdatedComponent(SlideDelta, Rotation, true, Hit);
			}
		}
	}
	return 0.f;
}

By calling this instead of “SlideAlongSurface” my pawn slides around the world smoothly while hitting multiple surfaces at once.

Video showing it off:

Hope this post can help someone else having the same problem;)

-Headstub