Critical Importance: Finding Jump Apex
Why find the apex?
There are a few reasons all of which are critical to ensuring the best player experience and/or fidelity of the game
Player Expectations
It's critical the player can trust their expectations, especially in games with platforming or parkour movement mechanics such that they can learn how far the jump gets them, or the distance of a jump vs slide jump...etc.
If the movements aren't deterministic to reasonable accuracy players will miss judge movement actions and ultimately will lead to frustration at inconsistencies.
Level Designers
LDs need to know how far the player can move in certain situations so they can adequately build levels otherwise geometry might be too close, far, or downright impossible for the player to reach
imagine a ledge up at the player's expected max height that's the only way out of a section of the game, but players with lower framerate can't reliably hit the expected max so they literally cannot complete the game
Lesser degree but still valid: Animation fidelity
Animators may want to play some special animation, or react in some way to the player reaching the apex
Imagine the player punching the sky when they reach the apex
The list could go on to other areas as well like Audio, or VFX...etc depending on what the jump is for.
Why won't the apex be consistent?
Framerate and other factors affecting delta time.
All of the curves are read by reading values based off time, and DeltaTime controls the step size
This makes perfect sense for achieving framerate independent movement, however it does mean that at different frame rates the step size will be larger when reading from the graph, especially if there is some drop in frames mid jump.
It's small, but you can see the inconsistencies with the highest achieved apex

To illustrate it graphically, here we can see the jump curve with the apex in Green. The Yellow x's represent values of the curve sampled at each step size. Lets say a frame drop (Red) happens in which case the sample value was before, and after the Apex but never actually reaching the Apex

How to find the Jump Apex
Inspiration comes from Back Face Culling
We can actually use a similar method that rendering uses for back face culling in which we take the cross product of two sample vectors and compare the normal to determine directionality of the face.
In back face culling the computed is dotted against the viewing direction to determine if the face is pointing towards or away from the camera
Here instead of using the viewing direction, we're going to take 2 samples and compare the normals and whenever the normals point in different directions, apex has been found.
Finding normal along the curve given two points
You can think of the float curves just like vectors in 3D space.
Given a sample point on the graph P1 we can get the next sample point by just adding a step size to achieve P2.
P2-P1 gives us a vector in the direction of the "next value on the graph".
Time is constant in that it only increases, so you can imagine a constant vector pointing in the direction of time.
Taking the cross product of those two vectors yields a normal following right hand rule (even though Unreal is a left handed coordinate system) such that

The great thing is depending on the sample points, the Normal will change depending on if we're going up or down the curve by the nature of the right hand rule

Comparing Normals Iteratively to find where they differ
Start by taking 3 points on the curve, a constant step size, and creating vectors P2-P1, P3-P2 and finding the Normals then comparing them.
Continue as long as the Normals are in the same direction


Reduce step size and repeat
However once the Normals differ, then we know the Apex is somewhere between P1 & P3

Now we reduce the step size and rerun the computation.
The more times you run the computation with smaller and smaller step sizes (I chose to half the stepsize each time) the more accurate you'll be.
Since the curves are constant this algorithm can (and should) be run entirely outside of runtime and value for the apex stored in some asset data rather than having to do it at runtime since the curves cannot change at runtime.
Apex is midpoint between final start and end
Finally the apex will be at the midpoint between points P1 & P3
Notable mention
There are a few gatchas to watch out for which is why this function returns a bool as well as an out param, if the function returns false that means we have some degenerate case where no apex exists
infinite curves (i.e. straight lines that don't have an apex)
Code
Testing
I tested different framerates (120, 60, 30, 10) and they all reach the exact apex height 100% of the time.
ex: 226.78 is the apex height for all framerates

Last updated