Specialization

Third-Person Parkour

Introduction

I have created a third person parkour system in Unreal Engine and C++. It allows the player to move at different speeds, jump in different ways and climb in any direction, all while the camera keeps the target in focus. In a short while there will be an obstacle course available for download in the style of Getting Over It, where you can try out all these features on your own.



Features / Overview

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.


Situational camera


Climbing

When climbing, the camera offset also works on the up-vector.


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.


    Character

    Moving on floor


    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.


    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.

    Shooting a line trace in the direction the player is aiming to find something to hook to.


    • 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.



    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.



    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.


    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 this script 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