Engineering Post-Mortem: Wall Run System

A technical deep dive into extending Unreal Engine 5’s movement physics, managing engine crashes, and solving complex vector math problems.

Engine: Unreal Engine 5 (C++)
System: Physics-based wall-run using custom movement mode
Architecture: Implemented by extending UCharacterMovementComponent to remain compatible with Unreal’s movement pipeline and future networking support.

Execution Logic Flow

The following chart visualizes the decision-making inside TryEnterWallRun() and the custom physics update loop.

flowchart TD A[Player Jumps or Falls] --> B{Input Pressed?} B -- Yes --> D[Line Trace: Left and Right] B -- No --> C[Standard Falling] D --> E{Wall Hit?} E -- No --> C E -- Yes --> F[Validate Wall Surface] F --> F1[Dot Product Check] F1 -->|Valid| G[Enter MOVE_Custom] F1 -->|Invalid| C G --> H[PhysWallRun Loop] H --> I[Calculate Tangent Vector] I --> J[Project Velocity onto Wall] J --> K[Apply Dynamic Gravity Ramp] K --> L[Move UpdatedComponent]

The Challenge: Fighting Gravity & Momentum

The hardest part wasn’t wall detection, it was making the movement feel physically correct without instant sliding or unnatural sticking.

The Gravity Issue: Disabling gravity felt floaty. Using default gravity caused the player to drop too aggressively.

The Solution: A Dynamic Gravity Ramp. Gravity starts reduced on wall contact, then ramps up over time, naturally forcing the player to jump off or fall.


// Ramp gravity over time to maintain weight and control
float GravityFactor =
    FMath::Clamp(CurrentWallRunTime / GravityRampDuration, 0.f, 1.f);

// Apply gravity relative to time spent on wall
Velocity.Z += (BaseGravity + GravityRamp * GravityFactor) * DeltaTime;

// Prevent instant plummeting
Velocity.Z = FMath::Clamp(Velocity.Z, -600.f, 200.f);
        

Stability: Why the Engine Crashed

Extending UCharacterMovementComponent is powerful but dangerous. Early versions caused multiple crashes related to SafeMoveUpdatedComponent.

Directly modifying velocity inside PhysCustom without properly resolving blocking hits caused the capsule to become embedded in geometry, leading to physics instability and division-by-zero errors.

The Fix: All movement was rewritten to rely on FVector::VectorPlaneProject, ensuring velocity is safely projected along the wall surface before movement is committed.

Key Takeaways

Outcome

The result is a stable, physics-driven wall-run system with predictable entry, sustained momentum, and clean exit behavior, without teleportation or animation dependency.