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:
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.
IsOnFloor()
or IsOnWall()
is set only after MoveAndSlide()
those IsOnSomething()
will be reset after function ends.
try to call it again after calling MoveAndSlide
.
Thanks @volzhs I'll try that now
Edit: Unfortunately the problem is still happening
I also made an issue about this earlier in the day, see #16250.
EDIT: That timing. You're good.
@LikeLakers2 are you using GDScript? This is happening to me on C#
@divmgl Yes I am. My example project is all GDscript.
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._
c#
// 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);
}
so... @divmgl your original issue is solved, right?
having another issue (same with #16250) though.
@volzhs no this is not solved, I was simply misunderstanding the problem
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.
Hey @eon-s thanks for your response. I've tweaked the safe margin and I'm still getting this issue.
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.
@groud I've updated the code and it's still happening.
@divmgl Can you give us the new code ?
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.
@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.
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);
}
@groud I'd like to propose that at the first time too. :)
and I'm doing it that way with my personal project.
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.
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.
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()
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.
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.
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.
@justinluk You are the man, saving the day
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
It鈥檚 impressive that two years after the fact this is still a problem. I鈥檝e since moved on from Godot.
I don't trust functions I don't write myself. :P
While in general I鈥檇 agree with this, the point of a solution like Godot is that you can focus on the product and less on the boilerplate code.
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.)
Hey there,
I've run into this Problem just today and found an easy solution.
...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.
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
EASY FIX:
always apply gravity, DONT DO THIS
if grounded:
motion.y -= 10
EASY FIX:
always apply gravity, DONT DO THISif 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.
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:
``` c#
var motion = Vector2.Zero;
// manipulate motion - including resetting motion.y to 0 when hitting the floor
MoveAndSlide(motion, Vector2.Up);
Instead:
``` c#
var motion = Vector2.Zero;
// manipulate motion - don't set motion.y to 0, MoveAndSlide will handle floor collision
motion = MoveAndSlide(motion, Vector2.Up);
Most helpful comment
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.