Climbing Detection
Detection Strategies
The simplest way would be to fire a raycast from the players forward vector and look for surface collision that way, but this has some immediate shortcomings when thinking about walls with overhangs and/or convex or concave corners and trying to know if you can climb in any cardinal direction.

A workaround could be firing multiple raycasts from different positions on the body perhaps low, medium, and high positions, or adding additional ones offset by the input direction.
What we really want is "an area of detection around the player" which can be solved with multiple raycasts, but there is a much easier way to accomplish this by using a Shape Sweep.
Instead of individual rays, SweepMultiByChannel allows us to pass in a shape that can be used for uniform collision checks which just makes this problem much easier to solve.
Surface Detection
We'll need to override BeginPlay() for some setup, and TickComponent() so we can sweep for wall hits. We need a few properties which will define our sweep shape, as well as a way to store the collision hits, and query params for the sweep.
.h
The sweep should happen every Tick, and we don't want the sweep to trigger for our own Character
.cpp
I've been saying Sweep but you might think we don't actually need the shape to sweep anywhere, we just want to detect collisions in one static location.
However due to a bug if the Start and End position of SweepMultiByChannel() is the same I've noticed detection doesn't work properly, so instead we will sweep, just very closely out in front of us.
.cpp

Evaluating Surfaces
First let's create a dedicated function for determining if we can start climbing given any of the wall hits that we'll come back to this later
.h
.cpp
In games like Zelda BOTW and Genshin when the character runs into a wall they automatically start climbing if:
Horizontal check passes - the angle of intent is within some threshold (i.e. are we running into the wall head on or is our shoulder just scraping it)
Vertical check passes - the wall's angle is within a Climbable Threshold (i.e. not too steep, and not too shallow)
Horizontal Check - Angle of Intent
This check is very straight forward. We just want to make sure that the angle between the characters Forward Vector and the surface is less than some angle threshold
Is the player reasonably intending to run into the wall, i.e. intending to climb or not
.h
.cpp
Lastly update our CanStartClimbing() function to check angle of intent

Vertical Check - Surface Angle
There are a few requirements for a surface to be climbable. Let's create a function to handle the vertical checking.
.h
Ceiling and Floor check
The easiest check is just to make sure that our surface isn't perpendicular with what our game considers flat.
We do this by ignoring the Up component of the Normal, i.e. the Z component, in the 2D vector and compare it with the 3D vector.

Note, this only works because we assume the Up direction in our game is FVector::UpVector
If your game has unique gravity then instead project the 2D vector onto a place who's normal is opposite of gravity
We'll add the check into the CanStartClimbing() function and implement IsClimbableSurface
.cpp

Surface Height Check
There is another case we need to account for which is whether the surface is high enough to actually climb, as right now the game will indicate low ledges as climbable which wouldn't make any sense.

It seems reasonable that we should only be able to climb surfaces that are as tall (or taller) than our character, so lets add in a height check using the character's Eye Height as a reference for our "vertical threshold".
It would make sense if the length of the ray was equal to our collision edge, otherwise if we fire too far away we would incorrectly evaluate edges
Add a new function to handle the Eye Height check, and update IsClimbableSurface to use it
.h
.cpp

Resizing Eye Height length based off steepness
However using a static length raycast only works with perpendicular and steep surfaces. It falls short (pun intended) with longer shallow surfaces.

To solve this, we can make the length of the Eye Height raycast dependent on the surface angle meaning the more shallow the surface, the longer the raycast.
Update IsClimbableSurface() to increase where we consider our CollisionEdge by a steepness multiplier which uses the cosine of the surface angle as our scaling factor since it's a value between [0,1]

.cpp
Good, now we can properly detect all angles, except there is one last issue.
It's possible, based off how high the capsule collider is off the ground, and what your game considers its Max Walkable Angle for us to incorrectly evaluate a walkable surface as climbable

Luckily the CharacterMovementComponent has a built in accessor for getting the Max Walkable Angle via GetWalkableFloorAngle(). Let's add that as the last check
.cpp

Setting Custom Movement Mode
Now we're all setup to determine if we can climb something.
To actually perform the climb we need to tell our custom CharacterMovementComponent to SetMovementMode do a new custom mode.
CharacterMovementComponent::SetMovementMode takes an additional parameter when specifying MOVE_Custom so the developer can implement numerous amounts of custom modes.
We only have 1 custom mode, Climbing, but if we needed we could add them to our new enum
Then when the movement updates we can check if we CanStartClimbing() in which case we can set our new custom climbing mode
.h
.cpp
Now whenever we are in range of a climbable surface the movement mode will be set to Climbing.
Last updated