Wall Running on Curved Surfaces Issue

Godot Version

v4.6.3.stable.official [7d41c59c4]

Question

I have been trying to create something similar to Titanfall 2’s wall running system but I ran into an issue involving running along curved surfaces.

I keep the character stuck to the wall by applying a force based on the inverse of the wall normal. The wall normal is obtained by a raycast going from the player towards the wall (also using the inverse of the wall normal). The current wall normal value is naturally updated to whatever wall normal is picked up by this raycast. I can’t find anything about how this is done in Titanfall, but this is what I came up with. (If anyone has any knowledge on the topic, or have an idea about how this could be better implemented, please let me know)

This method works fine… as long as the player is moving fast enough into the wall run. If the player is moving a bit slower, the force applied to keep the player stuck to the wall ends up applying a force that is at least somewhat parallel to the orthogonal vector of the wall normal–or the surface of a given side of the cylinder/curved surface–whenever the normal is updated to the normal of the following side of the curved surface. This creates a sort of rubber banding effect where the player is prevented from going over the edge of current side by the “stick force”.

Here is a video which shows this issue: Watch issue | Streamable - Don’t mind the pink raycast, it’s for the initial latch to the wall.

(If it is is still unclear with the video I can make a better one, but here is also a diagram)

I thought this was all that was happening, but sometimes I feel like I am able to break past the force, get over the edge, and still not get pulled towards the new surface… so there might be a different issue playing into this behavior, but this is what I understand for now.

If anyone has any ideas of how to fix this issue, or has any alternatives for keeping a player to a curved surface while wall running, it would be greatly appreciated.

So far, I have tried making the raycast trail behind the player (based on the inverse of the move direction) but this was unreliable. I tried removing the part of the force that goes against the move direction, but I realized that that was… all of the force, apparently. The force towards the wall is either cancelled out or pushed against the move direction as well because of how move and slide works (I think?). I tried redirecting the velocity along the wall every normal change, but I couldn’t get the player to stick to the wall reliably. I tried interpolating the normal, but nothing seemed to come from that either. I have come to think that this is probably not how it is supposed to be done? I think maybe my last idea would be to use a spherecast or a body_test_motion instead of a raycast so there is more of a chance that the player is able to fully cross the edge before the new normal is applied (the normal is obtained sort of early since the ray comes from the center of the player instead of slightly in front.

Some other info (I’ll add to this as things come to mind):

  • Player has a cylinder collider (outlandish! I know.)
  • I’m not using the raycast3D nodes outside of debugging and demonstration, otherwise I am using the direct space state.

If you need me to show some of the code, let me know. I just, uh, need to do some… housecleaning beforehand.

What if you tried smoothly interpolating the “stick force” direction, so it doesn’t immediately snap to the new surface normal direction, instead rotating smoothly over some time (a very short time, like a tenth of a second - you’d need to test it out). Maybe that would give the character body a chance to keep going and correct course smoothly instead of changing directions abruptly.

I don’t know if that would work, but maybe it’s worth trying.

I actually tried that, and it sort of worked, but it still caused me to bounce if I was moving super slowly. It also messed up redirecting around interior curves. I found a different fix, though. You can read it if you’re curious. Thanks for the suggestion!

Yup. Turns out a big part of the issue was entirely unrelated to anything aforementioned (whoops).

I was using this function I made to replicate CS:S surfing to prevent clipping on edges while wallrunning and getting stuck in walls at high speeds. This worked, but I forgot to add an is_on_wall() check before I added it to the wallrun state, so it was constantly going off of an old wall normal. This was why the bouncing was so extreme compared to the direction and magnitude of the stick force, and why I was locked to one face of the cylinder. The reason I was able to properly redirect when I got a running start was because I was fast enough to touch the following side and update the wall normal.

After fixing it, I realized that the function was actually the thing doing most of the redirecting, so I just got rid of the stick force and tried the redirecting thing again:

curr_wall_normal = %MaintainWallrun.get_collision_normal()
			
var speed = velocity.length()
velocity = velocity.slide(curr_wall_normal)
	
velocity = velocity.normalized() * speed

This works perfectly, and is probably how it is supposed to be done… if anyone else is making something like this.