Atlas
How PhysWalking resolves floor & step-up
YouHow does PhysWalking step up onto ledges and update the floor?
AnswerHow PhysWalking resolves floor & step-upUCharacterMovementComponent::PhysWalking advances character movement in bounded sub-steps, keeping the capsule grounded across ramps and ledges. MoveAlongFloor projects horizontal velocity onto CurrentFloor and sweeps the capsule. When blocked by a vertical face, StepUp sweeps up by MaxStepHeight, forward, and back down.… 3 source references
1 edit awaiting review
Edit to CharacterMovementComponent.cpp awaiting review+3−1

How PhysWalking resolves floor & step-up

Documentsaved 12:54:01 AMrevision 2 of 2

Each sub-step in UCharacterMovementComponent::PhysWalking projects horizontal velocity onto the current floor plane, sweeps the capsule forward, and re-evaluates the floor before the next iteration.

3 / 4One walking sub-step in UCharacterMovementComponentpipeline

On a blocking wall hit, StepUp sweeps up, forward, and down onto the ledge.

Frame cost on GameThread

GameThread movement & physics shareprofile

UnrealEditor.exe · 0.82–1.46 s · GameThread · 4,820 samples

Function
Engine38%1.2%
Engine34%3.8%
Engine23%6.4%
Engine12%9.1%

Walkable slope projection

Movement along a ramp of normal n\mathbf{n} keeps horizontal speed by projecting Δx\Delta \mathbf{x} onto the tangent plane:

dramp=Δx−Δx⋅nnz z^,nz>cos⁡(θmax⁡)\mathbf{d}_{\text{ramp}} = \Delta \mathbf{x} - \frac{\Delta \mathbf{x} \cdot \mathbf{n}}{n_z}\,\hat{\mathbf{z}}, \quad n_z > \cos(\theta_{\max})
Engine/Source/Runtime/Engine/Private/Components/CharacterMovementComponent.cpp
21void UCharacterMovementComponent::PhysWalking(float deltaTime, int32 Iterations)
25 while ((deltaTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations))
SCOPE_CYCLE_COUNTER(STAT_CharacterMovementPerformMovement);

if (!HasValidData() || UpdatedComponent->IsSimulatingPhysics())
{
return;
}

// Integrate pending root motion and velocity for the active movement mode.
StartNewPhysics(DeltaSeconds, 0);
UpdateComponentVelocity();
}

void UCharacterMovementComponent::PhysWalking(float deltaTime, int32 Iterations)
{
SCOPE_CYCLE_COUNTER(STAT_CharPhysWalking);

while ((deltaTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations))
{
Iterations++;
const float timeTick = GetSimulationTimeStep(deltaTime, Iterations);
deltaTime -= timeTick;

const FVector OldLocation = UpdatedComponent->GetComponentLocation();
FStepDownResult StepDownResult;

// Project horizontal velocity onto the current floor plane and sweep.
MoveAlongFloor(Velocity, timeTick, &StepDownResult);

// Reuse the step-down floor hit when available to avoid a redundant sweep.
if (StepDownResult.bComputedFloor)
{
CurrentFloor = StepDownResult.FloorResult;
}
else
{
Why StepDownResult avoids a second sweep2 min ago

Why. StepUp already sweeps downward to land the capsule on the step and populates StepDownResult.FloorResult.

Cost. Skipping FindFloor here saves an extra capsule sweep on every ledge step-up.

FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, false);
}

if (!CurrentFloor.IsWalkableFloor())
{
SetMovementMode(MOVE_Falling);
StartNewPhysics(deltaTime, Iterations);
return;
}
}
}

void UCharacterMovementComponent::MoveAlongFloor(
const FVector& InVelocity,
float DeltaSeconds,
FStepDownResult* OutStepDownResult)
{
if (!CurrentFloor.IsWalkableFloor())
{
return;
}