Specialization

Third-Person Parkour

Introduction

A third-person parkour system made in Unreal Engine and C++. It allows the player to move in various ways depending on situation, and the camera changes behaviour to keep the player's target in focus.


This project has been done on the side of Futuregame's curriculum, lasting approximately 12 weeks part-time.


IMPORTANT NOTE:

Animations are not in focus. They are used for visualization purposes only. I have used free animations from Mixamo and retargeted them to fit Unreal Engine's mannequin. Since my options were limited, some movements could have used better suited animations. There is additionally a stickman being animated, for clarity.


Below, you can see all features used in an obstacle course, in the style of Getting Over It. You can also review the code on GitHub.

Features

Character

Climbing


Initiate climbing

If the player gets up to a climbing wall, it automatically starts climbing.


  • I created a new collision channel in Unreal Engine that is used on all climbable actors.
  • Three line traces are used to locate a wall. One going straight forward from the center of the character, and one on each side of it.
  • If two out of three traces hit a wall, the wall is wide enough to climb.

Change how movement works

The player climbs in relation to the character's forward vector instead of the camera's.


  • When climbing, the players enter Unreal Engine's flying movement mode.
  • The friction (braking deceleration) is set to max.
  • The up input is now added to the Z-axis (up) instead of the X-axis (forward).
  • The character is no longer rotated towards the movement direction.
  • The movement is based on the character's rotation rather than the camera's yaw rotation.

Follow the wall

When climbing a curved or tilted surface, the character's rotation follows.


  • Several line traces were created to get the surface normal and rotate the character towards it.
  • All traces start at the character location and end in the direction of the input, going all the way from slightly behind the character to in front.
  • The traces are done in a loop, going from back to front, and break out when there's a hit.
  • The order that the traces are done is critical to be able to climb tight corners.


Find ledge

If the player finds a ledge, it climbs on top of it.


  • A new collision channel is created, which is used on all actors that the player should be able to climb onto.
  • Two line traces are going from the character and straight forward - one slightly lower than the other.
  • If the bottom trace finds a ledge and the top one hits nothing, the character moves to the end of the top trace.


Drop down

The player can interrupt climbing.


  • By pressing left CTRL, the same timer that's used for climb jumping is reset.


Moving on ground


Floor angle

The player changes speed depending on the floor angle.


  • There are several line traces going from the character to the floor in front and to the sides.
  • I created my own function to find the shortest float in an array, since Unreal's built-in function to find the smallest value only allowed three floats.
  • The distance of the shortest line trace is normalized and inverted to change the player's speed with a percentage.


Screenshot 2024-12-18 163320
Screenshot 2024-12-06 154246

Speed over time

The character's movement speed is changed over time instead of instantly.


  • Whenever there is movement input, the speed is lerping to the max speed of current player state.
  • When no input is detected and the player has reached a high enough speed, an artificial input is started in the direction the character is facing. It slows down until standing still.
  • If there is an obstacle in the way, or a new input is detected while slowing down and the player is too slow to start a new artificial input, the player stops immediately.

Run up a wall

When running into a wall, the player starts running up on it.


  • The player must be running and not falling.
  • Two long line traces are used in the character's forward vector, one being slightly longer than the other.
  • If the longer trace hits a wall but not the other, the wall is at enough distance from the player to run up.
  • A shorter line trace is used in the character's forward vector to detect when the wall is right in front.
  • If the short line trace gets a hit, a capsule trace looks for any obstacles above the character.
  • If there are no obstacles, the character starts running up the wall.
  • The player can interrupt this by jumping, and by that performing a wall jump.

Success

Fail

Sliding down wall

When the character is mid-air facing a wall it starts sliding down instead of free falling.


  • The player can start sliding if it isn't exhausted or climbing, and is currently falling.
  • The same line traces that were used to check the floor angle are used to decide if the surface is steep enough to slide down.
  • If all requirements are met, the gravity is set to 0 and a downward velocity is added to the player.


Hookshot


Aiming

Time is slowed down and camera zooms in to let the player aim better.


  • Current state is saved before entering aiming state, so that the player can return to it when finished.
  • The character's rotation follows the camera's rotation.
  • Time is slowed down, which lets the player hover in place.
  • Camera zooms in and is placed on the character's shoulder, to make aiming easier.
  • Energy is drained over time. If depleted, aiming is interrupted.

Using the hookshot

Looking for something to hook to in the direction the player is aiming.


  • A line trace is going straight forward from the camera's location.
  • If an actor with my custom collision channel "Hook" is found, the player starts moving there in a straight line.
  • The player cannot move around while the hookshot is being used.

Leave aiming

Resets all the settings to how they were before aiming.


  • If the hookshot was used successfully, it will be marked as used until the player almost reaches its target location.
  • If the hookshot is not used, the player state is switched back to the saved one.



Energy


Drain energy

Energy is drained when running up a rising floor and performing certain actions. When depleted, the player's actions are limited.


  • When aiming to use the hookshot, the energy is drained over time.
  • When running, the amount of energy that is drained depends on the floor's angle.
  • There is an instant energy loss when jumping and climb jumping.
  • The energy directly affects the player's speed.
  • If the energy is depleted, the player can't jump, aim the hookshot, run, climb or wall jump.

Restore energy


  • When climbing, the player can restore energy by pressing the action button.
  • The amount of energy that is restored depends on the climbing wall's angle.
  • When energy isn't being used, it's regenerated over time.

Much restoriation

Little restoration

Jumping


Regular jump

An impulse on the character's up-vector that can be performed while on the floor.

Wall jumping

Jump in the direction of the wall surface normal.


  • When jumping while sliding down a wall, the character performs a jump in the direction of the wall surface normal.
  • It can also be performed when running up a wall.


Climb jumping

Jump in the direction that the player is moving.


  • A timer decides if the player is allowed to jump.
  • If allowed, the friction (braking deceleration in flying mode) is decreased.
  • Velocity is added based on the movement input.
  • If there is no movement input, the player jumps out from the wall.



Camera

Camera movement


Basic movement

The camera rotates around the character on the yaw and pitch axes.


  • The camera is attached to the player via a spring arm, which will shorten the distance from the camera to the player in case an object comes between them. 


Field of view

The FOV (Field Of View) is changed depending on movement input.


  • There are different FOV's for being idle, walking and sprinting. The transitions are made smooth with lerping.


Offset camera

The camera is offset on the right vector in relation to the character depending on where the player is heading and looking.


  • When the character is moving, its rotation is compared to the camera's forward vector. The result will decide if the camera is offset to the right or left side.
  • When giving the camera input, I simply check if the input values are positive or negative to decide the offset.
  • A small threshold was added to hinder drastic changes from just nudging the input.
  • The offset values are changed over time and clamped to a set max distance.
  • To make transitions smoother, the spring arm's offset is lerped towards the calculated values.


Moving character

Moving camera

Situational camera


Climbing


  • When climbing, the camera offset also works on the up-vector.
  • The springarm's socket is offset to avoid clipping when the character is rotated


Moving character

Moving camera

Idle

When there has been no input for a while, the camera starts rotating around the player, zooms out and decreases FOV.


Wall behind the character

The camera zooms out when the player can perform a wall jump.


  • Check if the player is allowed to do a wall jump (while climbing, running up a wall or the floor is steep).
  • If a wall jump is allowed, a line trace is going from the character to straight behind it to detect if a wall is there.
  • If above criteria is met, a timer for the springarm is reset.
  • When the timer is ticking, the camera's spring arm's length is increased with lerping. When the timer has reached its target, the length is decreased to its original length.


    Screenshot 2025-02-12 150658
    Screenshot 2025-02-12 150714
    Screenshot 2025-02-12 150745

    Clean code

    Coding practices


    • Avoided nesting by using guard clause as much as possible.
    • With the use of public getters I make sure to only change a behaviour in the dedicated script.
    • CamelCase is used for consistency and a clean look.
    • Enums help keeping track of what state the player is in. One is used to control player input and the other is to decide character animations.


    Screenshot 2024-12-18 163825
    Screenshot 2025-01-15 154355
    Screenshot 2024-12-18 162540
    Screenshot 2024-12-06 154414

    Abstraction

    The project is seperated into several scripts that each have a specific task.


    • Late into working on the project I realized that it was getting difficult to navigate. My main script was, at that point, 1200 lines of code.
    • I started abstracting it and made seperate components for the camera, springarm, climbing, animation, and hookshot. The ground movement remains in the main character script.
    • I created a base class that handles all inputs and some of their base logic. The character script inherits from the base class and decides what the inputs should do.
    • With the use of data classes I cleaned up all constant variables from the h-files.
    • I would have liked to abstract the ground movement as well, but as time was running out I prioritized other things.
    • If I hadn't abstracted the project, my character's cpp-file would be approximately 2000 lines by now and the h-file 1000 lines. Instead, my character's cpp-file is around 700 lines and the h-file around 300.


      Screenshot 2024-12-18 163003
      Screenshot 2025-01-15 155157
      Screenshot 2025-01-15 154744
      Screenshot 2025-01-15 160123

      See for yourself

      Take a look at the project on GitHub. Click the button below.