Godot: KinematicBody2D is_on_floor() flickering when moving by low values

Created on 31 Jan 2018  路  23Comments  路  Source: godotengine/godot

Godot version:
3.0 stable, non-mono build

OS/device including version:
Windows 10 Home, version 1709 (Fall Update)

Issue description:
I'm not actually sure how to explain this issue that well, so forgive me if this explanation is poor, but you might understand the issue better if you follow the Steps to reproduce below. It's weird for me to explain, so again, forgive me.

What's expected: KinematicBody2D's is_on_floor() should be true under either of the following logical conditions:

  1. If the player is in the air, and their last move_and_slide would have put them in a wall or against a wall, no matter the speed at which the player would have hit it.
  2. If the player is already considered on the floor, and does not move away from what is considered the wall.

What happened: KinematicBody2D's is_on_floor() seems to flicker in the example when moving towards the floor, if you're moving towards it at very low speeds. It also flickers if you outright don't move towards the floor at all while on the floor (because we're moving 0 pixels down, then 20 down, then 0, then 20, etc.).

Whilst talking to HeartBeast about this, he suggested that is_on_floor() could use test_move() with the negative of floor_normal set in move_and_slide(). If we get a collision then, we would be considered on the floor. Perhaps that could be a solution, or the start of a solution?

Steps to reproduce:

  1. Open the attached project.
  2. Run the project. Use the up arrow key to jump, and left and right to move. In the output should be a spam of Not on floor when in the air, and On Floor when on the floor (the random number is so you know it's still scrolling by).
  3. Stop the game, and open Player.gd.
  4. Fiddle around with line 22 (motion.y = GRAVITY), setting it to different values and rerun the game. The lower the number, the worse the issue will get, with some numbers (~10) causing one-frame windows of not being on the ground when hitting a wall, some lower numbers (~7) causing the flicker while simply moving left and right, and many low numbers (<5) outright causing flickering of is_on_floor(). 0 also causes this issue.

Minimal reproduction project:
HB_Mini_Platformer.zip (Based off of HeartBeast's platformer tutorial!)

bug physics

Most helpful comment

Hello,
it's still bugged using the GDScript version 3.1 (flathub release) Fedora.
Even tweaking the safe margin don't solve the problem, the "flickering" appear only in one direction, maybe it's because of the Collision Poly not perfectly flat?
Demo project:
Debug.zip

All 23 comments

I can also confirm that this is happening. I just submitted an issue for this as well https://github.com/godotengine/godot/issues/16268

You need to tweak the safe_margin value of the KinematicBody2D to adjust for lower GRAVITY values.

@mrcdk That doesn't explain the flickering... Did you read the issue?

@mrcdk adjusting the safe_margin and moving it to the lowest value, 0.0001, does not fix the issue.

@divmgl the cause of your issue is different than the cause of this issue.

https://streamable.com/9tsq5

@mrcdk Still doesn't explain the flickering... if I don't move up or down, and I'm already in the safe margin, (or I move down while already in the safe margin, thus not moving at all) shouldn't is_on_floor still stay true? I'm not sure what that video is trying to prove...

No, is_on_floor() won't stay true if you don't change the safe_margin property to a lower value.

Kinematic bodies will always try to stay in a state of no collision with anything so every physics frame they will separate from any object that are colliding with them. This is why there is a safe_margin property to give them some room to check if they are colliding because if not it wouldn't finish. Because the gravity is low the safe margin has to also be also low because the resulting avoiding movement from the collision will be smaller.

The video shows that when I change the gravity is_on_floor() starts flickering and it only stops when I set a lower safe_margin and how when the gravity is higher I can safely make the safe_margin higher too.

That sounds like a bug to me, though. Not only is this "trying to stay in a state of no collision" thing that you're explaining seemingly not documented on the KinematicBody2D docs, why would lower safe margins not move the object away enough to no longer be considered colliding? I get what you're saying now, but all I'm seeing is a bug, in some form, if not two bugs.

Okay, then why isn't that explained on this page, where it would be even more relevant? Not to mention, you seem to have ignored my other question. Why do lower safe margins not move the object away enough, so that they'd no longer be considered colliding?

I'm sorry to be a complainer, but my patience is wearing a bit thin with some of these answers...

EDIT: Reading that issue currently.

I edited my answer with another issue where it was better explained https://github.com/godotengine/godot/issues/15048

Yeah, I noticed. I read it through. I think I agree with the assessment that issue has, that the default for the safe margin is too big. I suppose this issue would also serve as a reminder to make sure our docs are accurate in all the right places.

(Side note: Sorry if I've been seeming antagonistic. That's not my intention. Like in that issue, I was perceiving your solution as a workaround rather than an acknowledgement of what seemed like an obvious bug, which was bothering me. I didn't want to seem like I was trying to pin you as a bbad guy.)

Putting this as a separate message so it's seen. I'm hoping this issue doesn't end up mostly forgotten like the other issue. From what I can tell now, there's at minimum a docs issue, and an issue with the default safe margin. I'm hoping those get fixed soon.

I'll also take this moment to mention that even with a lower default safe margin, the whole avoiding collisions thing still seems to be unintuitive, like it could easily trip up newcomers, even if it is mentioned in the tutorials. I'd like at least some discussion on that, if that's not too much to ask.

I was doing the same tutorial and ran into the same problem. It gets worse when you try to use a Camera2D and turn on the smoothing, it flickers like there's no tomorrow.

Did you find a solution to this @LikeLakers2 ?

@klerpi Well like they said, turn safe margin on the character down to its minimum and. But it's not obvious that a safe margin change is needed, hence me keeping this issue open.

I think that this is a flaw within the engine. A body that, after a move_and_slide() call with a linear_velocity having magnitude > 0, stops before having completed its route or doesn't move at all should update its state of is_on_floor(), is_on_wall() etc.

move_and_slide with low velocities (<150) doesn't even trigger get_slide_count() to return >0 if a collision has clearly occurred.
Decreasing the safe margin is simply not effective and should not be the solution in the first place as the whole system wouldn't be consistent at different velocities.

At this point to me this is a critical bug.

I don't mean to rush anybody, but I'm curious if anyone's planning to take on this issue. Or is the issue already fixed?

yeap, Godot 3.1 have these issue too

I think I fixed it, but this needs more testing. Please give it a try.

@reduz My apologies for the late response, but I checked it out. The problem seems partially fixed? You've fixed the one-frame flickers when the character hits a wall, at least.

What I've noticed is that I have to be moving into the safe margin every time I move, or else the flickering of is_on_floor() happens while I'm moving left and right.

To explain what I mean: I've found that if you edit Line 22 of Player.gd (not the GRAVITY constant, but actually line 22), and replace "GRAVITY" on that line with a number below ($Player.safe_margin * 100) (such as 0 or 1), the flickering of is_on_floor() occurs, but only while moving left or right.

I'm honestly not quite sure if you would consider that an error in my code, or if it's an error in Godot's physics. I'll leave that call up to you.

Hello,
it's still bugged using the GDScript version 3.1 (flathub release) Fedora.
Even tweaking the safe margin don't solve the problem, the "flickering" appear only in one direction, maybe it's because of the Collision Poly not perfectly flat?
Demo project:
Debug.zip

Good day,

I confirm that the bug is still present in Godot v3.2.3.stable.official as reported in the comment of the ghost account. The is_on_floor() method is indeed broken in his Debug project. When printing the motion.y (Vector2) it is always 0 but the is_on_floor() returns false.

I had a slightly different bug: when moving my player left and right on a tile set, the motion.y (Vector2) was sometimes jumping to 30 or returning a small negative value. I simply reshaped the Capsule CollisionShape2D and it got fixed. I think that somehow the pointy end of the shape in contact with the tiles was too pointy and was "getting in the gap" between 2 tiles.

image

Hope it helps!

Was this page helpful?
2 / 5 - 1 ratings