Movement System

This is a custom first-person movement system I built in Unreal Engine 5 using C++ as my graduation project.

It's made to feel smooth, responsive, and fluid, with mechanics like double jump, dash, wall-running, ledge climbing, crouching, and sliding.

The goal was to create a movement setup that feels great to control and can easily be expanded with new abilities or traversal options.

Features

Double Jump

I built this double jump system as an extension of Unreal's character movement, aiming to make it feel responsive and flexible while working seamlessly with other movement features like wall-running, dashing, and ledge climbing.

The logic is pretty straightforward, the character keeps track of whether it's grounded and if a midair jump is still available. On a jump input, it checks those states and decides whether to perform a regular jump or launch the player midair for a second jump.

When the player lands, the states reset automatically, re-enabling dashing and grounded movement.

I kept the system simple and modular so it can easily tie into other abilities. For example, jumping off a wall-run re-enables the double jump and dash, letting the player chain moves together smoothly. It's a small feature, but it adds a ton of freedom and flow to the overall movement system.

Dashing

The dash system is built to feel quick, responsive, and readable. It gives the player a burst of speed in their movement direction while keeping full control over how it connects to other abilities like wall-running or sliding.

When the dash input is triggered, the character checks a few key conditions before dashing: it can't dash while already dashing, wall-running, crouching, or if the dash is still on cooldown. Once cleared, it determines the dash direction — either from the player's current movement input or their velocity if they're already in motion. The dash uses manual interpolation instead of physics forces, which gives smoother, more predictable control over timing, distance, and FOV effects.

While dashing, the character's position is linearly interpolated between the start and end points each frame in Tick(), creating a smooth motion independent of physics.

When the dash ends, gravity and normal movement resume immediately.

The dash cooldown resets only when grounded, preventing players from endlessly chaining dashes in midair.

Overall, this system focuses on player control and flow. It's intentionally lightweight and blends seamlessly into other abilities. You can dash out of a wall-run, double jump during a dash, or slide right after landing, and it all feels fluid and connected.

Wall Run

The wall-run system lets the player run along vertical surfaces while maintaining smooth control over momentum, gravity, and camera tilt. I built it to feel fluid and natural, something you can easily transition into from a jump, then out of into a dash or double jump, without losing movement flow.

The system constantly checks for valid walls around the player using line traces to the left and right. If a wall is detected within range and its normal isn't too steep or flat, the player "sticks" to it and starts wall-running in the appropriate direction.

CheckForWall() handles all the detection logic. It performs a series of traces to both sides of the player to determine if there's a suitable wall for running. If it finds one, it sets the wall's normal, determines the run direction (left or right), and triggers the stick-to-wall logic.

Once a valid wall is found, the player is both pushed toward the surface and launched along it using LaunchCharacter(). This keeps the player glued to the wall while allowing them to move horizontally.

While running, the camera tilts dynamically depending on which side of the wall the player is on, giving a strong sense of motion and spatial awareness.

If the player jumps off or loses the wall, StopWallRun() resets all flags and restores normal gravity.

The system also has a temporary suppression mechanic, which prevents instantly reattaching to the same wall right after jumping off. This keeps the motion clean and prevents jittery transitions. Overall, the wall-run system is designed around freedom and momentum. It connects seamlessly with dashing, double jumping, and sliding, allowing the player to chain moves together without breaking flow.

Ledge Climb

The ledge climb system lets the player automatically grab and pull up onto a ledge while falling near a climbable surface. It's designed to feel smooth and contextual, you don't have to press anything; if you're jumping toward a ledge, the character naturally grabs it and climbs up.

The logic is driven by two line traces every frame inside FindLedge(), one low and one high, that check if there's a climbable wall in front of the player but no obstacle above it. This ensures the character can actually pull up without hitting geometry.

If the bottom trace hits (meaning there's a wall directly in front of you) but the top trace doesn't (meaning there's open space above it), and you're currently falling, the system triggers a climb.

When the climb starts, the player's velocity is frozen and the movement component is disabled temporarily by switching the state to Eps_Climbing. The actual climb motion uses UKismetSystemLibrary::MoveComponentTo(), which smoothly interpolates the player's root component up and forward to the top of the ledge. This gives a clean animation-like motion without needing a full animation montage.

AttemptClimb() simply sets bCanClimb to true so the climb can't retrigger mid-animation.

Once the climb finishes (after ClimbTime seconds), CheckPlayerState() detects that the climb duration has passed and calls DontClimb(), resetting the flag and returning control to the player.

Finally, DontClimb() just re-enables normal movement again.

Overall, this system is designed for consistency and readability, instead of relying on animations or complex IK, it uses pure movement logic with line traces and component motion. It integrates naturally with the rest of the movement suite, for example, you can jump toward a wall, wall-run briefly, then grab a ledge and pull up, all in one continuous flow.

Crouch & Sliding

The crouch system lets the player switch seamlessly between standing, crouching, and sliding states depending on movement speed and player input. It's designed to feel responsive and dynamic, similar to movement systems in fast-paced shooters like Apex Legends.

The crouch system is handled through a combination of height interpolation, camera offset, and movement state control. When the player presses the crouch input, the code checks if they're eligible to crouch, meaning they aren't already crouching, sliding, wall-running, or dashing. If they're moving fast enough forward, the same input triggers a slide instead.

When the player releases the crouch key, EndCrouch() resets the crouch state, but only if the player can stand (i.e., there's no obstacle above their head). The system uses a vertical line trace to check this before transitioning back to standing.

The CanStand() function performs that vertical trace from the character's capsule upwards to ensure there's enough room.

The visual and physical height transitions happen through the CrouchHeightChange() function, which smoothly interpolates both the camera height and capsule collider height toward their crouched or standing values. This keeps the motion grounded and realistic instead of snapping between poses.

If the player is sprinting and crouches, the system triggers a slide instead. StartSliding() launches the player forward, lowers friction, and widens the camera FOV to emphasize speed.

When the slide ends, friction and FOV are reset.

In short, the crouch system isn't just a toggle. It's a layered movement state that connects with the rest of the character's abilities. You can sprint into a slide, jump mid-slide into a wall-run, or cancel a crouch into a dash, all without breaking the flow or camera continuity.