I’m not sure what you mean by that exactly, I don’t touch the raycast in any way in code (other than checking if it’s colliding), it just rotates with the player, is there something I should be doing with the raycast?
Yes. Raycast will collide on the next physics tick. So if you re-position the player and the raycast is following, the result of casting from that position will really be available no sooner than the next frame/tick. So either call RayCast3D::force_raycast_update() immediately prior to hit query or do ray hit tests directly on physics state as described here: Ray-casting — Godot Engine (4.5) documentation in English
I put force_raycast_update() at the beginning of my physics process before any movement code but unfortunately that doesn’t seem to help. I have a feeling that this issue is more specific to the way I implemented my movement system with how the movement and rotation is smoothed using lerps, unless I’m just not getting something in which case I apologize. ![]()
Hard to tell without seeing the code.
This is the code used for walking, it is a bit messy and not fully refined yet, but basically the movement and rotation get smoothed out using lerps and delta, as long as the raycasts are hitting. If the raycasts are not hitting the player movement is stopped immediately as to not let them fall off.
if(state==PlayerStates.WALK):
if (direction):
if(step_raycast_left.is_colliding() && step_raycast_right.is_colliding()):
velocity.x = lerpf(velocity.x, direction.x * current_speed, ACCELERATION * delta)
velocity.z = lerpf(velocity.z, direction.z * current_speed, ACCELERATION * delta)
mesh_root.global_rotation.y = lerp_angle(mesh_root.global_rotation.y,atan2(velocity.x,velocity.z), ROTATION_RATE * delta)
else:
velocity.x = 0
velocity.z = 0
mesh_root.global_rotation.y = lerp_angle(mesh_root.global_rotation.y,atan2(direction.x,direction.z), ROTATION_RATE * delta)
elif(step_raycast_left.is_colliding() && step_raycast_right.is_colliding()):
velocity.x = lerpf(velocity.x,0,DECCELERATION * delta)
velocity.z = lerpf(velocity.z,0,DECCELERATION * delta)
else:
velocity.x = 0
velocity.z = 0
Edit: Exact values of the constants if you’re wondering, in this case current_speed is WALK_SPEED:
const WALK_SPEED:float = 5.5
const DECCELERATION: float = 14.5
const ACCELERATION: float = 6.45
const ROTATION_RATE: float = 18.5
Well rotation interpolation should stop as well. It affects the position of raycasts.
Stopped the rotation interpolation when raycast isn’t hitting, issue still persists sadly. Seems like the issue is while the raycast is still hitting, the movement or rotation interpolation doesn’t stop quickly enough and the player character goes just a bit too much over the edge, as far as I can tell anyway.
Your code lerps if nothing is pressed regardless of raycast state. Velocity should always be 0 if raycasts are not hitting.
Comment out all lerping/smoothing and first make the ledge stop work properly with “hard” version of movement. When that is sorted, add the smoothing back. Or just separate rayhit test, put it at the end of velocity logic and override any previously set velocity if raycasts are not hitting.
The lerps only occur if both raycasts are hitting, otherwise velocity is immediately set to 0.
That being said, I removed all the lerps and I can still recreate this issue, walking diagonally lets the player move just a bit before the raycast stops hitting, and that lets the player walk off the ledge too much. Perhaps I need to reposition my raycasts?
Yes, tweak the raycasts until you get something that feels right.
Btw, you should never use operator == with floating point numbers.
I don’t believe I used it anywhere with a float but correct me if I’m overseeing something.
I tried tweaking the raycasts a few different ways, putting them inside and outside the player collider all around, but so far I can always still have the player walk off the ledge too much. In the end I might just go with using colliders around ledges like you mentioned previously, this is starting to drive me a bit crazy. ![]()
if(direction)
direction is a floating point vector. Conversion to boolean requires zero equality testing under the hood. It’s most likely done using operator ==
I’ll keep that in mind.
Here’s a quick test. It should keep the player inside the collider and not get it stuck. It’s a quick and dirty one raycast setup. But it does the basic job. Raycast position is automatically updated to be in front of the player for as much as calculated velocity would push it in the next frame.
Automatic sliding on ledges would feel much better imo, so it’s probably worth using colliders. Depending on your level design collider creation could be automated on startup or in editor.
It’d still be possible to implement approximate sliding using raycasts. By setting up two concentric circular raycast arrays it’s possible to estimate the ledge direction and detect corners. Once you know the direction, simply project the velocity to that direction if near the edge. A bit of math required but probably worth it as well if you want to fully automate sliding without using any collider setup.
extends CharacterBody3D
func _input(e):
# cam orbit around origin
if e is InputEventMouseMotion and e.button_mask & MOUSE_BUTTON_MASK_RIGHT:
var p = %cam.to_local(Vector3.ZERO)
%cam.position = Vector3.ZERO
%cam.rotate(Vector3.UP, -e.relative.x * 0.02)
%cam.rotate(%cam.global_basis.x, -e.relative.y * 0.02)
%cam.translate(-p)
func _physics_process(dt):
# player movement
var input = Vector2(Input.get_axis("ui_left", "ui_right"), Input.get_axis("ui_up", "ui_down"))
var move_dir = (%cam.global_basis * Vector3(input.x, 0.0, input.y)).slide(Vector3.UP).normalized()
velocity = move_dir * 2.0
# raycast and constrain
%rcast.global_position = global_position + velocity * dt + Vector3(0.0, 0.5, 0.0)
%rcast.force_update_transform()
%rcast.force_raycast_update()
if not %rcast.is_colliding():
velocity = Vector3.ZERO
move_and_slide()
Adjusted this for my code and this seems to work pretty flawlessly from my testing so far, even with movement interpolation! Thanks! ![]()
As for the sliding, I’m not quite sure, I want the player to have precise control for platforming, so I don’t necessarily want them to slide along ledges since it might break a precise jump, but if it does end up being annoying I can always switch to colliders.
wall thats it its simple

