Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

isOnFloor is intermittently false when moving against the floor normal #16268

Closed
divmgl opened this issue Feb 1, 2018 · 37 comments
Closed

isOnFloor is intermittently false when moving against the floor normal #16268

divmgl opened this issue Feb 1, 2018 · 37 comments

Comments

@divmgl
Copy link

divmgl commented Feb 1, 2018

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:

peridot.zip

I've already set the FLOOR_NORMAL when using MoveAndSlide, so there's no reason as to why this should be happening.

@volzhs
Copy link
Contributor

volzhs commented Feb 1, 2018

IsOnFloor() or IsOnWall() is set only after MoveAndSlide()
those IsOnSomething() will be reset after function ends.
try to call it again after calling MoveAndSlide.

@divmgl divmgl changed the title isOnFloor is always false isOnFloor is intermittently false when moving against the floor normal Feb 1, 2018
@divmgl
Copy link
Author

divmgl commented Feb 1, 2018

Thanks @volzhs I'll try that now

Edit: Unfortunately the problem is still happening

@LikeLakers2
Copy link
Contributor

LikeLakers2 commented Feb 1, 2018

I also made an issue about this earlier in the day, see #16250.

EDIT: That timing. You're good.

@divmgl
Copy link
Author

divmgl commented Feb 1, 2018

@LikeLakers2 are you using GDScript? This is happening to me on C#

@LikeLakers2
Copy link
Contributor

@divmgl Yes I am. My example project is all GDscript.

@divmgl
Copy link
Author

divmgl commented Feb 1, 2018

intermittent-is-on-floor

@volzhs
Copy link
Contributor

volzhs commented Feb 1, 2018

I not sure how can this can be solved with PhysicsServer.
but probably you can get expected behavior by doing this.
I'm using only GDscript so... it might be modified.

// Vertical jumping logic
if (isOnFloor) {
    Velocity.y = 0.1;
    isJumping = false;
    isDoubleJumping = false;
} else {
    Velocity.y += delta * GRAVITY;
    GD.Print("not on floor, gravity: " + Velocity.y);
}

@volzhs
Copy link
Contributor

volzhs commented Feb 1, 2018

so... @divmgl your original issue is solved, right?
having another issue (same with #16250) though.

@divmgl
Copy link
Author

divmgl commented Feb 1, 2018

@volzhs no this is not solved, I was simply misunderstanding the problem

@ghost ghost added this to the 3.1 milestone Feb 1, 2018
@eon-s
Copy link
Contributor

eon-s commented Feb 1, 2018

I remember an issue like this, is your safe margin the default 0.08?
If it is, lower it according of your scale and speeds and test again.

@divmgl
Copy link
Author

divmgl commented Feb 1, 2018

Hey @eon-s thanks for your response. I've tweaked the safe margin and I'm still getting this issue.

@groud
Copy link
Member

groud commented Feb 1, 2018

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.

@divmgl
Copy link
Author

divmgl commented Feb 1, 2018

@groud I've updated the code and it's still happening.

@groud
Copy link
Member

groud commented Feb 1, 2018

@divmgl Can you give us the new code ?

@divmgl
Copy link
Author

divmgl commented Feb 1, 2018

peridot.zip

I've removed the content folder so you may not be able to run it but it's essentially the same thing just different code. It's still happening.

@LikeLakers2
Copy link
Contributor

@groud I don't think this is divmgl's fault, as my example project (over at #16250) also has this issue, but it only has a _fixed_process and nothing else.

@groud
Copy link
Member

groud commented Feb 1, 2018

Instead of:

if (isOnFloor) {
    Velocity.y = 0.1;
    isJumping = false;
    isDoubleJumping = false;
} else {
    Velocity.y += delta * GRAVITY;
    GD.Print("not on floor, gravity: " + Velocity.y);
}

Could you try this ? :

Velocity.y += delta * GRAVITY;
if (isOnFloor) {
    isJumping = false;
    isDoubleJumping = false;
} else {
    GD.Print("not on floor, gravity: " + Velocity.y);
}

@volzhs
Copy link
Contributor

volzhs commented Feb 1, 2018

@groud I'd like to propose that at the first time too. :)
and I'm doing it that way with my personal project.

@mrcdk
Copy link
Contributor

mrcdk commented Feb 1, 2018

The movement of any PhysicBody/PhysicsBody2D has to be done in the _physics_process() function (_PhysicsProcess() in C#) because _process() can be called multiple times between physics process ticks.

@groud
Copy link
Member

groud commented Feb 1, 2018

The movement of any PhysicBody/PhysicsBody2D has to be done in the _physics_process() function (_PhysicsProcess() in C#) because _process() can be called multiple times between physics process ticks.

No, it's needed only if you need synchronisation with the physics engine. Thus, with a KinematicBody it's not necessary.

@mrcdk
Copy link
Contributor

mrcdk commented Feb 1, 2018

It is necessary. KinematicBody internally needs to be synchronized with the physics engine.
This is move_and_collide() code https://github.com/godotengine/godot/blob/master/scene/2d/physics_body_2d.cpp#L993-L1015 which will get called by move_and_slide() https://github.com/godotengine/godot/blob/master/scene/2d/physics_body_2d.cpp#L1032

Notice that move_and_collide() uses the global_transform of the object to test the movement with the physics engine and then updates it with the resulting collision information. So, if you call 2 times move_and_slide() in one physics frame the first time the collision will be correct and the body will be moved accordingly to that collsion but the second time it won't find any collision because it was already resolved in the first call. So the first time is_on_floor() is true but the second time is_on_floor() is false. Which is the problem this issue is reporting because it's using _process() and not _physics_process()

@divmgl
Copy link
Author

divmgl commented Feb 1, 2018

Velocity.y = 0 seemed to be problem. Just to make sure there are no future problems, I moved the MoveAndSlide call to PhysicsProcess and the rest of the velocity altering code now lives in Process.

I changed Velocity.y = 10 and lowered the safe margin to 0.001 and the issue is now solved.

@nobreconfrade
Copy link

nobreconfrade commented Apr 24, 2019

I'm having a problem with it as well. It look like my is_on_floor() is returning false even if no jump was made (motion.y = -VALUE).

The code is here.

@justinluk
Copy link

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 = 5 to get it to return true.

@Jendoliver
Copy link

@justinluk You are the man, saving the day

@JonathanGrant92
Copy link

JonathanGrant92 commented Jan 24, 2020

My working hypothesis on this is that if move_and_slide() and is_on_floor() are called when the latter would return null, then you will have unexpected behavior using that returned value in control flow. The is_on_floor() function may return null if called out of sync with the physics engine, per previous comments.
Instead, try:

if Input.is_action_pressed("ui_up")
  velocity.y = jumpSpeed
  isInFlight = true

move_and_slide(Vector2(velocity.x, velocity.y), Vector2(0,-1))

if is_on_floor:
  isInFlight = false

This works for me, but isInFlight must have greater scope than the method it's written in, so it's data is remembered during the next _process() call. It's too early for me to tell if this solution works in all cases, hence my hypothesis. Another advantage of my approach is that I avoid introducing unexpected data into my script because I don't use an external function's data for anything except validation. I don't trust functions I don't write myself. :P

@divmgl
Copy link
Author

divmgl commented Jan 24, 2020

It’s impressive that two years after the fact this is still a problem. I’ve since moved on from Godot.

I don't trust functions I don't write myself. :P

While in general I’d agree with this, the point of a solution like Godot is that you can focus on the product and less on the boilerplate code.

@theteleforce
Copy link

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:

if is_on_floor():
	if Input.is_action_just_pressed("ui_up"):
		motion.y = -JUMP_SPEED
	else:
		motion.y = 0 
else:
	motion.y += GRAVITY

motion = move_and_slide(motion, UP)

Use a loop like this:

motion.y += GRAVITY
if Input.is_action_just_pressed("ui_up"):
		motion.y = -JUMP_SPEED

motion = move_and_slide(motion, UP)

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() stays True.

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, so is_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 return False for 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, so is_on_floor() returns True. 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, causing is_on_floor() to remain False... 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.)

@Bytenex
Copy link

Bytenex commented Feb 15, 2020

Hey there,

I've run into this Problem just today and found an easy solution.

The Problem

...is a physical one. I guess you aren't implementing force and inertia of mass.
That basically means that if you are standing on the ground, there is still gravity applied to you. You aren't actually moving because your muscles can counter the force, but you are still pulled to the ground.

is_on_floor() is actually calculated with real gravity. So if your character isn't falling because of movement.y = 0 then you aren't touching the ground. You are ε0 (the tiniest little bit) above and floating, because move_and_slide() can't move your character to a postion where two hitboxes collide. Instead it sets you right above.

The Fixes

Hard way
Implement force and inertia of mass so gravity is working correctly and the movement.y is calculated by velocity, applied forces and inertia of mass.

Easy way
Setting movement.y to a minimal falling rate (e.g. -0.1) let's you mock the applied gravity/force.

if is_on_floor():  
    movement.y = -0.1
else:
    movement.y -= GRAVITY * delta

@miklaskarjalainen
Copy link

miklaskarjalainen commented Feb 20, 2020

EASY FIX:
always apply gravity, DONT DO THIS

if grounded:
motion.y -= 10

@Bytenex
Copy link

Bytenex commented Feb 21, 2020

EASY FIX:
always apply gravity, DONT DO THIS

if grounded:
motion.y -= 10

If you implement gravity your character is actually at movement.y ~= -0.16.

Setting your movement.y to something high like 10 will make your character drop instantaneously when loosing ground without jumping. Your initial speed (V_0) has to be around 0 or your calculations will be incorrect.

y = V_0 - g * t

The real question is: What is actually needed?

  • If you want realistic falling behaviour then you have to implement gravity and btw calculate y and x of your fall, because in real life you can't change direction while jumping/falling

  • If an easy solution is needed because you don't care about realism (and that's okay, depends on your game), then applying a modifier of -0.1 to every movement. y is a valuable solution.

@JohnJKerr
Copy link

Posting here after encountering exactly the same issue in Godot 3.2.2 (C#/mono) and finding this thread. The fix for me was to replace my existing motion vector with the output vector from MoveAndSlide / move_and_slide. So, rather than:

var motion = Vector2.Zero;
// manipulate motion - including resetting motion.y to 0 when hitting the floor
MoveAndSlide(motion, Vector2.Up);

Instead:

var motion = Vector2.Zero;
// manipulate motion - don't set motion.y to 0, MoveAndSlide will handle floor collision
motion = MoveAndSlide(motion, Vector2.Up);

@ezequielleonzybert
Copy link

If you set the velocity.y to be 0 when isOnFloor, then change it to 1 or something positive, in that way it will detect the collision.

@MrAlienBoi
Copy link

Nope. Up till now, version 3.2.3 stable, still having the same issues. After searching up the entire web for a solution, none. I'm using this function on 3d so im guessing something in code has a problem with it. My animations glitches when using the function. Results alternate between true and false 10 times when colliding. Definitely a system bug.

@MrAlienBoi
Copy link

Being new to github and all, i'm not even sure if they closed the issue and left it. How am I to know whether they are actually trying to fix this?

@Calinou
Copy link
Member

Calinou commented Apr 16, 2021

Nope. Up till now, version 3.2.3 stable, still having the same issues. After searching up the entire web for a solution, none. I'm using this function on 3d so im guessing something in code has a problem with it. My animations glitches when using the function. Results alternate between true and false 10 times when colliding. Definitely a system bug.

Try upgrading to 3.3rc9 and see if you can still reproduce the issue there. (Make a backup of your project before upgrading.)

Also try switching the 3D physics engine GodotPhysics in the Project Settings.

Being new to github and all, i'm not even sure if they closed the issue and left it. How am I to know whether they are actually trying to fix this?

This issue was closed by its author, not by a maintainer. There's #35780 which is still open.

@Xtron1234
Copy link

try to use move_and_slide_with_snap(x,y, Vector2.UP)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests