Godot Version
4.3
Question
I converted my code to use a custom time scale variable instead of the built-in Engine.time_scale
to avoid issues with built-in time scale affecting everything when I need it to just affect the physics, but move_and_slide()
uses the built-in delta. So, I tried converting to use move_and_collide()
instead:
# This replicates move_and_slide() just with move_and_collide() instead.
var collision = move_and_collide(velocity * delta)
if collision:
velocity = velocity.slide(collision.get_normal())
My problem is that if I want to use is_on_floor()
than I have to use move_and_slide()
, but can’t use a custom delta (and by extension a custom time scale). If I use move_and_collide()
instead than I can use a custom delta but can’t use is_on_floor()
anymore. I did find a solution, but it’s not very good, and extremely buggy. (real delta is delta unaffected by time scale)
if real_delta == 0:
return # Avoid dividing by zero.
var real_delta_reciprocal: float = 1 / real_delta
# Undo the delta calculations in move_and_slide(),
# so that we can use our own delta calculations!
velocity *= real_delta_reciprocal * delta
move_and_slide()
velocity = get_real_velocity() # And then get back the proper velocity.
Is there a way to update the is_on_floor()
function without the use of move_and_slide()
?
I have decided to try to use move_and_collide()
combined with my own custom is_on_floor()
function.
var collision: KinematicCollision2D = move_and_collide(velocity * delta)
if collision:
velocity = velocity.slide(collision.get_normal())
_on_floor = collision.get_angle(Vector2.UP) <= floor_max_angle
_on_ceiling = collision.get_angle(Vector2.DOWN) <= floor_max_angle
_on_wall = collision.get_angle(Vector2.RIGHT) <= floor_max_angle or \
collision.get_angle(Vector2.LEFT) <= floor_max_angle
else:
_on_floor = false
_on_ceiling = false
_on_wall = false
However, now my problem is that I can only get one KinematicCollision2D
, even if there were multiple collisions! Meaning that if I rub up against a wall while on the floor, it’ll only detect either the floor or the wall, but not both. This leads to some confusion on whether the player is on the floor or the wall. The best solution to this would be to loop over all the collisions, but we only have one collision to work with. If there is a way to get an array of collisions, that should solve the problem, granted there aren’t any other issues.
func _physics_process(delta):
var collision = move_and_collide(velocity * delta)
if collision:
if collision.normal.dot(Vector2.UP) > 0.7:
print("is on floor")
My problem was never detecting which collision was the floor, but that it would only detect one collision. So, if you were touching a floor and a wall, it would count as touching only the floor OR the wall.
I have figured out how to call move_and_collide()
multiple times! From what I can tell this should fix the issue:
func _handle_collision(delta: float) -> void:
_on_floor = false
_on_ceiling = false
_on_wall = false
var collision := move_and_collide(velocity * delta)
while collision != null:
velocity = velocity.slide(collision.get_normal())
if collision.get_angle(Vector2.UP) <= floor_max_angle:
_on_floor = true
if collision.get_angle(Vector2.DOWN) <= floor_max_angle:
_on_ceiling = true
if collision.get_angle(Vector2.RIGHT) <= floor_max_angle or \
collision.get_angle(Vector2.LEFT) <= floor_max_angle:
_on_wall = true
collision = move_and_collide(velocity * delta)
Also, if you have issues with the custom is_on_floor()
constantly switching between true and false each frame, it is likely this is caused by only applying gravity when airborne. So, when you are considered on the ground, you won’t recollide with the ground the next frame. Just apply gravity every frame, and you should be fine.