godot: isOnFloor is intermittently false when moving against the floor normal
Godot version: 3.0, C# with Mono
OS/device including version: Windows 10, GTX 1080
Issue description:
KinematicBody2D.IsOnFloor() is intermittently false when moving against the floor normal
Steps to reproduce:
- Press the Spacebar. The character will refuse to jump because he’s not on the floor.
Minimal reproduction project:
I’ve already set the FLOOR_NORMAL when using MoveAndSlide, so there’s no reason as to why this should be happening.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Comments: 37 (15 by maintainers)
I recently ran into this issue as well. Seems like is_on_floor() doesn’t work correctly unless a collision occurs with the floor. So I had to apply a
velocity.y = 5to get it to return true.Since I had this problem recently (and this issue seems to get some traffic,) I figured I’d spend a few days looking into it. The bad news is, I don’t see an easy fix in the engine. The good news is, it’s very easy to fix in your code.
The short solution is simple, per @justinluck. Instead of having a movement loop like this:
Use a loop like this:
This will make it so you’ll always have some downwards velocity on the ground, causing a collision with the floor and ensuring
is_on_floor()staysTrue.That’s enough to fix the bug. For the curious, I’ve attached a longer explanation below.
The key to this bug is that objects start every motion by pushing themselves out of collision distance of all other objects. However, this only happens when the first object is moving. That object is then moved along its intended path as much as possible before it actually overlaps with another object.
Say you’re using the first loop above (the bad one). On the first frame of your game,
is_on_floor()is false by default, so gravity will be applied, pushing you as close to the floor as it can (well within collision distance). As long as you don’t start moving, you won’t be kicked out, sois_on_floor()will remain true.Once you start moving, though, the engine snaps awake and kicks you out of the floor. Here’s the kicker: if your motion is perfectly horizontal, well, you won’t move downwards. This leaves you just out of collision distance with the floor, causing
is_on_floor()to returnFalsefor that frame. Seeing this, your script quite naturally adds gravity for the next frame, giving you a downward velocity and pushing you close to the floor again, sois_on_floor()returnsTrue. And now that you’re on the floor, the script doesn’t add gravity, so in the next frame you get kicked out and move perfectly horizontally again, causingis_on_floor()to remainFalse… and so on.I’ve honestly got no idea how this would be fixed (and it probably shouldn’t be). One idea is to only kick a collider out of a body if it isn’t moving parallel to said body . I’ve tested this, and although it removes the issue in a vacuum, it comes back when you’re colliding with another object (i.e. running into a wall.)
You have to call ModeAndSlide() BEFORE IsOnFloor() or IsOnWall().
What happens is probably the following: the MoveAndSlide() functions set the internal attribute isOnFloor. Since you use MoveAndSlide() at the end of your function, the values are sometimes still usable for the next _process() call. But sometimes, a physics frame goes between two _process() calls which resets the values to 0.